Merge branch 'master' into python
commit
12dbdb078c
|
@ -123,8 +123,8 @@ jobs:
|
||||||
- name: Set Up Source
|
- name: Set Up Source
|
||||||
run: rsync --filter=":- .gitignore" -r ./ pkg/snap/solvespace-snap-src
|
run: rsync --filter=":- .gitignore" -r ./ pkg/snap/solvespace-snap-src
|
||||||
- name: Build Snap
|
- name: Build Snap
|
||||||
|
uses: snapcore/action-build@v1
|
||||||
id: build
|
id: build
|
||||||
uses: diddlesnaps/snapcraft-multiarch-action@v1
|
|
||||||
with:
|
with:
|
||||||
path: pkg/snap
|
path: pkg/snap
|
||||||
- name: Upload & Release to Edge
|
- name: Upload & Release to Edge
|
||||||
|
@ -142,40 +142,6 @@ jobs:
|
||||||
snap: ${{ steps.build.outputs.snap }}
|
snap: ${{ steps.build.outputs.snap }}
|
||||||
release: edge,beta
|
release: edge,beta
|
||||||
|
|
||||||
deploy_snap_arm64:
|
|
||||||
needs: [test_ubuntu, test_windows, test_macos]
|
|
||||||
name: Deploy ARM64 Snap
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: docker/setup-qemu-action@v1
|
|
||||||
with:
|
|
||||||
image: tonistiigi/binfmt@sha256:df15403e06a03c2f461c1f7938b171fda34a5849eb63a70e2a2109ed5a778bde
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Fetch Tags
|
|
||||||
run: git fetch --force --tags
|
|
||||||
- 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
|
|
||||||
|
|
||||||
upload_release_assets:
|
upload_release_assets:
|
||||||
name: Upload Release Assets
|
name: Upload Release Assets
|
||||||
needs: [build_release_windows, build_release_windows_openmp, build_release_macos]
|
needs: [build_release_windows, build_release_windows_openmp, build_release_macos]
|
||||||
|
|
|
@ -21,6 +21,11 @@ jobs:
|
||||||
dir_name="solvespace-${version}"
|
dir_name="solvespace-${version}"
|
||||||
archive_name="${dir_name}.tar.xz"
|
archive_name="${dir_name}.tar.xz"
|
||||||
archive_path="${HOME}/${archive_name}"
|
archive_path="${HOME}/${archive_name}"
|
||||||
|
commit_sha="$GITHUB_SHA"
|
||||||
|
|
||||||
|
sed -e 's/^\(include(GetGitCommitHash)\)/#\1/' \
|
||||||
|
-e 's/^# \(set(GIT_COMMIT_HASH\).*/\1 '"$commit_sha"')/' \
|
||||||
|
-i CMakeLists.txt
|
||||||
|
|
||||||
echo "::set-output name=archive_name::${archive_name}"
|
echo "::set-output name=archive_name::${archive_name}"
|
||||||
echo "::set-output name=archive_path::${archive_path}"
|
echo "::set-output name=archive_path::${archive_path}"
|
||||||
|
|
|
@ -42,3 +42,20 @@ jobs:
|
||||||
run: .github/scripts/install-macos.sh ci
|
run: .github/scripts/install-macos.sh ci
|
||||||
- name: Build & Test
|
- name: Build & Test
|
||||||
run: .github/scripts/build-macos.sh debug arm64 && .github/scripts/build-macos.sh debug x86_64
|
run: .github/scripts/build-macos.sh debug arm64 && .github/scripts/build-macos.sh debug x86_64
|
||||||
|
|
||||||
|
test_flatpak:
|
||||||
|
name: Test Flatpak x86_64
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: bilelmoussaoui/flatpak-github-actions:freedesktop-21.08
|
||||||
|
options: --privileged
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v4
|
||||||
|
with:
|
||||||
|
bundle: "solvespace.flatpak"
|
||||||
|
manifest-path: "pkg/flatpak/com.solvespace.SolveSpace.json"
|
||||||
|
cache-key: flatpak-builder-${{ github.sha }}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
3.x - since the 3.0 release, only available in edge builds
|
3.1
|
||||||
---
|
---
|
||||||
|
|
||||||
Constraints:
|
Constraints:
|
||||||
|
|
|
@ -40,7 +40,7 @@ include(GetGitCommitHash)
|
||||||
|
|
||||||
string(SUBSTRING "${GIT_COMMIT_HASH}" 0 8 solvespace_GIT_HASH)
|
string(SUBSTRING "${GIT_COMMIT_HASH}" 0 8 solvespace_GIT_HASH)
|
||||||
project(solvespace
|
project(solvespace
|
||||||
VERSION 3.0
|
VERSION 3.1
|
||||||
LANGUAGES C CXX ASM)
|
LANGUAGES C CXX ASM)
|
||||||
|
|
||||||
set(ENABLE_GUI ON CACHE BOOL
|
set(ENABLE_GUI ON CACHE BOOL
|
||||||
|
@ -91,6 +91,10 @@ endif()
|
||||||
if(MINGW)
|
if(MINGW)
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++")
|
||||||
|
# Link 32 bit SolveSpace with --large-address-aware which allows it to access
|
||||||
|
# up to 3GB on a properly configured 32 bit Windows and up to 4GB on 64 bit.
|
||||||
|
# See https://msdn.microsoft.com/en-us/library/aa366778
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--large-address-aware")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Ensure that all platforms use 64-bit IEEE floating point operations for consistency;
|
# Ensure that all platforms use 64-bit IEEE floating point operations for consistency;
|
||||||
|
@ -182,12 +186,13 @@ if(APPLE)
|
||||||
set(CMAKE_FIND_FRAMEWORK LAST)
|
set(CMAKE_FIND_FRAMEWORK LAST)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
set(M_LIBRARY "" CACHE STRING "libm (not necessary)" FORCE)
|
||||||
|
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 eigen")
|
|
||||||
include_directories(extlib/eigen)
|
|
||||||
|
|
||||||
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 "")
|
||||||
|
@ -199,16 +204,19 @@ set(MIMALLOC_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/mimalloc/include)
|
||||||
if(NOT FORCE_VENDORED_Eigen3)
|
if(NOT FORCE_VENDORED_Eigen3)
|
||||||
find_package(Eigen3 CONFIG)
|
find_package(Eigen3 CONFIG)
|
||||||
endif()
|
endif()
|
||||||
if(FORCE_VENDORED_Eigen3 OR NOT EIGEN3_FOUND)
|
if(FORCE_VENDORED_Eigen3 OR NOT EIGEN3_INCLUDE_DIRS)
|
||||||
message(STATUS "Using in-tree Eigen")
|
message(STATUS "Using in-tree Eigen")
|
||||||
set(EIGEN3_FOUND YES)
|
set(EIGEN3_FOUND YES)
|
||||||
set(EIGEN3_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/eigen)
|
set(EIGEN3_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/eigen)
|
||||||
else()
|
else()
|
||||||
message(STATUS "Using system Eigen: ${EIGEN3_INCLUDE_DIRS}")
|
message(STATUS "Using system Eigen: ${EIGEN3_INCLUDE_DIRS}")
|
||||||
endif()
|
endif()
|
||||||
|
if(NOT EXISTS "${EIGEN3_INCLUDE_DIRS}")
|
||||||
|
message(FATAL_ERROR "Eigen 3 not found on system or in-tree")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
if(WIN32 OR APPLE)
|
if(WIN32 OR APPLE OR EMSCRIPTEN)
|
||||||
# On Win32 and macOS we use vendored packages, since there is little to no benefit
|
# On Win32 and macOS we use vendored packages, since there is little to no benefit
|
||||||
# to trying to find system versions. In particular, trying to link to libraries from
|
# to trying to find system versions. In particular, trying to link to libraries from
|
||||||
# Homebrew or macOS system libraries into the .app file is highly likely to result
|
# Homebrew or macOS system libraries into the .app file is highly likely to result
|
||||||
|
@ -269,7 +277,7 @@ else()
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB REQUIRED)
|
||||||
find_package(PNG REQUIRED)
|
find_package(PNG REQUIRED)
|
||||||
find_package(Freetype REQUIRED)
|
find_package(Freetype REQUIRED)
|
||||||
pkg_check_modules(CAIRO REQUIRED cairo)
|
find_package(Cairo REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# GUI dependencies
|
# GUI dependencies
|
||||||
|
@ -303,6 +311,8 @@ if(ENABLE_GUI)
|
||||||
elseif(APPLE)
|
elseif(APPLE)
|
||||||
find_package(OpenGL REQUIRED)
|
find_package(OpenGL REQUIRED)
|
||||||
find_library(APPKIT_LIBRARY AppKit REQUIRED)
|
find_library(APPKIT_LIBRARY AppKit REQUIRED)
|
||||||
|
elseif(EMSCRIPTEN)
|
||||||
|
# Everything is built in
|
||||||
else()
|
else()
|
||||||
find_package(OpenGL REQUIRED)
|
find_package(OpenGL REQUIRED)
|
||||||
find_package(SpaceWare)
|
find_package(SpaceWare)
|
||||||
|
@ -373,9 +383,19 @@ if(MSVC)
|
||||||
# Same for the (C99) __func__ special variable; we use it only in C++ code.
|
# Same for the (C99) __func__ special variable; we use it only in C++ code.
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D__func__=__FUNCTION__")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D__func__=__FUNCTION__")
|
||||||
|
|
||||||
|
# Multi-processor Compilation
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
|
||||||
|
|
||||||
# We rely on these /we flags. They correspond to the GNU-style flags below as
|
# We rely on these /we flags. They correspond to the GNU-style flags below as
|
||||||
# follows: /w4062=-Wswitch
|
# follows: /w4062=-Wswitch
|
||||||
set(WARNING_FLAGS "${WARNING_FLAGS} /we4062")
|
set(WARNING_FLAGS "${WARNING_FLAGS} /we4062")
|
||||||
|
|
||||||
|
# Link 32 bit SolveSpace with /LARGEADDRESSAWARE which allows it to access
|
||||||
|
# up to 3GB on a properly configured 32 bit Windows and up to 4GB on 64 bit.
|
||||||
|
# See https://msdn.microsoft.com/en-us/library/aa366778
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LARGEADDRESSAWARE")
|
||||||
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /LARGEADDRESSAWARE")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
|
111
README.md
111
README.md
|
@ -33,6 +33,14 @@ automatically built by the SolveSpace maintainers for each stable release.
|
||||||
|
|
||||||
[rel]: https://github.com/solvespace/solvespace/releases
|
[rel]: https://github.com/solvespace/solvespace/releases
|
||||||
|
|
||||||
|
### Via Flathub
|
||||||
|
|
||||||
|
Official releases can be installed as a Flatpak from Flathub.
|
||||||
|
|
||||||
|
[Get SolveSpace from Flathub](https://flathub.org/apps/details/com.solvespace.SolveSpace)
|
||||||
|
|
||||||
|
These should work on any Linux distribution that supports Flatpak.
|
||||||
|
|
||||||
### Via Snap Store
|
### Via Snap Store
|
||||||
|
|
||||||
Official releases can be installed from the `stable` channel.
|
Official releases can be installed from the `stable` channel.
|
||||||
|
@ -68,18 +76,18 @@ from the following links:
|
||||||
Extract the downloaded archive and install or execute the contained file as is
|
Extract the downloaded archive and install or execute the contained file as is
|
||||||
appropriate for your platform.
|
appropriate for your platform.
|
||||||
|
|
||||||
### Via third-party packages
|
|
||||||
|
|
||||||
_Third-party_ nightly binary packages for Debian and Ubuntu are available via
|
|
||||||
[notesalexp.org][notesalexp]. These packages are automatically built from
|
|
||||||
non-released source code. The SolveSpace maintainers do not control the contents
|
|
||||||
of these packages and cannot guarantee their functionality.
|
|
||||||
|
|
||||||
[notesalexp]: https://notesalexp.org/packages/en/source/solvespace/
|
|
||||||
|
|
||||||
### Via source code
|
### Via source code
|
||||||
|
|
||||||
See below.
|
Irrespective of the OS used, before building, check out the project and the
|
||||||
|
necessary submodules:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/solvespace/solvespace
|
||||||
|
cd solvespace
|
||||||
|
git submodule update --init
|
||||||
|
```
|
||||||
|
|
||||||
|
You will need `git`. See the platform specific instructions below to install it.
|
||||||
|
|
||||||
## Building on Linux
|
## Building on Linux
|
||||||
|
|
||||||
|
@ -106,13 +114,7 @@ sudo dnf install git gcc-c++ cmake zlib-devel libpng-devel \
|
||||||
mesa-libGL-devel mesa-libGLU-devel libspnav-devel
|
mesa-libGL-devel mesa-libGLU-devel libspnav-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
Before building, check out the project and the necessary submodules:
|
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||||
|
|
||||||
```sh
|
|
||||||
git clone https://github.com/solvespace/solvespace
|
|
||||||
cd solvespace
|
|
||||||
git submodule update --init extlib/libdxfrw extlib/mimalloc extlib/eigen
|
|
||||||
```
|
|
||||||
|
|
||||||
After that, build SolveSpace as following:
|
After that, build SolveSpace as following:
|
||||||
|
|
||||||
|
@ -146,13 +148,7 @@ Debian derivative (e.g. Ubuntu) these can be installed with:
|
||||||
apt-get install git build-essential cmake mingw-w64
|
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](#via-source-code).
|
||||||
|
|
||||||
```sh
|
|
||||||
git clone https://github.com/solvespace/solvespace
|
|
||||||
cd solvespace
|
|
||||||
git submodule update --init
|
|
||||||
```
|
|
||||||
|
|
||||||
Build 64-bit SolveSpace with the following:
|
Build 64-bit SolveSpace with the following:
|
||||||
|
|
||||||
|
@ -169,6 +165,45 @@ command-line interface is built as `build/bin/solvespace-cli.exe`.
|
||||||
|
|
||||||
Space Navigator support will not be available.
|
Space Navigator support will not be available.
|
||||||
|
|
||||||
|
### Building for web (very experimental)
|
||||||
|
|
||||||
|
**Please note that this port contains many critical bugs and unimplemented core functions.**
|
||||||
|
|
||||||
|
You will need the usual build tools, cmake and [Emscripten][]. On a Debian derivative (e.g. Ubuntu) dependencies other than Emscripten can be installed with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
apt-get install git build-essential cmake
|
||||||
|
```
|
||||||
|
|
||||||
|
First, install and prepare `emsdk`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/emscripten-core/emsdk
|
||||||
|
cd emsdk
|
||||||
|
./emsdk install latest
|
||||||
|
./emsdk activate latest
|
||||||
|
source ./emsdk_env.sh
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||||
|
|
||||||
|
After that, build SolveSpace as following:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
emcmake cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_LTO="ON" -DENABLE_TESTS="OFF" -DENABLE_CLI="OFF" -DENABLE_COVERAGE="OFF"
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
The graphical interface is built as multiple files in the `build/bin` directory with names
|
||||||
|
starting with `solvespace`. It can be run locally with `emrun build/bin/solvespace.html`.
|
||||||
|
|
||||||
|
The command-line interface is not available.
|
||||||
|
|
||||||
|
[emscripten]: https://emscripten.org/
|
||||||
|
|
||||||
## Building on macOS
|
## Building on macOS
|
||||||
|
|
||||||
You will need git, XCode tools, CMake and libomp. Git, CMake and libomp can be installed
|
You will need git, XCode tools, CMake and libomp. Git, CMake and libomp can be installed
|
||||||
|
@ -181,13 +216,7 @@ brew install git cmake libomp
|
||||||
XCode has to be installed via AppStore or [the Apple website][appledeveloper];
|
XCode has to be installed via AppStore or [the Apple website][appledeveloper];
|
||||||
it requires a free Apple ID.
|
it requires a free Apple ID.
|
||||||
|
|
||||||
Before building, check out the project and the necessary submodules:
|
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||||
|
|
||||||
```sh
|
|
||||||
git clone https://github.com/solvespace/solvespace
|
|
||||||
cd solvespace
|
|
||||||
git submodule update --init
|
|
||||||
```
|
|
||||||
|
|
||||||
After that, build SolveSpace as following:
|
After that, build SolveSpace as following:
|
||||||
|
|
||||||
|
@ -225,13 +254,7 @@ These can be installed from the ports tree:
|
||||||
pkg_add -U git cmake libexecinfo png json-c gtk3mm pangomm
|
pkg_add -U git cmake libexecinfo png json-c gtk3mm pangomm
|
||||||
```
|
```
|
||||||
|
|
||||||
Before building, check out the project and the necessary submodules:
|
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||||
|
|
||||||
```sh
|
|
||||||
git clone https://github.com/solvespace/solvespace
|
|
||||||
cd solvespace
|
|
||||||
git submodule update --init extlib/libdxfrw extlib/mimalloc extlib/eigen
|
|
||||||
```
|
|
||||||
|
|
||||||
After that, build SolveSpace as following:
|
After that, build SolveSpace as following:
|
||||||
|
|
||||||
|
@ -254,10 +277,14 @@ by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation.
|
||||||
You will need [git][gitwin], [cmake][cmakewin] and a C++ compiler
|
You will need [git][gitwin], [cmake][cmakewin] and a C++ compiler
|
||||||
(either Visual C++ or MinGW). If using Visual C++, Visual Studio 2015
|
(either Visual C++ or MinGW). If using Visual C++, Visual Studio 2015
|
||||||
or later is required.
|
or later is required.
|
||||||
|
If gawk is in your path be sure it is a proper Windows port that can handle CL LF line endings.
|
||||||
|
If not CMake may fail in libpng due to some awk scripts - issue #1228.
|
||||||
|
|
||||||
|
Before building, [check out the project and the necessary submodules](#via-source-code).
|
||||||
|
|
||||||
### Building with Visual Studio IDE
|
### Building with Visual Studio IDE
|
||||||
|
|
||||||
Check out the git submodules. Create a directory `build` in
|
Create a directory `build` in
|
||||||
the source tree and point cmake-gui to the source tree and that directory.
|
the source tree and point cmake-gui to the source tree and that directory.
|
||||||
Press "Configure" and "Generate", then open `build\solvespace.sln` with
|
Press "Configure" and "Generate", then open `build\solvespace.sln` with
|
||||||
Visual C++ and build it.
|
Visual C++ and build it.
|
||||||
|
@ -269,9 +296,6 @@ First, ensure that `git` and `cl` (the Visual C++ compiler driver) are in your
|
||||||
Visual Studio install. Then, run the following in cmd or PowerShell:
|
Visual Studio install. Then, run the following in cmd or PowerShell:
|
||||||
|
|
||||||
```bat
|
```bat
|
||||||
git clone https://github.com/solvespace/solvespace
|
|
||||||
cd solvespace
|
|
||||||
git submodule update --init
|
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
|
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
|
||||||
|
@ -287,9 +311,6 @@ First, ensure that git and gcc are in your `$PATH`. Then, run the following
|
||||||
in bash:
|
in bash:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/solvespace/solvespace
|
|
||||||
cd solvespace
|
|
||||||
git submodule update --init
|
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||||
|
|
|
@ -4,12 +4,12 @@ function(disable_warnings)
|
||||||
if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w" PARENT_SCOPE)
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w" PARENT_SCOPE)
|
||||||
elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
|
elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W0" PARENT_SCOPE)
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W0 /MP" PARENT_SCOPE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w" PARENT_SCOPE)
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w" PARENT_SCOPE)
|
||||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W0" PARENT_SCOPE)
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W0 /MP" PARENT_SCOPE)
|
||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
# - Try to find Cairo
|
||||||
|
# Once done, this will define
|
||||||
|
#
|
||||||
|
# CAIRO_FOUND - system has Cairo
|
||||||
|
# CAIRO_INCLUDE_DIRS - the Cairo include directories
|
||||||
|
# CAIRO_LIBRARIES - link these to use Cairo
|
||||||
|
#
|
||||||
|
# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org>
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS
|
||||||
|
# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS
|
||||||
|
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
find_package(PkgConfig)
|
||||||
|
pkg_check_modules(PC_CAIRO QUIET cairo)
|
||||||
|
|
||||||
|
find_path(CAIRO_INCLUDE_DIRS
|
||||||
|
NAMES cairo.h
|
||||||
|
HINTS ${PC_CAIRO_INCLUDEDIR}
|
||||||
|
${PC_CAIRO_INCLUDE_DIRS}
|
||||||
|
PATH_SUFFIXES cairo
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(CAIRO_LIBRARIES
|
||||||
|
NAMES cairo
|
||||||
|
HINTS ${PC_CAIRO_LIBDIR}
|
||||||
|
${PC_CAIRO_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (CAIRO_INCLUDE_DIRS)
|
||||||
|
if (EXISTS "${CAIRO_INCLUDE_DIRS}/cairo-version.h")
|
||||||
|
file(READ "${CAIRO_INCLUDE_DIRS}/cairo-version.h" CAIRO_VERSION_CONTENT)
|
||||||
|
|
||||||
|
string(REGEX MATCH "#define +CAIRO_VERSION_MAJOR +([0-9]+)" _dummy "${CAIRO_VERSION_CONTENT}")
|
||||||
|
set(CAIRO_VERSION_MAJOR "${CMAKE_MATCH_1}")
|
||||||
|
|
||||||
|
string(REGEX MATCH "#define +CAIRO_VERSION_MINOR +([0-9]+)" _dummy "${CAIRO_VERSION_CONTENT}")
|
||||||
|
set(CAIRO_VERSION_MINOR "${CMAKE_MATCH_1}")
|
||||||
|
|
||||||
|
string(REGEX MATCH "#define +CAIRO_VERSION_MICRO +([0-9]+)" _dummy "${CAIRO_VERSION_CONTENT}")
|
||||||
|
set(CAIRO_VERSION_MICRO "${CMAKE_MATCH_1}")
|
||||||
|
|
||||||
|
set(CAIRO_VERSION "${CAIRO_VERSION_MAJOR}.${CAIRO_VERSION_MINOR}.${CAIRO_VERSION_MICRO}")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if ("${Cairo_FIND_VERSION}" VERSION_GREATER "${CAIRO_VERSION}")
|
||||||
|
message(FATAL_ERROR "Required version (" ${Cairo_FIND_VERSION} ") is higher than found version (" ${CAIRO_VERSION} ")")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Cairo REQUIRED_VARS CAIRO_INCLUDE_DIRS CAIRO_LIBRARIES
|
||||||
|
VERSION_VAR CAIRO_VERSION)
|
||||||
|
|
||||||
|
mark_as_advanced(
|
||||||
|
CAIRO_INCLUDE_DIRS
|
||||||
|
CAIRO_LIBRARIES
|
||||||
|
)
|
|
@ -0,0 +1,20 @@
|
||||||
|
set(EMSCRIPTEN 1)
|
||||||
|
|
||||||
|
set(CMAKE_C_OUTPUT_EXTENSION ".o")
|
||||||
|
set(CMAKE_CXX_OUTPUT_EXTENSION ".o")
|
||||||
|
set(CMAKE_EXECUTABLE_SUFFIX ".html")
|
||||||
|
|
||||||
|
set(CMAKE_SIZEOF_VOID_P 4)
|
||||||
|
|
||||||
|
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE)
|
||||||
|
|
||||||
|
# FIXME(emscripten): Suppress non-c-typedef-for-linkage warnings in solvespace.h
|
||||||
|
add_compile_options(-Wno-non-c-typedef-for-linkage)
|
||||||
|
|
||||||
|
|
||||||
|
# Enable optimization. Workaround for "too many locals" error when runs on browser.
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL Release)
|
||||||
|
add_compile_options(-O2)
|
||||||
|
else()
|
||||||
|
add_compile_options(-O1)
|
||||||
|
endif()
|
|
@ -0,0 +1,8 @@
|
||||||
|
set(CMAKE_SYSTEM_NAME Emscripten)
|
||||||
|
|
||||||
|
set(TRIPLE asmjs-unknown-emscripten)
|
||||||
|
|
||||||
|
set(CMAKE_C_COMPILER emcc)
|
||||||
|
set(CMAKE_CXX_COMPILER em++)
|
||||||
|
|
||||||
|
set(M_LIBRARY m)
|
|
@ -4,3 +4,7 @@ if(MSVC)
|
||||||
set(CMAKE_C_FLAGS_RELEASE_INIT "/MT /O2 /Ob2 /D NDEBUG")
|
set(CMAKE_C_FLAGS_RELEASE_INIT "/MT /O2 /Ob2 /D NDEBUG")
|
||||||
set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG")
|
set(CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
set(CMAKE_C_FLAGS_DEBUG_INIT "-g4")
|
||||||
|
endif()
|
||||||
|
|
|
@ -4,3 +4,7 @@ if(MSVC)
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE_INIT "/MT /O2 /Ob2 /D NDEBUG")
|
set(CMAKE_CXX_FLAGS_RELEASE_INIT "/MT /O2 /Ob2 /D NDEBUG")
|
||||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG")
|
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
set(CMAKE_CXX_FLAGS_DEBUG_INIT "-g4")
|
||||||
|
endif()
|
||||||
|
|
|
@ -6,3 +6,8 @@ add_executable(CDemo
|
||||||
|
|
||||||
target_link_libraries(CDemo
|
target_link_libraries(CDemo
|
||||||
slvs)
|
slvs)
|
||||||
|
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
set_target_properties(CDemo PROPERTIES
|
||||||
|
LINK_FLAGS "-s TOTAL_MEMORY=134217728")
|
||||||
|
endif()
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 4e643b6d3178e0ea2a093b7e14fe621631a91e4b
|
Subproject commit f819dbb4e4813fab464aee16770f39f11476bfea
|
|
@ -1,69 +1,97 @@
|
||||||
{
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/TingPing/flatpak-manifest-schema/master/flatpak-manifest.schema",
|
||||||
"app-id": "com.solvespace.SolveSpace",
|
"app-id": "com.solvespace.SolveSpace",
|
||||||
"runtime": "org.freedesktop.Platform",
|
"runtime": "org.freedesktop.Platform",
|
||||||
"runtime-version": "20.08",
|
"runtime-version": "21.08",
|
||||||
"sdk": "org.freedesktop.Sdk",
|
"sdk": "org.freedesktop.Sdk",
|
||||||
"finish-args": [
|
"finish-args": [
|
||||||
/* Access to display server and OpenGL */
|
"--device=dri",
|
||||||
"--share=ipc",
|
"--share=ipc",
|
||||||
"--socket=fallback-x11",
|
"--socket=fallback-x11",
|
||||||
"--socket=wayland",
|
"--socket=wayland"
|
||||||
"--device=dri",
|
|
||||||
/* Access to save files */
|
|
||||||
"--filesystem=home"
|
|
||||||
],
|
],
|
||||||
"cleanup": [
|
"cleanup": [
|
||||||
"/include",
|
"/include",
|
||||||
"/lib/*/include",
|
"/lib/cmake",
|
||||||
"*.a",
|
"/lib/pkgconfig",
|
||||||
"*.la",
|
|
||||||
"*.m4",
|
|
||||||
"/lib/libslvs*.so*",
|
|
||||||
"/lib/libglibmm_generate_extra_defs*.so*",
|
|
||||||
"/share/pkgconfig",
|
|
||||||
"*.pc",
|
|
||||||
"/share/man",
|
|
||||||
"/share/doc",
|
|
||||||
"/share/aclocal",
|
"/share/aclocal",
|
||||||
/* mm-common junk */
|
"/share/pkgconfig",
|
||||||
"/bin/mm-common-prepare",
|
"*.la"
|
||||||
"/share/mm-common"
|
|
||||||
],
|
],
|
||||||
"command": "solvespace",
|
"command": "solvespace",
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"name": "mm-common",
|
"name": "mm-common",
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "https://download.gnome.org/sources/mm-common/1.0/mm-common-1.0.2.tar.xz",
|
|
||||||
"sha256": "a2a99f3fa943cf662f189163ed39a2cfc19a428d906dd4f92b387d3659d1641d"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sigc++",
|
|
||||||
"config-opts": [
|
|
||||||
"--disable-documentation"
|
|
||||||
],
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "https://download.gnome.org/sources/libsigc++/2.10/libsigc%2B%2B-2.10.6.tar.xz",
|
|
||||||
"sha256": "dda176dc4681bda9d5a2ac1bc55273bdd381662b7a6d49e918267d13e8774e1b"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "glibmm",
|
|
||||||
"config-opts": [],
|
|
||||||
"buildsystem": "meson",
|
"buildsystem": "meson",
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://download.gnome.org/sources/glibmm/2.64/glibmm-2.64.5.tar.xz",
|
"url": "https://download.gnome.org/sources/mm-common/1.0/mm-common-1.0.4.tar.xz",
|
||||||
"sha256": "508fc86e2c9141198aa16c225b16fd6b911917c0d3817602652844d0973ea386"
|
"sha256": "e954c09b4309a7ef93e13b69260acdc5738c907477eb381b78bb1e414ee6dbd8",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "mm-common",
|
||||||
|
"stable-only": true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/bin",
|
||||||
|
"/share/doc",
|
||||||
|
"/share/man",
|
||||||
|
"/share/mm-common"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sigc++",
|
||||||
|
"buildsystem": "meson",
|
||||||
|
"config-opts": [
|
||||||
|
"-Dbuild-examples=false"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://download.gnome.org/sources/libsigc++/2.10/libsigc++-2.10.8.tar.xz",
|
||||||
|
"sha256": "235a40bec7346c7b82b6a8caae0456353dc06e71f14bc414bcc858af1838719a",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "libsigc++",
|
||||||
|
"stable-only": true,
|
||||||
|
"versions": {
|
||||||
|
"<": "3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/lib/sigc++-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "glibmm",
|
||||||
|
"buildsystem": "meson",
|
||||||
|
"config-opts": [
|
||||||
|
"-Dbuild-examples=false"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://download.gnome.org/sources/glibmm/2.66/glibmm-2.66.4.tar.xz",
|
||||||
|
"sha256": "199ace5682d81b15a1d565480b4a950682f2db6402c8aa5dd7217d71edff81d5",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "glibmm",
|
||||||
|
"stable-only": true,
|
||||||
|
"versions": {
|
||||||
|
"<": "2.68.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/lib/giomm-*",
|
||||||
|
"/lib/glibmm-*",
|
||||||
|
"/lib/libglibmm_generate_extra_defs-*.so*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -74,76 +102,152 @@
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "http://ftp.gnome.org/pub/GNOME/sources/cairomm/1.12/cairomm-1.12.0.tar.xz",
|
"url": "https://download.gnome.org/sources/cairomm/1.12/cairomm-1.12.0.tar.xz",
|
||||||
"sha256": "a54ada8394a86182525c0762e6f50db6b9212a2109280d13ec6a0b29bfd1afe6"
|
"sha256": "a54ada8394a86182525c0762e6f50db6b9212a2109280d13ec6a0b29bfd1afe6",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "cairomm",
|
||||||
|
"stable-only": true,
|
||||||
|
"versions": {
|
||||||
|
"<": "1.16.0"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/lib/cairomm-*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pangomm",
|
"name": "pangomm",
|
||||||
"config-opts": [
|
|
||||||
"--disable-documentation"
|
|
||||||
],
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "http://ftp.gnome.org/pub/GNOME/sources/pangomm/2.40/pangomm-2.40.2.tar.xz",
|
|
||||||
"sha256": "0a97aa72513db9088ca3034af923484108746dba146e98ed76842cf858322d05"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "atkmm",
|
|
||||||
"config-opts": [
|
|
||||||
"--disable-documentation"
|
|
||||||
],
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "http://ftp.gnome.org/pub/GNOME/sources/atkmm/2.28/atkmm-2.28.0.tar.xz",
|
|
||||||
"sha256": "4c4cfc917fd42d3879ce997b463428d6982affa0fb660cafcc0bc2d9afcedd3a"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "gtkmm",
|
|
||||||
"config-opts": [],
|
|
||||||
"buildsystem": "meson",
|
"buildsystem": "meson",
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://download.gnome.org/sources/gtkmm/3.24/gtkmm-3.24.4.tar.xz",
|
"url": "https://download.gnome.org/sources/pangomm/2.46/pangomm-2.46.2.tar.xz",
|
||||||
"sha256": "9beb71c3e90cfcfb790396b51e3f5e7169966751efd4f3ef9697114be3be6743"
|
"sha256": "57442ab4dc043877bfe3839915731ab2d693fc6634a71614422fb530c9eaa6f4",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "pangomm",
|
||||||
|
"stable-only": true,
|
||||||
|
"versions": {
|
||||||
|
"<": "2.48.0"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/lib/pangomm-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atkmm",
|
||||||
|
"buildsystem": "meson",
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://download.gnome.org/sources/atkmm/2.28/atkmm-2.28.2.tar.xz",
|
||||||
|
"sha256": "a0bb49765ceccc293ab2c6735ba100431807d384ffa14c2ebd30e07993fd2fa4",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "atkmm",
|
||||||
|
"stable-only": true,
|
||||||
|
"versions": {
|
||||||
|
"<": "2.30.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/lib/atkmm-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gtkmm",
|
||||||
|
"buildsystem": "meson",
|
||||||
|
"config-opts": [
|
||||||
|
"-Dbuild-demos=false",
|
||||||
|
"-Dbuild-tests=false"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://download.gnome.org/sources/gtkmm/3.24/gtkmm-3.24.6.tar.xz",
|
||||||
|
"sha256": "4b3e142e944e1633bba008900605c341a93cfd755a7fa2a00b05d041341f11d6",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "gnome",
|
||||||
|
"name": "gtkmm",
|
||||||
|
"stable-only": true,
|
||||||
|
"versions": {
|
||||||
|
"<": "4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/lib/gdkmm-*",
|
||||||
|
"/lib/gtkmm-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eigen",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"builddir": true,
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz",
|
||||||
|
"sha256": "8586084f71f9bde545ee7fa6d00288b264a2b7ac3607b974e54d13e7162c1c72",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "anitya",
|
||||||
|
"project-id": 13751,
|
||||||
|
"stable-only": true,
|
||||||
|
"url-template": "https://gitlab.com/libeigen/eigen/-/archive/$version/eigen-$version.tar.gz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "libjson-c",
|
"name": "libjson-c",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"builddir": true,
|
||||||
|
"config-opts": [
|
||||||
|
"-DBUILD_STATIC_LIBS=OFF",
|
||||||
|
"-DENABLE_THREADING=ON"
|
||||||
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
/* 0.15-nodoc doesn't build */
|
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://s3.amazonaws.com/json-c_releases/releases/json-c-0.13.1-nodoc.tar.gz",
|
"url": "https://s3.amazonaws.com/json-c_releases/releases/json-c-0.16.tar.gz",
|
||||||
"sha256": "94a26340c0785fcff4f46ff38609cf84ebcd670df0c8efd75d039cc951d80132"
|
"sha256": "8e45ac8f96ec7791eaf3bb7ee50e9c2100bbbc87b8d0f1d030c5ba8a0288d96b",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "anitya",
|
||||||
|
"project-id": 1477,
|
||||||
|
"stable-only": true,
|
||||||
|
"url-template": "https://s3.amazonaws.com/json-c_releases/releases/json-c-$version.tar.gz"
|
||||||
}
|
}
|
||||||
],
|
}
|
||||||
"buildsystem": "cmake",
|
]
|
||||||
"builddir": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "SolveSpace",
|
"name": "solvespace",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"builddir": true,
|
||||||
|
"config-opts": [
|
||||||
|
"-DFLATPAK=ON",
|
||||||
|
"-DENABLE_TESTS=OFF"
|
||||||
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "dir",
|
"type": "dir",
|
||||||
"path": "../.."
|
"path": "../.."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"buildsystem": "cmake",
|
"cleanup": [
|
||||||
"builddir": true,
|
"/lib/libslvs*.so*"
|
||||||
"config-opts": [
|
|
||||||
"-DFLATPAK=ON",
|
|
||||||
"-DENABLE_CLI=OFF",
|
|
||||||
"-DENABLE_TESTS=OFF"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: solvespace
|
name: solvespace
|
||||||
base: core20
|
base: core22
|
||||||
summary: Parametric 2d/3d CAD
|
summary: Parametric 2d/3d CAD
|
||||||
adopt-info: solvespace
|
adopt-info: solvespace
|
||||||
description: |
|
description: |
|
||||||
|
@ -15,6 +15,7 @@ description: |
|
||||||
confinement: strict
|
confinement: strict
|
||||||
license: GPL-3.0
|
license: GPL-3.0
|
||||||
compression: lzo
|
compression: lzo
|
||||||
|
grade: stable
|
||||||
|
|
||||||
layout:
|
layout:
|
||||||
/usr/share/solvespace:
|
/usr/share/solvespace:
|
||||||
|
@ -24,11 +25,11 @@ apps:
|
||||||
solvespace:
|
solvespace:
|
||||||
command: usr/bin/solvespace
|
command: usr/bin/solvespace
|
||||||
desktop: solvespace.desktop
|
desktop: solvespace.desktop
|
||||||
extensions: [gnome-3-38]
|
extensions: [gnome]
|
||||||
plugs: [opengl, unity7, home, removable-media, gsettings, network]
|
plugs: [opengl, unity7, home, removable-media, gsettings, network]
|
||||||
cli:
|
cli:
|
||||||
command: usr/bin/solvespace-cli
|
command: usr/bin/solvespace-cli
|
||||||
extensions: [gnome-3-38]
|
extensions: [gnome]
|
||||||
plugs: [home, removable-media, network]
|
plugs: [home, removable-media, network]
|
||||||
|
|
||||||
parts:
|
parts:
|
||||||
|
@ -37,16 +38,14 @@ parts:
|
||||||
source: ./solvespace-snap-src
|
source: ./solvespace-snap-src
|
||||||
source-type: local
|
source-type: local
|
||||||
override-pull: |
|
override-pull: |
|
||||||
snapcraftctl pull
|
craftctl default
|
||||||
git submodule update --init extlib/libdxfrw extlib/mimalloc extlib/eigen
|
git submodule update --init extlib/libdxfrw extlib/mimalloc extlib/eigen
|
||||||
override-build: |
|
override-build: |
|
||||||
snapcraftctl build
|
craftctl default
|
||||||
project_version=$(grep CMAKE_PROJECT_VERSION:STATIC CMakeCache.txt | cut -d "=" -f2)
|
project_version=$(grep CMAKE_PROJECT_VERSION:STATIC CMakeCache.txt | cut -d "=" -f2)
|
||||||
cd $SNAPCRAFT_PART_SRC
|
cd $CRAFT_PART_SRC
|
||||||
version="$project_version~$(git rev-parse --short=8 HEAD)"
|
version="$project_version~$(git rev-parse --short=8 HEAD)"
|
||||||
snapcraftctl set-version "$version"
|
craftctl set version="$version"
|
||||||
git describe --exact-match HEAD && grade="stable" || grade="devel"
|
|
||||||
snapcraftctl set-grade "$grade"
|
|
||||||
cmake-parameters:
|
cmake-parameters:
|
||||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||||
- -DCMAKE_BUILD_TYPE=Release
|
- -DCMAKE_BUILD_TYPE=Release
|
||||||
|
@ -54,8 +53,6 @@ parts:
|
||||||
- -DSNAP=ON
|
- -DSNAP=ON
|
||||||
- -DENABLE_OPENMP=ON
|
- -DENABLE_OPENMP=ON
|
||||||
- -DENABLE_LTO=ON
|
- -DENABLE_LTO=ON
|
||||||
build-snaps:
|
|
||||||
- gnome-3-38-2004-sdk
|
|
||||||
build-packages:
|
build-packages:
|
||||||
- zlib1g-dev
|
- zlib1g-dev
|
||||||
- libpng-dev
|
- libpng-dev
|
||||||
|
@ -67,6 +64,7 @@ parts:
|
||||||
- libspnav-dev
|
- libspnav-dev
|
||||||
- git
|
- git
|
||||||
- g++
|
- g++
|
||||||
|
- libc6-dev
|
||||||
stage-packages:
|
stage-packages:
|
||||||
- libspnav0
|
- libspnav0
|
||||||
- libsigc++-2.0-0v5
|
- libsigc++-2.0-0v5
|
||||||
|
@ -74,14 +72,14 @@ parts:
|
||||||
cleanup:
|
cleanup:
|
||||||
after: [solvespace]
|
after: [solvespace]
|
||||||
plugin: nil
|
plugin: nil
|
||||||
build-snaps: [gnome-3-38-2004]
|
build-snaps: [gnome-42-2204]
|
||||||
override-prime: |
|
override-prime: |
|
||||||
set -eux
|
set -eux
|
||||||
for snap in "gnome-3-38-2004"; do # List all content-snaps you're using here
|
for snap in "gnome-42-2204"; do # List all content-snaps you're using here
|
||||||
cd "/snap/$snap/current" && find . -type f,l -exec rm -f "$SNAPCRAFT_PRIME/{}" "$SNAPCRAFT_PRIME/usr/{}" \;
|
cd "/snap/$snap/current" && find . -type f,l -exec rm -f "$CRAFT_PRIME/{}" "$CRAFT_PRIME/usr/{}" \;
|
||||||
done
|
done
|
||||||
for cruft in bug lintian man; do
|
for cruft in bug lintian man; do
|
||||||
rm -rf $SNAPCRAFT_PRIME/usr/share/$cruft
|
rm -rf $CRAFT_PRIME/usr/share/$cruft
|
||||||
done
|
done
|
||||||
find $SNAPCRAFT_PRIME/usr/share/doc/ -type f -not -name 'copyright' -delete
|
find $CRAFT_PRIME/usr/share/doc/ -type f -not -name 'copyright' -delete
|
||||||
find $SNAPCRAFT_PRIME/usr/share -type d -empty -delete
|
find $CRAFT_PRIME/usr/share -type d -empty -delete
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# First, set up registration functions for the kinds of resources we handle.
|
# First, set up registration functions for the kinds of resources we handle.
|
||||||
set(resource_root ${CMAKE_CURRENT_SOURCE_DIR}/)
|
set(resource_root ${CMAKE_CURRENT_SOURCE_DIR}/)
|
||||||
set(resource_list)
|
set(resource_list)
|
||||||
|
set(resource_names)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/win32/versioninfo.rc.in
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/win32/versioninfo.rc.in
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/win32/versioninfo.rc)
|
${CMAKE_CURRENT_BINARY_DIR}/win32/versioninfo.rc)
|
||||||
|
@ -83,6 +84,23 @@ elseif(APPLE)
|
||||||
DEPENDS ${source}
|
DEPENDS ${source}
|
||||||
VERBATIM)
|
VERBATIM)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
elseif(EMSCRIPTEN)
|
||||||
|
set(resource_dir ${CMAKE_BINARY_DIR}/src/res)
|
||||||
|
|
||||||
|
function(add_resource name)
|
||||||
|
set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name})
|
||||||
|
set(target ${resource_dir}/${name})
|
||||||
|
set(resource_list "${resource_list};${target}" PARENT_SCOPE)
|
||||||
|
set(resource_names "${resource_names};res/${name}" PARENT_SCOPE)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${target}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory ${resource_dir}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy ${source} ${target}
|
||||||
|
COMMENT "Copying resource ${name}"
|
||||||
|
DEPENDS ${source}
|
||||||
|
VERBATIM)
|
||||||
|
endfunction()
|
||||||
else() # Unix
|
else() # Unix
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
@ -112,6 +130,7 @@ function(add_resources)
|
||||||
foreach(name ${ARGN})
|
foreach(name ${ARGN})
|
||||||
add_resource(${name})
|
add_resource(${name})
|
||||||
set(resource_list "${resource_list}" PARENT_SCOPE)
|
set(resource_list "${resource_list}" PARENT_SCOPE)
|
||||||
|
set(resource_names "${resource_names}" PARENT_SCOPE)
|
||||||
endforeach()
|
endforeach()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
@ -262,6 +281,7 @@ add_resources(
|
||||||
icons/text-window/shaded.png
|
icons/text-window/shaded.png
|
||||||
icons/text-window/workplane.png
|
icons/text-window/workplane.png
|
||||||
locales.txt
|
locales.txt
|
||||||
|
locales/cs_CZ.po
|
||||||
locales/de_DE.po
|
locales/de_DE.po
|
||||||
locales/en_US.po
|
locales/en_US.po
|
||||||
locales/fr_FR.po
|
locales/fr_FR.po
|
||||||
|
@ -270,6 +290,7 @@ add_resources(
|
||||||
locales/tr_TR.po
|
locales/tr_TR.po
|
||||||
locales/ru_RU.po
|
locales/ru_RU.po
|
||||||
locales/zh_CN.po
|
locales/zh_CN.po
|
||||||
|
locales/ja_JP.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
|
||||||
|
@ -304,4 +325,6 @@ add_custom_target(resources
|
||||||
DEPENDS ${resource_list})
|
DEPENDS ${resource_list})
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set_property(TARGET resources PROPERTY EXTRA_SOURCES ${rc_file})
|
set_property(TARGET resources PROPERTY EXTRA_SOURCES ${rc_file})
|
||||||
|
elseif(EMSCRIPTEN)
|
||||||
|
set_property(TARGET resources PROPERTY NAMES ${resource_names})
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
SolveSpace is a free (GPLv3) parametric 3d CAD tool. Applications include:
|
SolveSpace is a free (GPLv3) parametric 3d CAD tool. Applications include:
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Modeling 3d parts — draw with extrudes, revolves, and Boolean operations</li>
|
<li>Modeling 3d parts — draw with extrudes, revolves/helix, and Boolean operations</li>
|
||||||
<li>Modeling 2d parts — draw the part as a single section, and export; use 3d assembly to verify fit</li>
|
<li>Modeling 2d parts — draw the part as a single section, and export; use 3d assembly to verify fit</li>
|
||||||
<li>Modeling 3d-printed parts — export the STL or other triangle mesh expected by most slicers</li>
|
<li>Modeling 3d-printed parts — export the STL or other triangle mesh expected by most slicers</li>
|
||||||
<li>Preparing 2D CAM data — export 2d vector art for a waterjet machine or laser cutter</li>
|
<li>Preparing 2D CAM data — export 2d vector art for a waterjet machine or laser cutter</li>
|
||||||
|
@ -31,6 +31,34 @@
|
||||||
<url type="bugtracker">https://github.com/solvespace/solvespace/issues</url>
|
<url type="bugtracker">https://github.com/solvespace/solvespace/issues</url>
|
||||||
|
|
||||||
<launchable type="desktop-id">@DESKTOP_FILE_NAME@</launchable>
|
<launchable type="desktop-id">@DESKTOP_FILE_NAME@</launchable>
|
||||||
|
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<caption>Main window with an empty document</caption>
|
||||||
|
<image>https://solvespace.com/pics/window-linux-main.png</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<caption>Property Browser with an empty document</caption>
|
||||||
|
<image>https://solvespace.com/pics/window-linux-property-browser.png</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<caption>Viewing and editing constraints on a model</caption>
|
||||||
|
<image>https://solvespace.com/pics/front-page-pic.png</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<caption>3D view of a stand made from notched angle iron, from the "ex-stand" project</caption>
|
||||||
|
<image>https://solvespace.com/pics/ex-stand-detail.jpg</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<caption>Dimensioning a 2D sketch for a case for a printed circuit board, from the "ex-case" project</caption>
|
||||||
|
<image>https://solvespace.com/pics/ex-case-outline.png</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<caption>Showing tracing of Chebyshev's linkage, from the "ex-chebyshev" project</caption>
|
||||||
|
<image>https://solvespace.com/pics/ex-chebyshev.png</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
|
||||||
<provides>
|
<provides>
|
||||||
<mediatype>application/x-solvespace</mediatype>
|
<mediatype>application/x-solvespace</mediatype>
|
||||||
</provides>
|
</provides>
|
||||||
|
@ -38,6 +66,19 @@
|
||||||
<content_rating type="oars-1.0" />
|
<content_rating type="oars-1.0" />
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="3.1" date="2022-06-01" type="stable">
|
||||||
|
<description>
|
||||||
|
<p>Major new stable release. Includes new arc and line length ratio and difference
|
||||||
|
constraints, comments associated with point entities. Adds "exploded view" to sketches,
|
||||||
|
and support for displaying measurements in "feet-inches". Adds a pitch parameter to
|
||||||
|
helix extrusions. Allows use of Point and Normal to define a new workplane to sketch in.
|
||||||
|
Adds live updating of Property Browser while dragging the sketch, and active links for
|
||||||
|
all points, normals, and vectors in the Property Browser. Adds the ability to link STL
|
||||||
|
files into a model. Includes a variety of UI improvements. Speeds up complex sketches
|
||||||
|
by up to 8x and doubles the maximum unknowns.</p>
|
||||||
|
</description>
|
||||||
|
<url>https://github.com/solvespace/solvespace/releases/tag/v3.0</url>
|
||||||
|
</release>
|
||||||
<release version="3.0" date="2021-04-18" type="stable">
|
<release version="3.0" date="2021-04-18" type="stable">
|
||||||
<description>
|
<description>
|
||||||
<p>Major new stable release. Includes new intersection boolean operation,
|
<p>Major new stable release. Includes new intersection boolean operation,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Version=1.0
|
Version=1.0
|
||||||
Name=SolveSpace
|
Name=SolveSpace
|
||||||
Comment=A parametric 2d/3d CAD
|
Comment=A parametric 2d/3d CAD
|
||||||
Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace
|
Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace %f
|
||||||
MimeType=application/x-solvespace
|
MimeType=application/x-solvespace
|
||||||
Icon=com.solvespace.SolveSpace
|
Icon=com.solvespace.SolveSpace
|
||||||
Type=Application
|
Type=Application
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Version=1.0
|
Version=1.0
|
||||||
Name=SolveSpace
|
Name=SolveSpace
|
||||||
Comment=A parametric 2d/3d CAD
|
Comment=A parametric 2d/3d CAD
|
||||||
Exec=solvespace
|
Exec=solvespace %f
|
||||||
MimeType=application/x-solvespace
|
MimeType=application/x-solvespace
|
||||||
Icon=${SNAP}/meta/icons/hicolor/scalable/apps/snap.solvespace.svg
|
Icon=${SNAP}/meta/icons/hicolor/scalable/apps/snap.solvespace.svg
|
||||||
Type=Application
|
Type=Application
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Version=1.0
|
Version=1.0
|
||||||
Name=SolveSpace
|
Name=SolveSpace
|
||||||
Comment=A parametric 2d/3d CAD
|
Comment=A parametric 2d/3d CAD
|
||||||
Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace
|
Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace %f
|
||||||
MimeType=application/x-solvespace
|
MimeType=application/x-solvespace
|
||||||
Icon=solvespace
|
Icon=solvespace
|
||||||
Type=Application
|
Type=Application
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# This file lists the ISO locale codes (ISO 639-1/ISO 3166-1), Windows LCIDs,
|
# This file lists the ISO locale codes (ISO 639-1/ISO 3166-1), Windows LCIDs,
|
||||||
# and human-readable names for every culture supported by SolveSpace.
|
# and human-readable names for every culture supported by SolveSpace.
|
||||||
|
cs-CZ,1029,Česky
|
||||||
de-DE,0407,Deutsch
|
de-DE,0407,Deutsch
|
||||||
en-US,0409,English (US)
|
en-US,0409,English (US)
|
||||||
fr-FR,040C,Français
|
fr-FR,040C,Français
|
||||||
|
@ -8,3 +9,4 @@ ru-RU,0419,Русский
|
||||||
tr-TR,041F,Türkçe
|
tr-TR,041F,Türkçe
|
||||||
uk-UA,0422,Українська
|
uk-UA,0422,Українська
|
||||||
zh-CN,0804,简体中文
|
zh-CN,0804,简体中文
|
||||||
|
ja-JP,0411,日本語
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -7,15 +7,15 @@ msgstr ""
|
||||||
"Project-Id-Version: SolveSpace 3.0\n"
|
"Project-Id-Version: SolveSpace 3.0\n"
|
||||||
"Report-Msgid-Bugs-To: whitequark@whitequark.org\n"
|
"Report-Msgid-Bugs-To: whitequark@whitequark.org\n"
|
||||||
"POT-Creation-Date: 2022-02-01 16:24+0200\n"
|
"POT-Creation-Date: 2022-02-01 16:24+0200\n"
|
||||||
"PO-Revision-Date: 2018-07-19 06:55+0000\n"
|
"PO-Revision-Date: 2022-04-30 16:44+0200\n"
|
||||||
"Last-Translator: Reini Urban <rurban@cpan.org>\n"
|
"Last-Translator: Reini Urban <rurban@cpan.org>\n"
|
||||||
"Language-Team: none\n"
|
"Language-Team: none\n"
|
||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Generator: Zanata 4.5.0\n"
|
"X-Generator: Poedit 2.4.2\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
#: clipboard.cpp:309
|
#: clipboard.cpp:309
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -26,7 +26,7 @@ msgstr ""
|
||||||
"Ausschneiden, Einfügen und Kopieren sind nur in einer Arbeitsebene "
|
"Ausschneiden, Einfügen und Kopieren sind nur in einer Arbeitsebene "
|
||||||
"zulässig.\n"
|
"zulässig.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Aktivieren Sie eine mit Skizze -> In Arbeitsebene"
|
"Aktivieren Sie eine mit \"Skizze -> In Arbeitsebene\"."
|
||||||
|
|
||||||
#: clipboard.cpp:326
|
#: clipboard.cpp:326
|
||||||
msgid "Clipboard is empty; nothing to paste."
|
msgid "Clipboard is empty; nothing to paste."
|
||||||
|
@ -172,12 +172,12 @@ msgstr "Längenverhältnis"
|
||||||
#: constraint.cpp:25
|
#: constraint.cpp:25
|
||||||
msgctxt "constr-name"
|
msgctxt "constr-name"
|
||||||
msgid "arc-arc-length-ratio"
|
msgid "arc-arc-length-ratio"
|
||||||
msgstr ""
|
msgstr "Bogen-Bogen-Längenverhältnis"
|
||||||
|
|
||||||
#: constraint.cpp:26
|
#: constraint.cpp:26
|
||||||
msgctxt "constr-name"
|
msgctxt "constr-name"
|
||||||
msgid "arc-line-length-ratio"
|
msgid "arc-line-length-ratio"
|
||||||
msgstr ""
|
msgstr "Bogen-Linien-Längenverhältnis"
|
||||||
|
|
||||||
#: constraint.cpp:27
|
#: constraint.cpp:27
|
||||||
msgctxt "constr-name"
|
msgctxt "constr-name"
|
||||||
|
@ -187,12 +187,12 @@ msgstr "Längendifferenz"
|
||||||
#: constraint.cpp:28
|
#: constraint.cpp:28
|
||||||
msgctxt "constr-name"
|
msgctxt "constr-name"
|
||||||
msgid "arc-arc-len-difference"
|
msgid "arc-arc-len-difference"
|
||||||
msgstr ""
|
msgstr "Bogen-Bogen-Längendifferenz"
|
||||||
|
|
||||||
#: constraint.cpp:29
|
#: constraint.cpp:29
|
||||||
msgctxt "constr-name"
|
msgctxt "constr-name"
|
||||||
msgid "arc-line-len-difference"
|
msgid "arc-line-len-difference"
|
||||||
msgstr ""
|
msgstr "Bogen-Linien-Längendifferenz"
|
||||||
|
|
||||||
#: constraint.cpp:30
|
#: constraint.cpp:30
|
||||||
msgctxt "constr-name"
|
msgctxt "constr-name"
|
||||||
|
@ -306,7 +306,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Die Bogentangente und das Liniensegment müssen einen gemeinsamen Endpunkt "
|
"Die Bogentangente und das Liniensegment müssen einen gemeinsamen Endpunkt "
|
||||||
"haben. Schränken Sie mit \"Einschränkung / Auf Punkt\" ein, bevor Sie die "
|
"haben. Schränken Sie mit \"Einschränkung / Auf Punkt\" ein, bevor Sie die "
|
||||||
"Tangente einschränken. -> Sc"
|
"Tangente einschränken."
|
||||||
|
|
||||||
#: constraint.cpp:163
|
#: constraint.cpp:163
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -315,7 +315,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Die Kurventangente und das Liniensegment müssen einen gemeinsamen Endpunkt "
|
"Die Kurventangente und das Liniensegment müssen einen gemeinsamen Endpunkt "
|
||||||
"haben. Schränken Sie mit \"Einschränkung / Auf Punkt\" ein, bevor Sie die "
|
"haben. Schränken Sie mit \"Einschränkung / Auf Punkt\" ein, bevor Sie die "
|
||||||
"Tangente einschränken. -> Sc"
|
"Tangente einschränken."
|
||||||
|
|
||||||
#: constraint.cpp:189
|
#: constraint.cpp:189
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -323,7 +323,7 @@ msgid ""
|
||||||
"before constraining tangent."
|
"before constraining tangent."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Die Kurven müssen einen gemeinsamen Endpunkt haben. Schränken Sie mit "
|
"Die Kurven müssen einen gemeinsamen Endpunkt haben. Schränken Sie mit "
|
||||||
"\"Einschränkung / Auf Punkt\" ein, bevor Sie die Tangente einschränken. -> Sc"
|
"\"Einschränkung / Auf Punkt\" ein, bevor Sie die Tangente einschränken."
|
||||||
|
|
||||||
#: constraint.cpp:238
|
#: constraint.cpp:238
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -408,6 +408,12 @@ msgid ""
|
||||||
" * two arcs\n"
|
" * two arcs\n"
|
||||||
" * one arc and one line segment\n"
|
" * one arc and one line segment\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Ungültige Auswahl für Einschränkung \"Längenverhältnis\". Diese "
|
||||||
|
"Einschränkung ist anwendbar auf:\n"
|
||||||
|
"\n"
|
||||||
|
" * zwei Liniensegmente\n"
|
||||||
|
" * zwei Bögen\n"
|
||||||
|
" * einen Bogen und ein Liniensegment\n"
|
||||||
|
|
||||||
#: constraint.cpp:441
|
#: constraint.cpp:441
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -418,6 +424,12 @@ msgid ""
|
||||||
" * two arcs\n"
|
" * two arcs\n"
|
||||||
" * one arc and one line segment\n"
|
" * one arc and one line segment\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Ungültige Auswahl für Einschränkung \"Längendifferenz\". Diese Einschränkung "
|
||||||
|
"ist anwendbar auf:\n"
|
||||||
|
"\n"
|
||||||
|
" * zwei Liniensegmente\n"
|
||||||
|
" * zwei Bögen\n"
|
||||||
|
" * einen Bogen und ein Liniensegment\n"
|
||||||
|
|
||||||
#: constraint.cpp:472
|
#: constraint.cpp:472
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -584,7 +596,7 @@ msgid ""
|
||||||
"2d View to export bare lines and curves."
|
"2d View to export bare lines and curves."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Kein Festkörper vorhanden; zeichnen Sie eines mit Extrusionen und Drehungen, "
|
"Kein Festkörper vorhanden; zeichnen Sie eines mit Extrusionen und Drehungen, "
|
||||||
"oder exportieren Sie bloße Linien und Kurven mit \"2D-Ansicht exportieren\""
|
"oder exportieren Sie bloße Linien und Kurven mit \"2D-Ansicht exportieren\"."
|
||||||
|
|
||||||
#: export.cpp:61
|
#: export.cpp:61
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -699,7 +711,7 @@ msgstr "&Neu"
|
||||||
|
|
||||||
#: graphicswin.cpp:43
|
#: graphicswin.cpp:43
|
||||||
msgid "&Open..."
|
msgid "&Open..."
|
||||||
msgstr "&Öffnen"
|
msgstr "&Öffnen..."
|
||||||
|
|
||||||
#: graphicswin.cpp:44
|
#: graphicswin.cpp:44
|
||||||
msgid "Open &Recent"
|
msgid "Open &Recent"
|
||||||
|
@ -727,7 +739,7 @@ msgstr "Exportiere 2D-Auswahl…"
|
||||||
|
|
||||||
#: graphicswin.cpp:51
|
#: graphicswin.cpp:51
|
||||||
msgid "Export 3d &Wireframe..."
|
msgid "Export 3d &Wireframe..."
|
||||||
msgstr "Exportiere 3D-Drahtgittermodell"
|
msgstr "Exportiere 3D-Drahtgittermodell..."
|
||||||
|
|
||||||
#: graphicswin.cpp:52
|
#: graphicswin.cpp:52
|
||||||
msgid "Export Triangle &Mesh..."
|
msgid "Export Triangle &Mesh..."
|
||||||
|
@ -859,7 +871,7 @@ msgstr "Perspektivische Projektion"
|
||||||
|
|
||||||
#: graphicswin.cpp:97
|
#: graphicswin.cpp:97
|
||||||
msgid "Show E&xploded View"
|
msgid "Show E&xploded View"
|
||||||
msgstr ""
|
msgstr "Zeige e&xplodierte Ansicht"
|
||||||
|
|
||||||
#: graphicswin.cpp:98
|
#: graphicswin.cpp:98
|
||||||
msgid "Dimension &Units"
|
msgid "Dimension &Units"
|
||||||
|
@ -879,7 +891,7 @@ msgstr "Maße in Zoll"
|
||||||
|
|
||||||
#: graphicswin.cpp:102
|
#: graphicswin.cpp:102
|
||||||
msgid "Dimensions in &Feet and Inches"
|
msgid "Dimensions in &Feet and Inches"
|
||||||
msgstr ""
|
msgstr "Maße in &Fuß und Inch"
|
||||||
|
|
||||||
#: graphicswin.cpp:104
|
#: graphicswin.cpp:104
|
||||||
msgid "Show &Toolbar"
|
msgid "Show &Toolbar"
|
||||||
|
@ -931,7 +943,7 @@ msgstr "D&rehen"
|
||||||
|
|
||||||
#: graphicswin.cpp:121
|
#: graphicswin.cpp:121
|
||||||
msgid "Link / Assemble..."
|
msgid "Link / Assemble..."
|
||||||
msgstr "Verknüpfen / Zusammensetzen"
|
msgstr "Verknüpfen / Zusammensetzen..."
|
||||||
|
|
||||||
#: graphicswin.cpp:122
|
#: graphicswin.cpp:122
|
||||||
msgid "Link Recent"
|
msgid "Link Recent"
|
||||||
|
@ -1047,11 +1059,11 @@ msgstr "Gleicher Abstand / Radius / Winkel"
|
||||||
|
|
||||||
#: graphicswin.cpp:158
|
#: graphicswin.cpp:158
|
||||||
msgid "Length / Arc Ra&tio"
|
msgid "Length / Arc Ra&tio"
|
||||||
msgstr ""
|
msgstr "Länge / Bogen Verhäl&tnis"
|
||||||
|
|
||||||
#: graphicswin.cpp:159
|
#: graphicswin.cpp:159
|
||||||
msgid "Length / Arc Diff&erence"
|
msgid "Length / Arc Diff&erence"
|
||||||
msgstr ""
|
msgstr "Länge / Bogen Diff&erenz"
|
||||||
|
|
||||||
#: graphicswin.cpp:160
|
#: graphicswin.cpp:160
|
||||||
msgid "At &Midpoint"
|
msgid "At &Midpoint"
|
||||||
|
@ -1119,7 +1131,7 @@ msgstr "Punkt nachzeichnen"
|
||||||
|
|
||||||
#: graphicswin.cpp:180
|
#: graphicswin.cpp:180
|
||||||
msgid "&Stop Tracing..."
|
msgid "&Stop Tracing..."
|
||||||
msgstr "Nachzeichnen beenden"
|
msgstr "Nachzeichnen beenden..."
|
||||||
|
|
||||||
#: graphicswin.cpp:181
|
#: graphicswin.cpp:181
|
||||||
msgid "Step &Dimension..."
|
msgid "Step &Dimension..."
|
||||||
|
@ -1139,7 +1151,7 @@ msgstr "&Website / Anleitung"
|
||||||
|
|
||||||
#: graphicswin.cpp:186
|
#: graphicswin.cpp:186
|
||||||
msgid "&Go to GitHub commit"
|
msgid "&Go to GitHub commit"
|
||||||
msgstr ""
|
msgstr "&Gehe zu GitHub commit"
|
||||||
|
|
||||||
#: graphicswin.cpp:188
|
#: graphicswin.cpp:188
|
||||||
msgid "&About"
|
msgid "&About"
|
||||||
|
@ -1300,6 +1312,12 @@ msgid ""
|
||||||
" * a point and a normal (through the point, orthogonal to the normal)\n"
|
" * a point and a normal (through the point, orthogonal to the normal)\n"
|
||||||
" * a workplane (copy of the workplane)\n"
|
" * a workplane (copy of the workplane)\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Ungültige Auswahl für neue Skizze in der Arbeitsebene. Diese Gruppe kann "
|
||||||
|
"erstellt werden mit:\n"
|
||||||
|
"\n"
|
||||||
|
" * einem Punkt (durch den Punkt, orthogonal zur Koordinatenachse)\n"
|
||||||
|
" * einem Punkt und zwei Linienabschnitten (durch den Punkt, parallel zu "
|
||||||
|
"den Linien)\n"
|
||||||
|
|
||||||
#: group.cpp:166
|
#: group.cpp:166
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -1307,7 +1325,7 @@ msgid ""
|
||||||
"will be extruded normal to the workplane."
|
"will be extruded normal to the workplane."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Aktivieren Sie vor der Extrusion eine Arbeitsebene (mit Skizze -> In "
|
"Aktivieren Sie vor der Extrusion eine Arbeitsebene (mit Skizze -> In "
|
||||||
"Arbeitsebene). Die Skizze wird senkrecht zur Arbeitsebene extrudiert"
|
"Arbeitsebene). Die Skizze wird senkrecht zur Arbeitsebene extrudiert."
|
||||||
|
|
||||||
#: group.cpp:175
|
#: group.cpp:175
|
||||||
msgctxt "group-name"
|
msgctxt "group-name"
|
||||||
|
@ -1434,7 +1452,7 @@ msgstr "Kante mit Länge Null!"
|
||||||
|
|
||||||
#: importmesh.cpp:136
|
#: importmesh.cpp:136
|
||||||
msgid "Text-formated STL files are not currently supported"
|
msgid "Text-formated STL files are not currently supported"
|
||||||
msgstr ""
|
msgstr "Text-formatierte STL Dateien werden aktuell nicht unterstützt"
|
||||||
|
|
||||||
#: modify.cpp:252
|
#: modify.cpp:252
|
||||||
msgid "Must be sketching in workplane to create tangent arc."
|
msgid "Must be sketching in workplane to create tangent arc."
|
||||||
|
@ -1447,7 +1465,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Um eine Bogentangente zu erstellen, wählen Sie einen Punkt, in dem sich zwei "
|
"Um eine Bogentangente zu erstellen, wählen Sie einen Punkt, in dem sich zwei "
|
||||||
"nicht-Konstruktionslinien oder -kreise in dieser Gruppe und Arbeitsebene "
|
"nicht-Konstruktionslinien oder -kreise in dieser Gruppe und Arbeitsebene "
|
||||||
"treffen. "
|
"treffen."
|
||||||
|
|
||||||
#: modify.cpp:386
|
#: modify.cpp:386
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -1646,7 +1664,7 @@ msgstr "SolveSpace-Modelle"
|
||||||
#: platform/gui.cpp:89
|
#: platform/gui.cpp:89
|
||||||
msgctxt "file-type"
|
msgctxt "file-type"
|
||||||
msgid "ALL"
|
msgid "ALL"
|
||||||
msgstr ""
|
msgstr "ALLE"
|
||||||
|
|
||||||
#: platform/gui.cpp:91
|
#: platform/gui.cpp:91
|
||||||
msgctxt "file-type"
|
msgctxt "file-type"
|
||||||
|
@ -1656,7 +1674,7 @@ msgstr "IDF Leiterplatte"
|
||||||
#: platform/gui.cpp:92
|
#: platform/gui.cpp:92
|
||||||
msgctxt "file-type"
|
msgctxt "file-type"
|
||||||
msgid "STL triangle mesh"
|
msgid "STL triangle mesh"
|
||||||
msgstr ""
|
msgstr "STL-Dreiecks-Netz"
|
||||||
|
|
||||||
#: platform/gui.cpp:96
|
#: platform/gui.cpp:96
|
||||||
msgctxt "file-type"
|
msgctxt "file-type"
|
||||||
|
@ -2072,7 +2090,7 @@ msgstr ""
|
||||||
|
|
||||||
#: style.cpp:735
|
#: style.cpp:735
|
||||||
msgid "Style name cannot be empty"
|
msgid "Style name cannot be empty"
|
||||||
msgstr "Name des Linientyps kann nicht leer sein."
|
msgstr "Name des Linientyps kann nicht leer sein"
|
||||||
|
|
||||||
#: textscreens.cpp:791
|
#: textscreens.cpp:791
|
||||||
msgid "Can't repeat fewer than 1 time."
|
msgid "Can't repeat fewer than 1 time."
|
||||||
|
@ -2084,7 +2102,7 @@ msgstr "Nicht mehr als 999 Wiederholungen möglich."
|
||||||
|
|
||||||
#: textscreens.cpp:820
|
#: textscreens.cpp:820
|
||||||
msgid "Group name cannot be empty"
|
msgid "Group name cannot be empty"
|
||||||
msgstr "Der Name der Gruppe darf nicht leer sein."
|
msgstr "Der Name der Gruppe darf nicht leer sein"
|
||||||
|
|
||||||
#: textscreens.cpp:872
|
#: textscreens.cpp:872
|
||||||
msgid "Opacity must be between zero and one."
|
msgid "Opacity must be between zero and one."
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -8,8 +8,8 @@ msgstr ""
|
||||||
"Project-Id-Version: SolveSpace 3.0\n"
|
"Project-Id-Version: SolveSpace 3.0\n"
|
||||||
"Report-Msgid-Bugs-To: whitequark@whitequark.org\n"
|
"Report-Msgid-Bugs-To: whitequark@whitequark.org\n"
|
||||||
"POT-Creation-Date: 2022-02-01 16:24+0200\n"
|
"POT-Creation-Date: 2022-02-01 16:24+0200\n"
|
||||||
"PO-Revision-Date: 2021-10-04 15:33+0300\n"
|
"PO-Revision-Date: 2022-11-05 19:37+0200\n"
|
||||||
"Last-Translator: Olesya Gerasimenko <translation-team@basealt.ru>\n"
|
"Last-Translator: ruevs Olesya Gerasimenko <translation-team@basealt.ru>\n"
|
||||||
"Language-Team: Basealt Translation Team\n"
|
"Language-Team: Basealt Translation Team\n"
|
||||||
"Language: ru_RU\n"
|
"Language: ru_RU\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
@ -936,7 +936,7 @@ msgstr "Тело В&ращения"
|
||||||
|
|
||||||
#: graphicswin.cpp:119
|
#: graphicswin.cpp:119
|
||||||
msgid "Re&volve"
|
msgid "Re&volve"
|
||||||
msgstr "Тело В&ращения"
|
msgstr "Тело В&ращения на угол"
|
||||||
|
|
||||||
#: graphicswin.cpp:121
|
#: graphicswin.cpp:121
|
||||||
msgid "Link / Assemble..."
|
msgid "Link / Assemble..."
|
||||||
|
@ -1350,9 +1350,9 @@ msgstr ""
|
||||||
"Группа может быть создана, используя в качестве выделения следующие "
|
"Группа может быть создана, используя в качестве выделения следующие "
|
||||||
"примитивы:\n"
|
"примитивы:\n"
|
||||||
"\n"
|
"\n"
|
||||||
" * точку и отрезок / координатный базис (нормаль) (тело вращения вокруг "
|
" * точку и отрезок / координатный базис (нормаль) (вращение вокруг "
|
||||||
"оси, проходящей через точку и параллельной отрезку / нормали)\n"
|
"оси, проходящей через точку и параллельной отрезку / нормали)\n"
|
||||||
" * отрезок (тело вращения вокруг оси, проходящей через отрезок)\n"
|
" * отрезок (вращение вокруг оси, проходящей через отрезок)\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|
||||||
#: group.cpp:201
|
#: group.cpp:201
|
||||||
|
@ -1363,7 +1363,7 @@ msgstr "тело-вращения"
|
||||||
#: group.cpp:206
|
#: group.cpp:206
|
||||||
msgid "Revolve operation can only be applied to planar sketches."
|
msgid "Revolve operation can only be applied to planar sketches."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Операция создания тела вращения может быть применена только к плоским "
|
"Операция создания тела вращения на угол может быть применена только к плоским "
|
||||||
"эскизам."
|
"эскизам."
|
||||||
|
|
||||||
#: group.cpp:217
|
#: group.cpp:217
|
||||||
|
@ -1374,19 +1374,19 @@ msgid ""
|
||||||
"to line / normal, through point)\n"
|
"to line / normal, through point)\n"
|
||||||
" * a line segment (revolved about line segment)\n"
|
" * a line segment (revolved about line segment)\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Неправильное выделение для создания группы тела вращения. \n"
|
"Неправильное выделение для создания группы тела вращения на угол. \n"
|
||||||
"Группа может быть создана, используя в качестве выделения следующие "
|
"Группа может быть создана, используя в качестве выделения следующие "
|
||||||
"примитивы:\n"
|
"примитивы:\n"
|
||||||
"\n"
|
"\n"
|
||||||
" * точку и отрезок / координатный базис (нормаль) (тело вращения вокруг "
|
" * точку и отрезок / координатный базис (нормаль) (вращение вокруг "
|
||||||
"оси, проходящей через точку и параллельной отрезку / нормали)\n"
|
"оси, проходящей через точку и параллельной отрезку / нормали)\n"
|
||||||
" * отрезок (тело вращения вокруг оси, проходящей через отрезок)\n"
|
" * отрезок (вращение вокруг оси, проходящей через отрезок)\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|
||||||
#: group.cpp:229
|
#: group.cpp:229
|
||||||
msgctxt "group-name"
|
msgctxt "group-name"
|
||||||
msgid "revolve"
|
msgid "revolve"
|
||||||
msgstr "тело-вращения"
|
msgstr "тело-вращения-на-угол"
|
||||||
|
|
||||||
#: group.cpp:234
|
#: group.cpp:234
|
||||||
msgid "Helix operation can only be applied to planar sketches."
|
msgid "Helix operation can only be applied to planar sketches."
|
||||||
|
@ -2219,7 +2219,7 @@ msgstr "Создать группу выдавливания текущего э
|
||||||
|
|
||||||
#: toolbar.cpp:70
|
#: toolbar.cpp:70
|
||||||
msgid "New group rotating active sketch"
|
msgid "New group rotating active sketch"
|
||||||
msgstr "Создать группу вращения текущего эскиза"
|
msgstr "Создать группу тела вращения текущего эскиза"
|
||||||
|
|
||||||
#: toolbar.cpp:72
|
#: toolbar.cpp:72
|
||||||
msgid "New group helix from active sketch"
|
msgid "New group helix from active sketch"
|
||||||
|
@ -2227,7 +2227,7 @@ msgstr "Создать группу тела выдавливания по ви
|
||||||
|
|
||||||
#: toolbar.cpp:74
|
#: toolbar.cpp:74
|
||||||
msgid "New group revolve active sketch"
|
msgid "New group revolve active sketch"
|
||||||
msgstr "Создать группу тела вращения из текущего эскиза"
|
msgstr "Создать группу тела вращения на угол из текущего эскиза"
|
||||||
|
|
||||||
#: toolbar.cpp:76
|
#: toolbar.cpp:76
|
||||||
msgid "New group step and repeat rotating"
|
msgid "New group step and repeat rotating"
|
||||||
|
|
|
@ -82,7 +82,9 @@ target_compile_definitions(slvs
|
||||||
PRIVATE -DLIBRARY)
|
PRIVATE -DLIBRARY)
|
||||||
|
|
||||||
target_include_directories(slvs
|
target_include_directories(slvs
|
||||||
PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
PUBLIC
|
||||||
|
${CMAKE_SOURCE_DIR}/include
|
||||||
|
${EIGEN3_INCLUDE_DIRS})
|
||||||
|
|
||||||
target_link_libraries(slvs PRIVATE slvs_deps)
|
target_link_libraries(slvs PRIVATE slvs_deps)
|
||||||
|
|
||||||
|
@ -101,7 +103,8 @@ endif()
|
||||||
set(every_platform_SOURCES
|
set(every_platform_SOURCES
|
||||||
platform/guiwin.cpp
|
platform/guiwin.cpp
|
||||||
platform/guigtk.cpp
|
platform/guigtk.cpp
|
||||||
platform/guimac.mm)
|
platform/guimac.mm
|
||||||
|
platform/guihtml.cpp)
|
||||||
|
|
||||||
# solvespace library
|
# solvespace library
|
||||||
|
|
||||||
|
@ -166,6 +169,7 @@ add_library(solvespace-core STATIC
|
||||||
srf/merge.cpp
|
srf/merge.cpp
|
||||||
srf/ratpoly.cpp
|
srf/ratpoly.cpp
|
||||||
srf/raycast.cpp
|
srf/raycast.cpp
|
||||||
|
srf/shell.cpp
|
||||||
srf/surface.cpp
|
srf/surface.cpp
|
||||||
srf/surfinter.cpp
|
srf/surfinter.cpp
|
||||||
srf/triangulate.cpp)
|
srf/triangulate.cpp)
|
||||||
|
@ -300,6 +304,56 @@ if(ENABLE_GUI)
|
||||||
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES"
|
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES"
|
||||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.solvespace"
|
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.solvespace"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||||
|
elseif(EMSCRIPTEN)
|
||||||
|
set(SHELL ${CMAKE_CURRENT_SOURCE_DIR}/platform/html/emshell.html)
|
||||||
|
set(LINK_FLAGS
|
||||||
|
--bind --shell-file ${SHELL}
|
||||||
|
--no-heap-copy -s ALLOW_MEMORY_GROWTH=1 -s WASM=1 -s ASYNCIFY=1
|
||||||
|
-s DYNCALLS=1 -s ASSERTIONS=1
|
||||||
|
-s TOTAL_STACK=33554432 -s TOTAL_MEMORY=134217728)
|
||||||
|
|
||||||
|
get_target_property(resource_names resources NAMES)
|
||||||
|
foreach(resource ${resource_names})
|
||||||
|
list(APPEND LINK_FLAGS --preload-file ${resource})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||||
|
list(APPEND LINK_FLAGS
|
||||||
|
--emrun --emit-symbol-map
|
||||||
|
-s DEMANGLE_SUPPORT=1
|
||||||
|
-s SAFE_HEAP=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_sources(solvespace PRIVATE
|
||||||
|
platform/guihtml.cpp)
|
||||||
|
|
||||||
|
string(REPLACE ";" " " LINK_FLAGS "${LINK_FLAGS}")
|
||||||
|
set_target_properties(solvespace PROPERTIES
|
||||||
|
LINK_FLAGS "${LINK_FLAGS}")
|
||||||
|
set_source_files_properties(platform/guihtml.cpp PROPERTIES
|
||||||
|
OBJECT_DEPENDS ${SHELL})
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
TARGET solvespace POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platform/html/solvespaceui.css
|
||||||
|
${EXECUTABLE_OUTPUT_PATH}/solvespaceui.css
|
||||||
|
COMMENT "Copying UI stylesheet"
|
||||||
|
VERBATIM)
|
||||||
|
add_custom_command(
|
||||||
|
TARGET solvespace POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platform/html/solvespaceui.js
|
||||||
|
${EXECUTABLE_OUTPUT_PATH}/solvespaceui.js
|
||||||
|
COMMENT "Copying UI script solvespaceui.js"
|
||||||
|
VERBATIM)
|
||||||
|
add_custom_command(
|
||||||
|
TARGET solvespace POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platform/html/filemanagerui.js
|
||||||
|
${EXECUTABLE_OUTPUT_PATH}/filemanagerui.js
|
||||||
|
COMMENT "Copying UI script filemanagerui.sj"
|
||||||
|
VERBATIM)
|
||||||
else()
|
else()
|
||||||
target_sources(solvespace PRIVATE
|
target_sources(solvespace PRIVATE
|
||||||
platform/guigtk.cpp)
|
platform/guigtk.cpp)
|
||||||
|
@ -335,7 +389,8 @@ target_compile_definitions(solvespace-headless
|
||||||
PRIVATE HEADLESS)
|
PRIVATE HEADLESS)
|
||||||
|
|
||||||
target_include_directories(solvespace-headless
|
target_include_directories(solvespace-headless
|
||||||
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
PUBLIC ${EIGEN3_INCLUDE_DIRS})
|
||||||
|
|
||||||
target_link_libraries(solvespace-headless
|
target_link_libraries(solvespace-headless
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
@ -363,7 +418,7 @@ endif()
|
||||||
|
|
||||||
# solvespace unix package
|
# solvespace unix package
|
||||||
|
|
||||||
if(NOT (WIN32 OR APPLE))
|
if(NOT (WIN32 OR APPLE OR EMSCRIPTEN))
|
||||||
if(ENABLE_GUI)
|
if(ENABLE_GUI)
|
||||||
install(TARGETS solvespace
|
install(TARGETS solvespace
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
|
@ -721,7 +721,11 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case Command::PARALLEL:
|
case Command::PARALLEL:
|
||||||
if(gs.vectors == 2 && gs.n == 2) {
|
if(gs.faces == 2 && gs.n == 2) {
|
||||||
|
c.type = Type::PARALLEL;
|
||||||
|
c.entityA = gs.face[0];
|
||||||
|
c.entityB = gs.face[1];
|
||||||
|
} else if(gs.vectors == 2 && gs.n == 2) {
|
||||||
c.type = Type::PARALLEL;
|
c.type = Type::PARALLEL;
|
||||||
c.entityA = gs.vector[0];
|
c.entityA = gs.vector[0];
|
||||||
c.entityB = gs.vector[1];
|
c.entityB = gs.vector[1];
|
||||||
|
@ -765,6 +769,7 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
} else {
|
} else {
|
||||||
Error(_("Bad selection for parallel / tangent constraint. This "
|
Error(_("Bad selection for parallel / tangent constraint. This "
|
||||||
"constraint can apply to:\n\n"
|
"constraint can apply to:\n\n"
|
||||||
|
" * two faces\n"
|
||||||
" * two line segments (parallel)\n"
|
" * two line segments (parallel)\n"
|
||||||
" * a line segment and a normal (parallel)\n"
|
" * a line segment and a normal (parallel)\n"
|
||||||
" * two normals (parallel)\n"
|
" * two normals (parallel)\n"
|
||||||
|
@ -776,13 +781,18 @@ void Constraint::MenuConstrain(Command id) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::PERPENDICULAR:
|
case Command::PERPENDICULAR:
|
||||||
if(gs.vectors == 2 && gs.n == 2) {
|
if(gs.faces == 2 && gs.n == 2) {
|
||||||
|
c.type = Type::PERPENDICULAR;
|
||||||
|
c.entityA = gs.face[0];
|
||||||
|
c.entityB = gs.face[1];
|
||||||
|
} else if(gs.vectors == 2 && gs.n == 2) {
|
||||||
c.type = Type::PERPENDICULAR;
|
c.type = Type::PERPENDICULAR;
|
||||||
c.entityA = gs.vector[0];
|
c.entityA = gs.vector[0];
|
||||||
c.entityB = gs.vector[1];
|
c.entityB = gs.vector[1];
|
||||||
} else {
|
} else {
|
||||||
Error(_("Bad selection for perpendicular constraint. This "
|
Error(_("Bad selection for perpendicular constraint. This "
|
||||||
"constraint can apply to:\n\n"
|
"constraint can apply to:\n\n"
|
||||||
|
" * two faces\n"
|
||||||
" * two line segments\n"
|
" * two line segments\n"
|
||||||
" * a line segment and a normal\n"
|
" * a line segment and a normal\n"
|
||||||
" * two normals\n"));
|
" * two normals\n"));
|
||||||
|
|
|
@ -89,6 +89,7 @@ void TextWindow::DescribeSelection() {
|
||||||
case Entity::Type::POINT_N_ROT_TRANS:
|
case Entity::Type::POINT_N_ROT_TRANS:
|
||||||
case Entity::Type::POINT_N_COPY:
|
case Entity::Type::POINT_N_COPY:
|
||||||
case Entity::Type::POINT_N_ROT_AA:
|
case Entity::Type::POINT_N_ROT_AA:
|
||||||
|
case Entity::Type::POINT_N_ROT_AXIS_TRANS:
|
||||||
p = e->PointGetNum();
|
p = e->PointGetNum();
|
||||||
Printf(false, "%FtPOINT%E at " PT_AS_STR, COSTR(e, p));
|
Printf(false, "%FtPOINT%E at " PT_AS_STR, COSTR(e, p));
|
||||||
break;
|
break;
|
||||||
|
@ -171,6 +172,7 @@ void TextWindow::DescribeSelection() {
|
||||||
double r = e->CircleGetRadiusNum();
|
double r = e->CircleGetRadiusNum();
|
||||||
Printf(true, " diameter = %Fi%s", SS.MmToString(r*2).c_str());
|
Printf(true, " diameter = %Fi%s", SS.MmToString(r*2).c_str());
|
||||||
Printf(false, " radius = %Fi%s", SS.MmToString(r).c_str());
|
Printf(false, " radius = %Fi%s", SS.MmToString(r).c_str());
|
||||||
|
Printf(false, " circumference = %Fi%s", SS.MmToString(2*M_PI*r).c_str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Entity::Type::FACE_NORMAL_PT:
|
case Entity::Type::FACE_NORMAL_PT:
|
||||||
|
@ -178,6 +180,8 @@ void TextWindow::DescribeSelection() {
|
||||||
case Entity::Type::FACE_N_ROT_TRANS:
|
case Entity::Type::FACE_N_ROT_TRANS:
|
||||||
case Entity::Type::FACE_N_ROT_AA:
|
case Entity::Type::FACE_N_ROT_AA:
|
||||||
case Entity::Type::FACE_N_TRANS:
|
case Entity::Type::FACE_N_TRANS:
|
||||||
|
case Entity::Type::FACE_ROT_NORMAL_PT:
|
||||||
|
case Entity::Type::FACE_N_ROT_AXIS_TRANS:
|
||||||
Printf(false, "%FtPLANE FACE%E");
|
Printf(false, "%FtPLANE FACE%E");
|
||||||
p = e->FaceGetNormalNum();
|
p = e->FaceGetNormalNum();
|
||||||
Printf(true, " normal = " PT_AS_NUM, CO(p));
|
Printf(true, " normal = " PT_AS_NUM, CO(p));
|
||||||
|
@ -424,14 +428,19 @@ void TextWindow::DescribeSelection() {
|
||||||
double d = (p1.Minus(p0)).Dot(n0);
|
double d = (p1.Minus(p0)).Dot(n0);
|
||||||
Printf(true, " distance = %Fi%s", SS.MmToString(d).c_str());
|
Printf(true, " distance = %Fi%s", SS.MmToString(d).c_str());
|
||||||
}
|
}
|
||||||
} else if(gs.n == 0 && gs.stylables > 0) {
|
|
||||||
Printf(false, "%FtSELECTED:%E comment text");
|
|
||||||
} else if(gs.n == 0 && gs.constraints == 1) {
|
} else if(gs.n == 0 && gs.constraints == 1) {
|
||||||
Constraint *c = SK.GetConstraint(gs.constraint[0]);
|
Constraint *c = SK.GetConstraint(gs.constraint[0]);
|
||||||
const std::string &desc = c->DescriptionString().c_str();
|
const std::string &desc = c->DescriptionString().c_str();
|
||||||
|
|
||||||
if(c->type == Constraint::Type::COMMENT) {
|
if(c->type == Constraint::Type::COMMENT) {
|
||||||
Printf(false, "%FtCOMMENT%E %s", desc.c_str());
|
Printf(false, "%FtCOMMENT%E %s", desc.c_str());
|
||||||
|
if(c->ptA != Entity::NO_ENTITY) {
|
||||||
|
Vector p = SK.GetEntity(c->ptA)->PointGetNum();
|
||||||
|
Printf(true, " attached to point at: " PT_AS_STR, COSTR(SK.GetEntity(c->ptA), p));
|
||||||
|
Vector dv = c->disp.offset;
|
||||||
|
Printf(false, " distance = %Fi%s", SS.MmToString(dv.Magnitude()).c_str());
|
||||||
|
Printf(false, " d(x, y, z) = " PT_AS_STR_NO_LINK, COSTR_NO_LINK(dv));
|
||||||
|
}
|
||||||
} else if(c->HasLabel()) {
|
} else if(c->HasLabel()) {
|
||||||
if(c->reference) {
|
if(c->reference) {
|
||||||
Printf(false, "%FtREFERENCE%E %s", desc.c_str());
|
Printf(false, "%FtREFERENCE%E %s", desc.c_str());
|
||||||
|
|
|
@ -26,6 +26,9 @@ bool EntityBase::HasVector() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprVector EntityBase::VectorGetExprsInWorkplane(hEntity wrkpl) const {
|
ExprVector EntityBase::VectorGetExprsInWorkplane(hEntity wrkpl) const {
|
||||||
|
if(IsFace()) {
|
||||||
|
return FaceGetNormalExprs();
|
||||||
|
}
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case Type::LINE_SEGMENT:
|
case Type::LINE_SEGMENT:
|
||||||
return (SK.GetEntity(point[0])->PointGetExprsInWorkplane(wrkpl)).Minus(
|
return (SK.GetEntity(point[0])->PointGetExprsInWorkplane(wrkpl)).Minus(
|
||||||
|
@ -62,6 +65,9 @@ ExprVector EntityBase::VectorGetExprs() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector EntityBase::VectorGetNum() const {
|
Vector EntityBase::VectorGetNum() const {
|
||||||
|
if(IsFace()) {
|
||||||
|
return FaceGetNormalNum();
|
||||||
|
}
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case Type::LINE_SEGMENT:
|
case Type::LINE_SEGMENT:
|
||||||
return (SK.GetEntity(point[0])->PointGetNum()).Minus(
|
return (SK.GetEntity(point[0])->PointGetNum()).Minus(
|
||||||
|
@ -79,6 +85,9 @@ Vector EntityBase::VectorGetNum() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector EntityBase::VectorGetRefPoint() const {
|
Vector EntityBase::VectorGetRefPoint() const {
|
||||||
|
if(IsFace()) {
|
||||||
|
return FaceGetPointNum();
|
||||||
|
}
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case Type::LINE_SEGMENT:
|
case Type::LINE_SEGMENT:
|
||||||
return ((SK.GetEntity(point[0])->PointGetNum()).Plus(
|
return ((SK.GetEntity(point[0])->PointGetNum()).Plus(
|
||||||
|
|
|
@ -918,7 +918,7 @@ try_again:
|
||||||
switch(LocateImportedFile(linkFileRelative, canCancel)) {
|
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::SolveSpaceLinkFileFilters);
|
||||||
dialog->ThawChoices(settings, "LinkSketch");
|
dialog->ThawChoices(settings, "LinkSketch");
|
||||||
dialog->SuggestFilename(linkFileRelative);
|
dialog->SuggestFilename(linkFileRelative);
|
||||||
if(dialog->RunModal()) {
|
if(dialog->RunModal()) {
|
||||||
|
|
|
@ -433,6 +433,11 @@ void GraphicsWindow::Init() {
|
||||||
// a canvas.
|
// a canvas.
|
||||||
window->SetMinContentSize(720, /*ToolbarDrawOrHitTest 636*/ 32 * 18 + 3 * 16 + 8 + 4);
|
window->SetMinContentSize(720, /*ToolbarDrawOrHitTest 636*/ 32 * 18 + 3 * 16 + 8 + 4);
|
||||||
window->onClose = std::bind(&SolveSpaceUI::MenuFile, Command::EXIT);
|
window->onClose = std::bind(&SolveSpaceUI::MenuFile, Command::EXIT);
|
||||||
|
window->onContextLost = [&] {
|
||||||
|
canvas = NULL;
|
||||||
|
persistentCanvas = NULL;
|
||||||
|
persistentDirty = true;
|
||||||
|
};
|
||||||
window->onRender = std::bind(&GraphicsWindow::Paint, this);
|
window->onRender = std::bind(&GraphicsWindow::Paint, this);
|
||||||
window->onKeyboardEvent = std::bind(&GraphicsWindow::KeyboardEvent, this, _1);
|
window->onKeyboardEvent = std::bind(&GraphicsWindow::KeyboardEvent, this, _1);
|
||||||
window->onMouseEvent = std::bind(&GraphicsWindow::MouseEvent, this, _1);
|
window->onMouseEvent = std::bind(&GraphicsWindow::MouseEvent, this, _1);
|
||||||
|
@ -712,16 +717,47 @@ double GraphicsWindow::ZoomToFit(const Camera &camera,
|
||||||
return scale;
|
return scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GraphicsWindow::ZoomToMouse(double zoomMultiplyer) {
|
||||||
|
double offsetRight = offset.Dot(projRight);
|
||||||
|
double offsetUp = offset.Dot(projUp);
|
||||||
|
|
||||||
|
double width, height;
|
||||||
|
window->GetContentSize(&width, &height);
|
||||||
|
|
||||||
|
double righti = currentMousePosition.x / scale - offsetRight;
|
||||||
|
double upi = currentMousePosition.y / scale - offsetUp;
|
||||||
|
|
||||||
|
// zoomMultiplyer of 1 gives a default zoom factor of 1.2x: zoomMultiplyer * 1.2
|
||||||
|
// zoom = adjusted zoom negative zoomMultiplyer will zoom out, positive will zoom in
|
||||||
|
//
|
||||||
|
|
||||||
|
scale *= exp(0.1823216 * zoomMultiplyer); // ln(1.2) = 0.1823216
|
||||||
|
|
||||||
|
double rightf = currentMousePosition.x / scale - offsetRight;
|
||||||
|
double upf = currentMousePosition.y / scale - offsetUp;
|
||||||
|
|
||||||
|
offset = offset.Plus(projRight.ScaledBy(rightf - righti));
|
||||||
|
offset = offset.Plus(projUp.ScaledBy(upf - upi));
|
||||||
|
|
||||||
|
if(SS.TW.shown.screen == TextWindow::Screen::EDIT_VIEW) {
|
||||||
|
if(havePainted) {
|
||||||
|
SS.ScheduleShowTW();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
havePainted = false;
|
||||||
|
Invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void GraphicsWindow::MenuView(Command id) {
|
void GraphicsWindow::MenuView(Command id) {
|
||||||
switch(id) {
|
switch(id) {
|
||||||
case Command::ZOOM_IN:
|
case Command::ZOOM_IN:
|
||||||
SS.GW.scale *= 1.2;
|
SS.GW.ZoomToMouse(1);
|
||||||
SS.ScheduleShowTW();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::ZOOM_OUT:
|
case Command::ZOOM_OUT:
|
||||||
SS.GW.scale /= 1.2;
|
SS.GW.ZoomToMouse(-1);
|
||||||
SS.ScheduleShowTW();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::ZOOM_TO_FIT:
|
case Command::ZOOM_TO_FIT:
|
||||||
|
@ -781,13 +817,18 @@ void GraphicsWindow::MenuView(Command id) {
|
||||||
Quaternion quatf = quat0;
|
Quaternion quatf = quat0;
|
||||||
double dmin = 1e10;
|
double dmin = 1e10;
|
||||||
|
|
||||||
// There are 24 possible views; 3*2*2*2
|
// There are 24 possible views (3*2*2*2), if all are
|
||||||
int i, j, negi, negj;
|
// allowed. If the user is in turn-table mode, the
|
||||||
for(i = 0; i < 3; i++) {
|
// isometric view must have the z-axis facing up, leaving
|
||||||
for(j = 0; j < 3; j++) {
|
// 8 possible views (2*1*2*2).
|
||||||
|
|
||||||
|
bool require_turntable = (id==Command::NEAREST_ISO && SS.turntableNav);
|
||||||
|
for(int i = 0; i < 3; i++) {
|
||||||
|
for(int j = 0; j < 3; j++) {
|
||||||
if(i == j) continue;
|
if(i == j) continue;
|
||||||
for(negi = 0; negi < 2; negi++) {
|
if(require_turntable && (j!=2)) continue;
|
||||||
for(negj = 0; negj < 2; negj++) {
|
for(int negi = 0; negi < 2; negi++) {
|
||||||
|
for(int negj = 0; negj < 2; negj++) {
|
||||||
Vector ou = ortho[i], ov = ortho[j];
|
Vector ou = ortho[i], ov = ortho[j];
|
||||||
if(negi) ou = ou.ScaledBy(-1);
|
if(negi) ou = ou.ScaledBy(-1);
|
||||||
if(negj) ov = ov.ScaledBy(-1);
|
if(negj) ov = ov.ScaledBy(-1);
|
||||||
|
|
|
@ -914,7 +914,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, event.shiftDown ? event.scrollDelta / 10 : event.scrollDelta);
|
this->MouseScroll(event.shiftDown ? event.scrollDelta / 10 : event.scrollDelta);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MouseEvent::Type::LEAVE:
|
case MouseEvent::Type::LEAVE:
|
||||||
|
@ -1478,17 +1478,10 @@ void GraphicsWindow::EditControlDone(const std::string &s) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsWindow::MouseScroll(double x, double y, double delta) {
|
void GraphicsWindow::MouseScroll(double zoomMultiplyer) {
|
||||||
double offsetRight = offset.Dot(projRight);
|
|
||||||
double offsetUp = offset.Dot(projUp);
|
|
||||||
|
|
||||||
double righti = x/scale - offsetRight;
|
|
||||||
double upi = y/scale - offsetUp;
|
|
||||||
|
|
||||||
// The default zoom factor is 1.2x for one scroll wheel click (delta==1).
|
|
||||||
// To support smooth scrolling where scroll wheel events come in increments
|
// To support smooth scrolling where scroll wheel events come in increments
|
||||||
// smaller (or larger) than 1 we do:
|
// smaller (or larger) than 1 we do:
|
||||||
// scale *= exp(ln(1.2) * delta);
|
// scale *= exp(ln(1.2) * zoomMultiplyer);
|
||||||
// to ensure that the same total scroll delta always results in the same
|
// 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.
|
// 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
|
// For example if we scroll a total delta of a+b in two events vs. one then
|
||||||
|
@ -1496,21 +1489,7 @@ void GraphicsWindow::MouseScroll(double x, double y, double delta) {
|
||||||
// while
|
// while
|
||||||
// scale * a * b != scale * (a+b)
|
// scale * a * b != scale * (a+b)
|
||||||
// So this constant is ln(1.2) = 0.1823216 to make the default zoom 1.2x
|
// So this constant is ln(1.2) = 0.1823216 to make the default zoom 1.2x
|
||||||
scale *= exp(0.1823216 * delta);
|
ZoomToMouse(zoomMultiplyer);
|
||||||
|
|
||||||
double rightf = x/scale - offsetRight;
|
|
||||||
double upf = y/scale - offsetUp;
|
|
||||||
|
|
||||||
offset = offset.Plus(projRight.ScaledBy(rightf - righti));
|
|
||||||
offset = offset.Plus(projUp.ScaledBy(upf - upi));
|
|
||||||
|
|
||||||
if(SS.TW.shown.screen == TextWindow::Screen::EDIT_VIEW) {
|
|
||||||
if(havePainted) {
|
|
||||||
SS.ScheduleShowTW();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
havePainted = false;
|
|
||||||
Invalidate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsWindow::MouseLeave() {
|
void GraphicsWindow::MouseLeave() {
|
||||||
|
|
|
@ -221,6 +221,7 @@ public:
|
||||||
std::function<bool(KeyboardEvent)> onKeyboardEvent;
|
std::function<bool(KeyboardEvent)> onKeyboardEvent;
|
||||||
std::function<void(std::string)> onEditingDone;
|
std::function<void(std::string)> onEditingDone;
|
||||||
std::function<void(double)> onScrollbarAdjusted;
|
std::function<void(double)> onScrollbarAdjusted;
|
||||||
|
std::function<void()> onContextLost;
|
||||||
std::function<void()> onRender;
|
std::function<void()> onRender;
|
||||||
|
|
||||||
virtual ~Window() = default;
|
virtual ~Window() = default;
|
||||||
|
@ -229,7 +230,7 @@ public:
|
||||||
virtual double GetPixelDensity() = 0;
|
virtual double GetPixelDensity() = 0;
|
||||||
// Returns raster graphics and coordinate scale (already applied on the platform side),
|
// Returns raster graphics and coordinate scale (already applied on the platform side),
|
||||||
// i.e. size of logical pixel in physical pixels, or device pixel ratio.
|
// i.e. size of logical pixel in physical pixels, or device pixel ratio.
|
||||||
virtual int GetDevicePixelRatio() = 0;
|
virtual double GetDevicePixelRatio() = 0;
|
||||||
// Returns (fractional) font scale, to be applied on top of (integral) device pixel ratio.
|
// Returns (fractional) font scale, to be applied on top of (integral) device pixel ratio.
|
||||||
virtual double GetDeviceFontScale() {
|
virtual double GetDeviceFontScale() {
|
||||||
return GetPixelDensity() / GetDevicePixelRatio() / 96.0;
|
return GetPixelDensity() / GetDevicePixelRatio() / 96.0;
|
||||||
|
|
|
@ -889,7 +889,7 @@ public:
|
||||||
return gtkWindow.get_screen()->get_resolution();
|
return gtkWindow.get_screen()->get_resolution();
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetDevicePixelRatio() override {
|
double GetDevicePixelRatio() override {
|
||||||
return gtkWindow.get_scale_factor();
|
return gtkWindow.get_scale_factor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -372,7 +372,8 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
double rotationGestureCurrent;
|
double rotationGestureCurrent;
|
||||||
Point2d trackpadPositionShift;
|
Point2d trackpadPositionShift;
|
||||||
bool inTrackpadScrollGesture;
|
bool inTrackpadScrollGesture;
|
||||||
int numTouches;
|
int activeTrackpadTouches;
|
||||||
|
bool scrollFromTrackpadTouch;
|
||||||
Platform::Window::Kind kind;
|
Platform::Window::Kind kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,7 +399,8 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
editor.action = @selector(didEdit:);
|
editor.action = @selector(didEdit:);
|
||||||
|
|
||||||
inTrackpadScrollGesture = false;
|
inTrackpadScrollGesture = false;
|
||||||
numTouches = 0;
|
activeTrackpadTouches = 0;
|
||||||
|
scrollFromTrackpadTouch = false;
|
||||||
self.acceptsTouchEvents = YES;
|
self.acceptsTouchEvents = YES;
|
||||||
kind = aKind;
|
kind = aKind;
|
||||||
if(kind == Platform::Window::Kind::TOPLEVEL) {
|
if(kind == Platform::Window::Kind::TOPLEVEL) {
|
||||||
|
@ -576,9 +578,16 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
using Platform::MouseEvent;
|
using Platform::MouseEvent;
|
||||||
|
|
||||||
MouseEvent event = [self convertMouseEvent:nsEvent];
|
MouseEvent event = [self convertMouseEvent:nsEvent];
|
||||||
// Check for number of touches to exclude single-finger scrolling on Magic Mouse
|
if(nsEvent.phase == NSEventPhaseBegan) {
|
||||||
bool isTrackpadEvent = numTouches >= 2 && nsEvent.subtype == NSEventSubtypeTabletPoint;
|
// If this scroll began on trackpad then touchesBeganWithEvent was called prior to this
|
||||||
if(isTrackpadEvent && kind == Platform::Window::Kind::TOPLEVEL) {
|
// event and we have at least one active trackpad touch. We store this information so we
|
||||||
|
// can handle scroll originating from trackpad differently below.
|
||||||
|
scrollFromTrackpadTouch = activeTrackpadTouches > 0 &&
|
||||||
|
nsEvent.subtype == NSEventSubtypeTabletPoint &&
|
||||||
|
kind == Platform::Window::Kind::TOPLEVEL;
|
||||||
|
}
|
||||||
|
// Check if we are scrolling on trackpad and handle things differently.
|
||||||
|
if(scrollFromTrackpadTouch) {
|
||||||
// This is how Cocoa represents 2 finger trackpad drag gestures, rather than going via
|
// This is how Cocoa represents 2 finger trackpad drag gestures, rather than going via
|
||||||
// NSPanGestureRecognizer which is how you might expect this to work... We complicate this
|
// NSPanGestureRecognizer which is how you might expect this to work... We complicate this
|
||||||
// further by also handling shift-two-finger-drag to mean rotate. Fortunately we're using
|
// further by also handling shift-two-finger-drag to mean rotate. Fortunately we're using
|
||||||
|
@ -632,20 +641,15 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)touchesBeganWithEvent:(NSEvent *)event {
|
- (void)touchesBeganWithEvent:(NSEvent *)event {
|
||||||
numTouches = [event touchesMatchingPhase:NSTouchPhaseTouching inView:self].count;
|
activeTrackpadTouches++;
|
||||||
[super touchesBeganWithEvent:event];
|
|
||||||
}
|
|
||||||
- (void)touchesMovedWithEvent:(NSEvent *)event {
|
|
||||||
numTouches = [event touchesMatchingPhase:NSTouchPhaseTouching inView:self].count;
|
|
||||||
[super touchesMovedWithEvent:event];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)touchesEndedWithEvent:(NSEvent *)event {
|
- (void)touchesEndedWithEvent:(NSEvent *)event {
|
||||||
numTouches = [event touchesMatchingPhase:NSTouchPhaseTouching inView:self].count;
|
activeTrackpadTouches--;
|
||||||
[super touchesEndedWithEvent:event];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)touchesCancelledWithEvent:(NSEvent *)event {
|
- (void)touchesCancelledWithEvent:(NSEvent *)event {
|
||||||
numTouches = 0;
|
activeTrackpadTouches--;
|
||||||
[super touchesCancelledWithEvent:event];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)mouseExited:(NSEvent *)nsEvent {
|
- (void)mouseExited:(NSEvent *)nsEvent {
|
||||||
|
@ -983,10 +987,10 @@ public:
|
||||||
return (displayPixelSize.width / displayPhysicalSize.width) * 25.4f;
|
return (displayPixelSize.width / displayPhysicalSize.width) * 25.4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetDevicePixelRatio() override {
|
double GetDevicePixelRatio() override {
|
||||||
NSSize unitSize = { 1.0f, 0.0f };
|
NSSize unitSize = { 1.0f, 0.0f };
|
||||||
unitSize = [ssView convertSizeToBacking:unitSize];
|
unitSize = [ssView convertSizeToBacking:unitSize];
|
||||||
return (int)unitSize.width;
|
return unitSize.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsVisible() override {
|
bool IsVisible() override {
|
||||||
|
|
|
@ -793,7 +793,7 @@ public:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WM_SIZING: {
|
case WM_SIZING: {
|
||||||
int pixelRatio = window->GetDevicePixelRatio();
|
double pixelRatio = window->GetDevicePixelRatio();
|
||||||
|
|
||||||
RECT rcw, rcc;
|
RECT rcw, rcc;
|
||||||
sscheck(GetWindowRect(window->hWindow, &rcw));
|
sscheck(GetWindowRect(window->hWindow, &rcw));
|
||||||
|
@ -806,10 +806,10 @@ public:
|
||||||
int adjHeight = rc->bottom - rc->top;
|
int adjHeight = rc->bottom - rc->top;
|
||||||
|
|
||||||
adjWidth -= nonClientWidth;
|
adjWidth -= nonClientWidth;
|
||||||
adjWidth = max(window->minWidth * pixelRatio, adjWidth);
|
adjWidth = max((int)(window->minWidth * pixelRatio), adjWidth);
|
||||||
adjWidth += nonClientWidth;
|
adjWidth += nonClientWidth;
|
||||||
adjHeight -= nonClientHeight;
|
adjHeight -= nonClientHeight;
|
||||||
adjHeight = max(window->minHeight * pixelRatio, adjHeight);
|
adjHeight = max((int)(window->minHeight * pixelRatio), adjHeight);
|
||||||
adjHeight += nonClientHeight;
|
adjHeight += nonClientHeight;
|
||||||
switch(wParam) {
|
switch(wParam) {
|
||||||
case WMSZ_RIGHT:
|
case WMSZ_RIGHT:
|
||||||
|
@ -868,7 +868,7 @@ public:
|
||||||
case WM_MOUSEMOVE:
|
case WM_MOUSEMOVE:
|
||||||
case WM_MOUSEWHEEL:
|
case WM_MOUSEWHEEL:
|
||||||
case WM_MOUSELEAVE: {
|
case WM_MOUSELEAVE: {
|
||||||
int pixelRatio = window->GetDevicePixelRatio();
|
double pixelRatio = window->GetDevicePixelRatio();
|
||||||
|
|
||||||
MouseEvent event = {};
|
MouseEvent event = {};
|
||||||
event.x = GET_X_LPARAM(lParam) / pixelRatio;
|
event.x = GET_X_LPARAM(lParam) / pixelRatio;
|
||||||
|
@ -941,7 +941,7 @@ public:
|
||||||
event.y = pt.y / 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) / WHEEL_DELTA;
|
event.scrollDelta = GET_WHEEL_DELTA_WPARAM(wParam) / (double)WHEEL_DELTA;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WM_MOUSELEAVE:
|
case WM_MOUSELEAVE:
|
||||||
|
@ -1109,10 +1109,10 @@ public:
|
||||||
return (double)dpi;
|
return (double)dpi;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetDevicePixelRatio() override {
|
double GetDevicePixelRatio() override {
|
||||||
UINT dpi;
|
UINT dpi;
|
||||||
sscheck(dpi = ssGetDpiForWindow(hWindow));
|
sscheck(dpi = ssGetDpiForWindow(hWindow));
|
||||||
return dpi / USER_DEFAULT_SCREEN_DPI;
|
return (double)dpi / USER_DEFAULT_SCREEN_DPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsVisible() override {
|
bool IsVisible() override {
|
||||||
|
@ -1177,7 +1177,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetContentSize(double *width, double *height) override {
|
void GetContentSize(double *width, double *height) override {
|
||||||
int pixelRatio = GetDevicePixelRatio();
|
double pixelRatio = GetDevicePixelRatio();
|
||||||
|
|
||||||
RECT rc;
|
RECT rc;
|
||||||
sscheck(GetClientRect(hWindow, &rc));
|
sscheck(GetClientRect(hWindow, &rc));
|
||||||
|
@ -1189,15 +1189,15 @@ public:
|
||||||
minWidth = (int)width;
|
minWidth = (int)width;
|
||||||
minHeight = (int)height;
|
minHeight = (int)height;
|
||||||
|
|
||||||
int pixelRatio = GetDevicePixelRatio();
|
double pixelRatio = GetDevicePixelRatio();
|
||||||
|
|
||||||
RECT rc;
|
RECT rc;
|
||||||
sscheck(GetClientRect(hWindow, &rc));
|
sscheck(GetClientRect(hWindow, &rc));
|
||||||
if(rc.right - rc.left < minWidth * pixelRatio) {
|
if(rc.right - rc.left < minWidth * pixelRatio) {
|
||||||
rc.right = rc.left + minWidth * pixelRatio;
|
rc.right = rc.left + (LONG)(minWidth * pixelRatio);
|
||||||
}
|
}
|
||||||
if(rc.bottom - rc.top < minHeight * pixelRatio) {
|
if(rc.bottom - rc.top < minHeight * pixelRatio) {
|
||||||
rc.bottom = rc.top + minHeight * pixelRatio;
|
rc.bottom = rc.top + (LONG)(minHeight * pixelRatio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1270,7 +1270,7 @@ public:
|
||||||
tooltipText = newText;
|
tooltipText = newText;
|
||||||
|
|
||||||
if(!newText.empty()) {
|
if(!newText.empty()) {
|
||||||
int pixelRatio = GetDevicePixelRatio();
|
double pixelRatio = GetDevicePixelRatio();
|
||||||
RECT toolRect;
|
RECT toolRect;
|
||||||
toolRect.left = (int)(x * pixelRatio);
|
toolRect.left = (int)(x * pixelRatio);
|
||||||
toolRect.top = (int)(y * pixelRatio);
|
toolRect.top = (int)(y * pixelRatio);
|
||||||
|
@ -1301,9 +1301,9 @@ public:
|
||||||
bool isMonospace, const std::string &text) override {
|
bool isMonospace, const std::string &text) override {
|
||||||
if(IsEditorVisible()) return;
|
if(IsEditorVisible()) return;
|
||||||
|
|
||||||
int pixelRatio = GetDevicePixelRatio();
|
double pixelRatio = GetDevicePixelRatio();
|
||||||
|
|
||||||
HFONT hFont = CreateFontW(-(LONG)fontHeight * GetDevicePixelRatio(), 0, 0, 0,
|
HFONT hFont = CreateFontW(-(int)(fontHeight * GetDevicePixelRatio()), 0, 0, 0,
|
||||||
FW_REGULAR, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
|
FW_REGULAR, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
|
||||||
DEFAULT_QUALITY, FF_DONTCARE, isMonospace ? L"Lucida Console" : L"Arial");
|
DEFAULT_QUALITY, FF_DONTCARE, isMonospace ? L"Lucida Console" : L"Arial");
|
||||||
if(hFont == NULL) {
|
if(hFont == NULL) {
|
||||||
|
@ -1324,12 +1324,12 @@ public:
|
||||||
sscheck(ReleaseDC(hEditor, hDc));
|
sscheck(ReleaseDC(hEditor, hDc));
|
||||||
|
|
||||||
RECT rc;
|
RECT rc;
|
||||||
rc.left = (LONG)x * pixelRatio;
|
rc.left = (LONG)(x * pixelRatio);
|
||||||
rc.top = (LONG)y * pixelRatio - tm.tmAscent;
|
rc.top = (LONG)(y * pixelRatio) - tm.tmAscent;
|
||||||
// Add one extra char width to avoid scrolling.
|
// Add one extra char width to avoid scrolling.
|
||||||
rc.right = (LONG)x * pixelRatio +
|
rc.right = (LONG)(x * pixelRatio) +
|
||||||
std::max((LONG)minWidth * pixelRatio, ts.cx + tm.tmAveCharWidth);
|
std::max((LONG)(minWidth * pixelRatio), ts.cx + tm.tmAveCharWidth);
|
||||||
rc.bottom = (LONG)y * pixelRatio + tm.tmDescent;
|
rc.bottom = (LONG)(y * pixelRatio) + tm.tmDescent;
|
||||||
sscheck(ssAdjustWindowRectExForDpi(&rc, 0, /*bMenu=*/FALSE, WS_EX_CLIENTEDGE,
|
sscheck(ssAdjustWindowRectExForDpi(&rc, 0, /*bMenu=*/FALSE, WS_EX_CLIENTEDGE,
|
||||||
ssGetDpiForWindow(hWindow)));
|
ssGetDpiForWindow(hWindow)));
|
||||||
|
|
||||||
|
@ -1608,7 +1608,7 @@ public:
|
||||||
|
|
||||||
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) {
|
||||||
std::string pattern = "*." + extension;
|
std::string pattern = "*." + extension;
|
||||||
if(!desc.empty()) desc += ", ";
|
if(!desc.empty()) desc += ", ";
|
||||||
desc += pattern;
|
desc += pattern;
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html><!--
|
||||||
|
--><head><!--
|
||||||
|
--><meta charset="utf-8"><!--
|
||||||
|
--><title>SolveSpace Web Edition (EXPERIMENTAL)</title><!--
|
||||||
|
--><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.1/css/all.css" integrity="sha384-O8whS3fhG2OnA5Kas0Y9l3cfpmYjapjI0E4theH4iuMD+pLhbf6JI0jIMfYcK3yZ" crossorigin="anonymous"><!--
|
||||||
|
--><link rel="stylesheet" href="solvespaceui.css"><!--
|
||||||
|
--><script src="solvespaceui.js"></script><!--
|
||||||
|
--><script src="filemanagerui.js"></script><!--
|
||||||
|
--></head><!--
|
||||||
|
--><body><!--
|
||||||
|
--><div id="splash">
|
||||||
|
<div class="center">
|
||||||
|
<div id="spinner"></div>
|
||||||
|
<div id="status">Downloading...</div>
|
||||||
|
<div id="crash" style="display:none;">
|
||||||
|
SolveSpace has crashed. See console for details.<br>
|
||||||
|
The Web Edition of SolveSpace is experimental,<br>
|
||||||
|
and may not be as reliable as the Desktop Edition.<br>
|
||||||
|
<a href="javascript:window.location.reload()">Restart</a>
|
||||||
|
</div>
|
||||||
|
<progress id="progress" value="0" max="100" hidden="1"></progress>
|
||||||
|
</div>
|
||||||
|
</div><!--
|
||||||
|
--><main><!--
|
||||||
|
FIXME(emscripten): without this, a window resize is required in Chrome
|
||||||
|
to get the layout to update and canvas size to match up. What?
|
||||||
|
--><ul class="menu menubar" style="visibility: hidden"><li>None</li></ul><!--
|
||||||
|
--><div id="container"><!--
|
||||||
|
--><div id="container0"><canvas id="canvas0"></canvas></div><!--
|
||||||
|
--><div id="view_separator"></div><!--
|
||||||
|
--><div id="container1parent"><!--
|
||||||
|
--><div id="container1"><canvas id="canvas1"></canvas></div><!--
|
||||||
|
--><div id="canvas1scrollbarbox"><!--
|
||||||
|
--><div id="canvas1scrollbar"></div><!--
|
||||||
|
--></div><!--
|
||||||
|
--></div><!--
|
||||||
|
--></div><!--
|
||||||
|
--></main><!--
|
||||||
|
--><script type="text/javascript">
|
||||||
|
var splashElement = document.getElementById('splash');
|
||||||
|
var spinnerElement = document.getElementById('spinner');
|
||||||
|
var statusElement = document.getElementById('status');
|
||||||
|
var progressElement = document.getElementById('progress');
|
||||||
|
var crashElement = document.getElementById('crash');
|
||||||
|
var canvas0Element = document.getElementById('canvas0');
|
||||||
|
var canvas1Element = document.getElementById('canvas1');
|
||||||
|
|
||||||
|
canvas0Element.oncontextmenu = function(event) { event.preventDefault(); }
|
||||||
|
canvas1Element.oncontextmenu = function(event) { event.preventDefault(); }
|
||||||
|
|
||||||
|
var Module = {
|
||||||
|
preRun: [],
|
||||||
|
postRun: [],
|
||||||
|
print: console.log,
|
||||||
|
printErr: console.error,
|
||||||
|
state: 'loading',
|
||||||
|
setStatus: function(text) {
|
||||||
|
if(this.state == 'crashed') {
|
||||||
|
spinnerElement.style.display = 'none';
|
||||||
|
statusElement.style.display = 'none';
|
||||||
|
crashElement.style.display = '';
|
||||||
|
splashElement.style.display = '';
|
||||||
|
} else if(text != '') {
|
||||||
|
console.log('Status:', text);
|
||||||
|
statusElement.innerText = text;
|
||||||
|
} else if(this.state != 'done') {
|
||||||
|
console.log('Status: Done!');
|
||||||
|
splashElement.style.display = 'none';
|
||||||
|
this.state = 'done';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
totalDependencies: 0,
|
||||||
|
monitorRunDependencies: function(remainingDependencies) {
|
||||||
|
this.totalDependencies = Math.max(this.totalDependencies, remainingDependencies);
|
||||||
|
if(remainingDependencies > 0) {
|
||||||
|
var completeDependencies = this.totalDependencies - remainingDependencies;
|
||||||
|
Module.setStatus('Preparing... (' + completeDependencies + '/' +
|
||||||
|
this.totalDependencies + ')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Module.setStatus('Downloading...');
|
||||||
|
window.onerror = function() {
|
||||||
|
Module.state = 'crashed';
|
||||||
|
Module.setStatus();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
</script><!--
|
||||||
|
-->{{{ SCRIPT }}}<!--
|
||||||
|
--></body></html>
|
|
@ -0,0 +1,525 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const FileManagerUI_OPEN = 0;
|
||||||
|
const FileManagerUI_SAVE = FileManagerUI_OPEN + 1;
|
||||||
|
const FileManagerUI_BROWSE = FileManagerUI_SAVE + 1;
|
||||||
|
|
||||||
|
//FIXME(emscripten): File size thresholds. How large file can we accept safely ?
|
||||||
|
|
||||||
|
/** Maximum filesize for a uploaded file.
|
||||||
|
* @type {number} */
|
||||||
|
const FileManagerUI_UPLOAD_FILE_SIZE_LIMIT = 50 * 1000 * 1000;
|
||||||
|
|
||||||
|
const tryMakeDirectory = (path) => {
|
||||||
|
try {
|
||||||
|
FS.mkdir(path);
|
||||||
|
} catch {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FileManagerUI {
|
||||||
|
/**
|
||||||
|
* @param {number} mode - dialog mode FileManagerUI_[ OPEN, SAVE, BROWSE ]
|
||||||
|
*/
|
||||||
|
constructor(mode) {
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.__isOpenDialog = false;
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.__isSaveDialog = false;
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.__isBrowseDialog = false;
|
||||||
|
|
||||||
|
if (mode == FileManagerUI_OPEN) {
|
||||||
|
this.__isOpenDialog = true;
|
||||||
|
} else if (mode == FileManagerUI_SAVE) {
|
||||||
|
this.__isSaveDialog = true;
|
||||||
|
} else {
|
||||||
|
this.__isBrowseDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {boolean} true if the dialog is shown. */
|
||||||
|
this.__isShown = false;
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
this.__extension_filters = [".slvs"];
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
this.__basePathInFilesystem = "";
|
||||||
|
|
||||||
|
/** @type {string} filename user selected. empty if nothing selected */
|
||||||
|
this.__selectedFilename = "";
|
||||||
|
|
||||||
|
this.__closedWithCancel = false;
|
||||||
|
|
||||||
|
this.__defaultFilename = "untitled";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** deconstructor
|
||||||
|
*/
|
||||||
|
dispose() {
|
||||||
|
if (this.__dialogRootElement) {
|
||||||
|
this.__dialogHeaderElement = null;
|
||||||
|
this.__descriptionElement = null;
|
||||||
|
this.__filelistElement = null;
|
||||||
|
this.__fileInputElement = null;
|
||||||
|
this.__saveFilenameInputElement = null;
|
||||||
|
this.__buttonContainerElement = null;
|
||||||
|
this.__dialogRootElement.parentElement.removeChild(this.__dialogRootElement);
|
||||||
|
this.__dialogRootElement = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} label
|
||||||
|
* @param {string} response
|
||||||
|
* @param {bool} isDefault
|
||||||
|
*/
|
||||||
|
__addButton(label, response, isDefault, onclick) {
|
||||||
|
const buttonElem = document.createElement("div");
|
||||||
|
addClass(buttonElem, "button");
|
||||||
|
setLabelWithMnemonic(buttonElem, label);
|
||||||
|
if (isDefault) {
|
||||||
|
addClass(buttonElem, "default");
|
||||||
|
addClass(buttonElem, "selected");
|
||||||
|
}
|
||||||
|
buttonElem.addEventListener("click", () => {
|
||||||
|
if (onclick) {
|
||||||
|
if (onclick()) {
|
||||||
|
this.__close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.__close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.__buttonContainerElement.appendChild(buttonElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} div element that built
|
||||||
|
*/
|
||||||
|
buildDialog() {
|
||||||
|
const root = document.createElement('div');
|
||||||
|
addClass(root, "modal");
|
||||||
|
root.style.display = "none";
|
||||||
|
root.style.zIndex = 1000;
|
||||||
|
|
||||||
|
const dialog = document.createElement('div');
|
||||||
|
addClass(dialog, "dialog");
|
||||||
|
addClass(dialog, "wide");
|
||||||
|
root.appendChild(dialog);
|
||||||
|
|
||||||
|
const messageHeader = document.createElement('strong');
|
||||||
|
this.__dialogHeaderElement = messageHeader;
|
||||||
|
addClass(messageHeader, "dialog_header");
|
||||||
|
dialog.appendChild(messageHeader);
|
||||||
|
|
||||||
|
const description = document.createElement('p');
|
||||||
|
this.__descriptionElement = description;
|
||||||
|
dialog.appendChild(description);
|
||||||
|
|
||||||
|
const filelistheader = document.createElement('h3');
|
||||||
|
filelistheader.textContent = 'Files:';
|
||||||
|
dialog.appendChild(filelistheader);
|
||||||
|
|
||||||
|
const filelist = document.createElement('ul');
|
||||||
|
this.__filelistElement = filelist;
|
||||||
|
addClass(filelist, 'filelist');
|
||||||
|
dialog.appendChild(filelist);
|
||||||
|
|
||||||
|
const dummyfilelistitem = document.createElement('li');
|
||||||
|
dummyfilelistitem.textContent = "(No file in psuedo filesystem)";
|
||||||
|
filelist.appendChild(dummyfilelistitem);
|
||||||
|
|
||||||
|
if (this.__isOpenDialog) {
|
||||||
|
const fileuploadcontainer = document.createElement('div');
|
||||||
|
dialog.appendChild(fileuploadcontainer);
|
||||||
|
|
||||||
|
const fileuploadheader = document.createElement('h3');
|
||||||
|
fileuploadheader.textContent = "Upload file:";
|
||||||
|
fileuploadcontainer.appendChild(fileuploadheader);
|
||||||
|
|
||||||
|
const dragdropdescription = document.createElement('p');
|
||||||
|
dragdropdescription.textContent = "(Drag & drop file to the following box)";
|
||||||
|
dragdropdescription.style.fontSize = "0.8em";
|
||||||
|
dragdropdescription.style.margin = "0.1em";
|
||||||
|
fileuploadcontainer.appendChild(dragdropdescription);
|
||||||
|
|
||||||
|
const filedroparea = document.createElement('div');
|
||||||
|
addClass(filedroparea, 'filedrop');
|
||||||
|
filedroparea.addEventListener('dragstart', (ev) => this.__onFileDragDrop(ev));
|
||||||
|
filedroparea.addEventListener('dragover', (ev) => this.__onFileDragDrop(ev));
|
||||||
|
filedroparea.addEventListener('dragleave', (ev) => this.__onFileDragDrop(ev));
|
||||||
|
filedroparea.addEventListener('drop', (ev) => this.__onFileDragDrop(ev));
|
||||||
|
fileuploadcontainer.appendChild(filedroparea);
|
||||||
|
|
||||||
|
const fileinput = document.createElement('input');
|
||||||
|
this.__fileInputElement = fileinput;
|
||||||
|
fileinput.setAttribute('type', 'file');
|
||||||
|
fileinput.style.width = "100%";
|
||||||
|
fileinput.addEventListener('change', (ev) => this.__onFileInputChanged(ev));
|
||||||
|
filedroparea.appendChild(fileinput);
|
||||||
|
|
||||||
|
} else if (this.__isSaveDialog) {
|
||||||
|
const filenameinputcontainer = document.createElement('div');
|
||||||
|
dialog.appendChild(filenameinputcontainer);
|
||||||
|
|
||||||
|
const filenameinputheader = document.createElement('h3');
|
||||||
|
filenameinputheader.textContent = "Filename:";
|
||||||
|
filenameinputcontainer.appendChild(filenameinputheader);
|
||||||
|
|
||||||
|
const filenameinput = document.createElement('input');
|
||||||
|
filenameinput.setAttribute('type', 'input');
|
||||||
|
filenameinput.style.width = "90%";
|
||||||
|
filenameinput.style.margin = "auto 1em auto 1em";
|
||||||
|
this.__saveFilenameInputElement = filenameinput;
|
||||||
|
filenameinputcontainer.appendChild(filenameinput);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paragraph element for spacer
|
||||||
|
dialog.appendChild(document.createElement('p'));
|
||||||
|
|
||||||
|
const buttoncontainer = document.createElement('div');
|
||||||
|
this.__buttonContainerElement = buttoncontainer;
|
||||||
|
addClass(buttoncontainer, "buttons");
|
||||||
|
dialog.appendChild(buttoncontainer);
|
||||||
|
|
||||||
|
this.__addButton('OK', 0, false, () => {
|
||||||
|
if (this.__isOpenDialog) {
|
||||||
|
let selectedFilename = null;
|
||||||
|
const fileitems = document.querySelectorAll('input[type="radio"][name="filemanager_filelist"]');
|
||||||
|
Array.from(fileitems).forEach((radiobox) => {
|
||||||
|
if (radiobox.checked) {
|
||||||
|
selectedFilename = radiobox.parentElement.getAttribute('data-filename');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (selectedFilename) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.__addButton('Cancel', 1, true, () => {
|
||||||
|
this.__closedWithCancel = true;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
setTitle(text) {
|
||||||
|
this.__dialogHeaderText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
setDescription(text) {
|
||||||
|
this.__descriptionText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} path file prefix. (ex) 'tmp/' to '/tmp/filename.txt'
|
||||||
|
*/
|
||||||
|
setBasePath(path) {
|
||||||
|
this.__basePathInFilesystem = path;
|
||||||
|
tryMakeDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filename
|
||||||
|
*/
|
||||||
|
setDefaultFilename(filename) {
|
||||||
|
this.__defaultFilename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} filter comma-separated extensions like ".slvs,.stl;."
|
||||||
|
*/
|
||||||
|
setFilter(filter) {
|
||||||
|
const exts = filter.split(',');
|
||||||
|
this.__extension_filters = exts;
|
||||||
|
}
|
||||||
|
|
||||||
|
__buildFileEntry(filename) {
|
||||||
|
const lielem = document.createElement('li');
|
||||||
|
const label = document.createElement('label');
|
||||||
|
label.setAttribute('data-filename', filename);
|
||||||
|
lielem.appendChild(label);
|
||||||
|
const radiobox = document.createElement('input');
|
||||||
|
radiobox.setAttribute('type', 'radio');
|
||||||
|
if (!this.__isOpenDialog) {
|
||||||
|
radiobox.style.display = "none";
|
||||||
|
}
|
||||||
|
radiobox.setAttribute('name', 'filemanager_filelist');
|
||||||
|
label.appendChild(radiobox);
|
||||||
|
const filenametext = document.createTextNode(filename);
|
||||||
|
label.appendChild(filenametext);
|
||||||
|
|
||||||
|
return lielem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string[]} filename array
|
||||||
|
*/
|
||||||
|
__getFileEntries() {
|
||||||
|
const basePath = this.__basePathInFilesystem;
|
||||||
|
/** @type {any[]} */
|
||||||
|
const nodes = FS.readdir(basePath);
|
||||||
|
/** @type {string[]} */
|
||||||
|
const files = nodes.filter((nodename) => {
|
||||||
|
return FS.isFile(FS.lstat(basePath + nodename).mode);
|
||||||
|
});
|
||||||
|
/*.map((filename) => {
|
||||||
|
return basePath + filename;
|
||||||
|
});*/
|
||||||
|
console.log(`__getFileEntries():`, files);
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]?} files file list already constructed
|
||||||
|
* @returns {string[]} filename array
|
||||||
|
*/
|
||||||
|
__getFileEntries_recurse(basePath) {
|
||||||
|
//FIXME:remove try catch block
|
||||||
|
try {
|
||||||
|
//const basePath = this.__basePathInFilesystem;
|
||||||
|
FS.currentPath = basePath;
|
||||||
|
/** @type {any[]} */
|
||||||
|
const nodes = FS.readdir(basePath);
|
||||||
|
|
||||||
|
const filesInThisDirectory = nodes.filter((nodename) => {
|
||||||
|
return FS.isFile(FS.lstat(basePath + "/" + nodename).mode);
|
||||||
|
}).map((filename) => {
|
||||||
|
return basePath + "/" + filename;
|
||||||
|
});
|
||||||
|
let files = filesInThisDirectory;
|
||||||
|
|
||||||
|
const directories = nodes.filter((nodename) => {
|
||||||
|
return FS.isDir(FS.lstat(basePath + "/" + nodename).mode);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < directories.length; i++) {
|
||||||
|
const directoryname = directories[i];
|
||||||
|
if (directoryname == '.' || directoryname == '..') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const orig_cwd = FS.currentPath;
|
||||||
|
const directoryfullpath = basePath + "/" + directoryname;
|
||||||
|
FS.currentPath = directoryfullpath;
|
||||||
|
files = files.concat(this.__getFileEntries_recurse(directoryfullpath));
|
||||||
|
FS.currentPath = orig_cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`__getFileEntries_recurse(): in "${basePath}"`, files);
|
||||||
|
return files;
|
||||||
|
|
||||||
|
} catch (excep) {
|
||||||
|
console.log(excep);
|
||||||
|
throw excep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__updateFileList() {
|
||||||
|
console.log(`__updateFileList()`);
|
||||||
|
Array.from(this.__filelistElement.children).forEach((elem) => {
|
||||||
|
this.__filelistElement.removeChild(elem);
|
||||||
|
});
|
||||||
|
// const files = this.__getFileEntries();
|
||||||
|
FS.currentPath = this.__basePathInFilesystem;
|
||||||
|
const files = this.__getFileEntries_recurse(this.__basePathInFilesystem);
|
||||||
|
if (files.length < 1) {
|
||||||
|
const dummyfilelistitem = document.createElement('li');
|
||||||
|
dummyfilelistitem.textContent = "(No file in psuedo filesystem)";
|
||||||
|
this.__filelistElement.appendChild(dummyfilelistitem);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
files.forEach((entry) => {
|
||||||
|
this.__filelistElement.appendChild(this.__buildFileEntry(entry));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {File} file
|
||||||
|
*/
|
||||||
|
__getFileAsArrayBuffer(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const filereader = new FileReader();
|
||||||
|
filereader.onerror = (ev) => {
|
||||||
|
reject(ev);
|
||||||
|
};
|
||||||
|
filereader.onload = (ev) => {
|
||||||
|
resolve(ev.target.result);
|
||||||
|
};
|
||||||
|
filereader.readAsArrayBuffer(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {File} file
|
||||||
|
*/
|
||||||
|
async __tryAddFile(file) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
if (!file) {
|
||||||
|
reject(new Error(`Invalid arg: file is ${file}`));
|
||||||
|
|
||||||
|
} else if (file.size > FileManagerUI_UPLOAD_FILE_SIZE_LIMIT) {
|
||||||
|
//FIXME(emscripten): Use our MessageDialog instead of browser's alert().
|
||||||
|
alert(`Specified file is larger than limit of ${FileManagerUI_UPLOAD_FILE_SIZE_LIMIT} bytes. Canceced.`);
|
||||||
|
reject(new Error(`File is too large: "${file.name} is ${file.size} bytes`));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Just add to Filesystem
|
||||||
|
const path = `${this.__basePathInFilesystem}${file.name}`;
|
||||||
|
const blobArrayBuffer = await this.__getFileAsArrayBuffer(file);
|
||||||
|
const u8array = new Uint8Array(blobArrayBuffer);
|
||||||
|
const fs = FS.open(path, "w");
|
||||||
|
FS.write(fs, u8array, 0, u8array.length, 0);
|
||||||
|
FS.close(fs);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
__addSelectedFile() {
|
||||||
|
if (this.__fileInputElement.files.length < 1) {
|
||||||
|
console.warn(`No file selected.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = this.__fileInputElement.files[0];
|
||||||
|
this.__tryAddFile(file)
|
||||||
|
.then(() => {
|
||||||
|
this.__updateFileList();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.__fileInputElement.value = null;
|
||||||
|
console.error(err);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {DragEvent} ev
|
||||||
|
*/
|
||||||
|
__onFileDragDrop(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (ev.type == "dragenter" || ev.type == "dragover" || ev.type == "dragleave") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ev.dataTransfer.files.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.__fileInputElement.files = ev.dataTransfer.files;
|
||||||
|
|
||||||
|
this.__addSelectedFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {InputEvent} _ev
|
||||||
|
*/
|
||||||
|
__onFileInputChanged(_ev) {
|
||||||
|
this.__addSelectedFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Show the FileManager UI dialog */
|
||||||
|
__show() {
|
||||||
|
this.__closedWithCancel = false;
|
||||||
|
|
||||||
|
/** @type {HTMLElement} */
|
||||||
|
this.__dialogRootElement = this.buildDialog();
|
||||||
|
document.querySelector('body').appendChild(this.__dialogRootElement);
|
||||||
|
|
||||||
|
this.__dialogHeaderElement.textContent = this.__dialogHeaderText || "File manager";
|
||||||
|
this.__descriptionElement.textContent = this.__descriptionText || "Select a file.";
|
||||||
|
if (this.__extension_filters) {
|
||||||
|
this.__descriptionElement.textContent += "Requested filter is " + this.__extension_filters.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.__isOpenDialog && this.__extension_filters) {
|
||||||
|
this.__fileInputElement.accept = this.__extension_filters.concat(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.__isSaveDialog) {
|
||||||
|
this.__saveFilenameInputElement.value = this.__defaultFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__dialogRootElement.style.display = "block";
|
||||||
|
this.__isShown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Close the dialog */
|
||||||
|
__close() {
|
||||||
|
this.__selectedFilename = "";
|
||||||
|
if (this.__isOpenDialog) {
|
||||||
|
Array.from(document.querySelectorAll('input[type="radio"][name="filemanager_filelist"]'))
|
||||||
|
.forEach((elem) => {
|
||||||
|
if (elem.checked) {
|
||||||
|
this.__selectedFilename = elem.parentElement.getAttribute("data-filename");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (this.__isSaveDialog) {
|
||||||
|
if (!this.__closedWithCancel) {
|
||||||
|
this.__selectedFilename = this.__saveFilenameInputElement.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.from(this.__filelistElement.children).forEach((elem) => {
|
||||||
|
this.__filelistElement.removeChild(elem);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dispose();
|
||||||
|
|
||||||
|
this.__isShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isShown() {
|
||||||
|
return this.__isShown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {Promise} filename string on resolved.
|
||||||
|
*/
|
||||||
|
showModalAsync() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.__show();
|
||||||
|
this.__updateFileList();
|
||||||
|
const intervalTimer = setInterval(() => {
|
||||||
|
if (!this.isShown()) {
|
||||||
|
clearInterval(intervalTimer);
|
||||||
|
resolve(this.__selectedFilename);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedFilename() {
|
||||||
|
return this.__selectedFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
show() {
|
||||||
|
this.__show();
|
||||||
|
this.__updateFileList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
window.FileManagerUI = FileManagerUI;
|
|
@ -0,0 +1,344 @@
|
||||||
|
* {
|
||||||
|
font-family: sans;
|
||||||
|
}
|
||||||
|
html, body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: black;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
html, body, canvas, #splash, #container {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Splashscreen */
|
||||||
|
#splash {
|
||||||
|
z-index: 1000;
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
#splash .center {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#splash a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#spinner {
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
margin: 0px auto;
|
||||||
|
border-left: 10px solid rgb(255, 255, 255);
|
||||||
|
border-top: 10px solid rgb(0, 255, 0);
|
||||||
|
border-right: 10px solid rgb(255, 0, 255);
|
||||||
|
border-bottom: 10px solid rgb(0, 255, 0);
|
||||||
|
border-radius: 100%;
|
||||||
|
animation: rotation 3s linear infinite;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
@keyframes rotation {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grid layout for main */
|
||||||
|
main {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
/* Use CSS Grid layout for vertical placement. */
|
||||||
|
display: grid;
|
||||||
|
/* Row 0 for menubar (fit to content), Row 1 for canvas0, canvas1 (rest of space) */
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.button {
|
||||||
|
border: 1px solid hsl(0, 0%, 60%);
|
||||||
|
background: hsl(0, 0%, 10%);
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.button.selected {
|
||||||
|
background: hsl(0, 0%, 20%);
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
background: hsl(0, 0%, 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Editors */
|
||||||
|
.editor {
|
||||||
|
position: fixed;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menus */
|
||||||
|
.menu {
|
||||||
|
font-size: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
padding-right: 10px;
|
||||||
|
list-style-type: none;
|
||||||
|
background: hsl(0, 0%, 20%);
|
||||||
|
color: white;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Normal menu items */
|
||||||
|
.menu > li {
|
||||||
|
z-index: 100;
|
||||||
|
font-size: 16px;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 19px;
|
||||||
|
margin: 2px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
.menu > li::before, .menu > li::after {
|
||||||
|
font-family: 'Font Awesome 5 Free';
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.menu > li.hover,
|
||||||
|
.menu > li.selected,
|
||||||
|
.menu.menubar > li:hover:not(.selected) {
|
||||||
|
background: hsl(0, 0%, 30%);
|
||||||
|
}
|
||||||
|
.menu > li.disabled {
|
||||||
|
color: hsl(0, 0%, 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check and radio menu items */
|
||||||
|
.menu > li {
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
.menu > li::before {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
left: 0px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
.menu > li.check::before {
|
||||||
|
content: '\f0c8';
|
||||||
|
}
|
||||||
|
.menu > li.check.active::before {
|
||||||
|
content: '\f14a';
|
||||||
|
}
|
||||||
|
.menu > li.radio::before {
|
||||||
|
content: '\f111';
|
||||||
|
}
|
||||||
|
.menu > li.radio.active::before {
|
||||||
|
content: '\f192';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Separator menu items */
|
||||||
|
.menu > li.separator {
|
||||||
|
height: 0px;
|
||||||
|
border-top: 1px solid hsl(0, 0%, 30%);
|
||||||
|
margin: 0 2px 0 2px;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Accelerators */
|
||||||
|
.menu > li > .accel {
|
||||||
|
text-align: right;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Submenus */
|
||||||
|
.menu > li > .menu,
|
||||||
|
.menu.popup {
|
||||||
|
display: none;
|
||||||
|
white-space: normal;
|
||||||
|
padding-right: 31px;
|
||||||
|
}
|
||||||
|
.menu > li.has-submenu::after {
|
||||||
|
content: '\f0da';
|
||||||
|
}
|
||||||
|
.menu > li.selected > .menu,
|
||||||
|
.menu > li.hover > .menu,
|
||||||
|
.menu.popup {
|
||||||
|
display: block;
|
||||||
|
background: hsl(0, 0%, 10%);
|
||||||
|
border: 1px solid hsl(0, 0%, 30%);
|
||||||
|
position: absolute;
|
||||||
|
left: 100%;
|
||||||
|
top: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Popup menus */
|
||||||
|
.menu.popup {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menubars */
|
||||||
|
.menubar {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
.menubar > li {
|
||||||
|
width: auto;
|
||||||
|
width: fit-content;
|
||||||
|
margin: 0;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.menubar > li.selected {
|
||||||
|
background: hsl(0, 0%, 10%);
|
||||||
|
border: 1px solid hsl(0, 0%, 30%);
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.menubar.menu > li.selected > .menu {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: -1px;
|
||||||
|
top: 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal popups */
|
||||||
|
.modal {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: hsla(0, 0%, 0%, 60%);
|
||||||
|
}
|
||||||
|
.modal > div {
|
||||||
|
position: absolute;
|
||||||
|
top: 15%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dialogs */
|
||||||
|
.dialog {
|
||||||
|
border: 1px solid hsl(0, 0%, 30%);
|
||||||
|
background: hsl(0, 0%, 10%);
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 400px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
max-height: 70%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.dialog.wide {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
.dialog > .buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
.dialog .filedrop {
|
||||||
|
margin: 1em 0 1em 0;
|
||||||
|
padding: 1em;
|
||||||
|
border: 2px solid black;
|
||||||
|
background-color: hsl(0, 0%, 50%);
|
||||||
|
}
|
||||||
|
.dialog .filelist {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.dialog .filelist li {
|
||||||
|
padding: 0.2em 0.5em 0.2em 0.5em;
|
||||||
|
break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mnemonics */
|
||||||
|
.label > u {
|
||||||
|
position: relative;
|
||||||
|
top: 0px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
body.mnemonic .label > u {
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Canvases */
|
||||||
|
canvas {
|
||||||
|
border: 0px none;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
/* FIXME(emscripten): this should be dynamically adjustable, not hardcoded in CSS */
|
||||||
|
#container0 {
|
||||||
|
flex-basis: 80%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container1parent {
|
||||||
|
flex-basis: 20%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: 410px;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 19px;
|
||||||
|
grid-template-rows: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container1 {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas1scrollbarbox {
|
||||||
|
/* 19px is a magic number for scrollbar width (Yes, this is platform-dependent value but looks almost working.) */
|
||||||
|
width: 19px;
|
||||||
|
min-width: 19px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
background-color: lightgray;
|
||||||
|
-webkit-overflow-scrolling: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas1scrollbar {
|
||||||
|
/* 0px will disable the scrollbar by browser. */
|
||||||
|
width: 1px;
|
||||||
|
/* Disable scrollbar as default. This value will be overwritten by program. */
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#view_separator {
|
||||||
|
width: 4px;
|
||||||
|
background: hsl(0, 0%, 20%);
|
||||||
|
}
|
|
@ -0,0 +1,813 @@
|
||||||
|
function isModal() {
|
||||||
|
var hasModal = !!document.querySelector('.modal');
|
||||||
|
var hasMenuBar = !!document.querySelector('.menubar .selected');
|
||||||
|
var hasPopupMenu = !!document.querySelector('.menu.popup');
|
||||||
|
return hasModal || hasMenuBar || hasPopupMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* String helpers */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} s - original string
|
||||||
|
* @param {number} digits - char length of generating string
|
||||||
|
* @param {string} ch - string to be used for padding
|
||||||
|
* @return {string} generated string ($digits chars length) or $s
|
||||||
|
*/
|
||||||
|
function stringPadLeft(s, digits, ch) {
|
||||||
|
if (s.length > digits) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
for (let i = s.length; i < digits; i++) {
|
||||||
|
s = ch + s;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generate a string expression of now
|
||||||
|
* @return {string} like a "2022_08_31_2245" string (for 2022-08-31 22:45; local time)
|
||||||
|
*/
|
||||||
|
function GetCurrentDateTimeString() {
|
||||||
|
const now = new Date();
|
||||||
|
const padLeft2 = (num) => { return stringPadLeft(num.toString(), 2, '0') };
|
||||||
|
return (`${now.getFullYear()}_${padLeft2(now.getMonth()+1)}_${padLeft2(now.getDate())}` +
|
||||||
|
`_` + `${padLeft2(now.getHours())}${padLeft2(now.getMinutes())}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CSS helpers */
|
||||||
|
function hasClass(element, className) {
|
||||||
|
return element.classList.contains(className);
|
||||||
|
}
|
||||||
|
function addClass(element, className) {
|
||||||
|
element.classList.add(className);
|
||||||
|
}
|
||||||
|
function removeClass(element, className) {
|
||||||
|
element.classList.remove(className);
|
||||||
|
}
|
||||||
|
function removeClassFromAllChildren(element, className) {
|
||||||
|
element.querySelectorAll('.' + className).forEach(function(element) {
|
||||||
|
removeClass(element, className);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mnemonic helpers */
|
||||||
|
function setLabelWithMnemonic(element, labelText) {
|
||||||
|
var label = document.createElement('span');
|
||||||
|
addClass(label, 'label');
|
||||||
|
element.appendChild(label);
|
||||||
|
|
||||||
|
var matches = labelText.match('(.*?)&(.)(.*)?');
|
||||||
|
if(matches) {
|
||||||
|
label.appendChild(document.createTextNode(matches[1]));
|
||||||
|
if(matches[2]) {
|
||||||
|
var mnemonic = document.createElement('u');
|
||||||
|
mnemonic.innerText = matches[2];
|
||||||
|
label.appendChild(mnemonic);
|
||||||
|
addClass(element, 'mnemonic-Key' + matches[2].toUpperCase());
|
||||||
|
}
|
||||||
|
if(matches[3]) {
|
||||||
|
label.appendChild(document.createTextNode(matches[3]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
label.appendChild(document.createTextNode(labelText))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Touchevent helper
|
||||||
|
* @param {TouchEvent} event
|
||||||
|
* @return {boolean} true if same element is target of touchstart and touchend
|
||||||
|
*/
|
||||||
|
function isSameElementOnTouchstartAndTouchend(event) {
|
||||||
|
const elementOnTouchStart = event.target;
|
||||||
|
const elementOnTouchEnd = document.elementFromPoint(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
|
||||||
|
return elementOnTouchStart == elementOnTouchEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button helpers */
|
||||||
|
function isButton(element) {
|
||||||
|
return hasClass(element, 'button');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button DOM traversal helpers */
|
||||||
|
function getButton(element) {
|
||||||
|
if(!element) return;
|
||||||
|
if(element.tagName == 'U') {
|
||||||
|
element = element.parentElement;
|
||||||
|
}
|
||||||
|
if(hasClass(element, 'label')) {
|
||||||
|
return getButton(element.parentElement);
|
||||||
|
} else if(isButton(element)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button behavior */
|
||||||
|
window.addEventListener('click', function(event) {
|
||||||
|
var button = getButton(event.target);
|
||||||
|
if(button) {
|
||||||
|
button.dispatchEvent(new Event('trigger'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener("touchend", (event) => {
|
||||||
|
if (!isSameElementOnTouchstartAndTouchend(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const button = getButton(event.target);
|
||||||
|
if (button) {
|
||||||
|
button.dispatchEvent(new Event('trigger'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('keydown', function(event) {
|
||||||
|
var selected = document.querySelector('.button.selected');
|
||||||
|
if(!selected) return;
|
||||||
|
|
||||||
|
var outSelected, newSelected;
|
||||||
|
if(event.key == 'ArrowRight') {
|
||||||
|
outSelected = selected;
|
||||||
|
newSelected = selected.nextElementSibling;
|
||||||
|
if(!newSelected) {
|
||||||
|
newSelected = outSelected.parentElement.firstElementChild;
|
||||||
|
}
|
||||||
|
} else if(event.key == 'ArrowLeft') {
|
||||||
|
outSelected = selected;
|
||||||
|
newSelected = selected.previousElementSibling;
|
||||||
|
if(!newSelected) {
|
||||||
|
newSelected = outSelected.parentElement.lastElementChild;
|
||||||
|
}
|
||||||
|
} else if(event.key == 'Enter') {
|
||||||
|
selected.dispatchEvent(new Event('trigger'));
|
||||||
|
} else if(event.key == 'Escape' && hasClass(selected, 'default')) {
|
||||||
|
selected.dispatchEvent(new Event('trigger'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(outSelected) removeClass(outSelected, 'selected');
|
||||||
|
if(newSelected) addClass(newSelected, 'selected');
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Editor helpers */
|
||||||
|
function isEditor(element) {
|
||||||
|
return hasClass(element, 'editor');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Editor DOM traversal helpers */
|
||||||
|
function getEditor(element) {
|
||||||
|
if(!element) return;
|
||||||
|
if(isEditor(element)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Editor behavior */
|
||||||
|
window.addEventListener('keydown', function(event) {
|
||||||
|
var editor = getEditor(event.target);
|
||||||
|
if(editor) {
|
||||||
|
if(event.key == 'Enter') {
|
||||||
|
editor.dispatchEvent(new Event('trigger'));
|
||||||
|
} else if(event.key == 'Escape') {
|
||||||
|
editor.style.display = 'none';
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}, {capture: true});
|
||||||
|
|
||||||
|
/* Menu helpers */
|
||||||
|
function isMenubar(element) {
|
||||||
|
return hasClass(element, 'menubar');
|
||||||
|
}
|
||||||
|
function isMenu(element) {
|
||||||
|
return hasClass(element, 'menu');
|
||||||
|
}
|
||||||
|
function isPopupMenu(element) {
|
||||||
|
return isMenu(element) && hasClass(element, 'popup')
|
||||||
|
}
|
||||||
|
function hasSubmenu(menuItem) {
|
||||||
|
return !!menuItem.querySelector('.menu');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu item helpers */
|
||||||
|
function isMenuItemSelectable(menuItem) {
|
||||||
|
return !(hasClass(menuItem, 'disabled') || hasClass(menuItem, 'separator'));
|
||||||
|
}
|
||||||
|
function isMenuItemSelected(menuItem) {
|
||||||
|
return hasClass(menuItem, 'selected') || hasClass(menuItem, 'hover');
|
||||||
|
}
|
||||||
|
function deselectMenuItem(menuItem) {
|
||||||
|
removeClass(menuItem, 'selected');
|
||||||
|
removeClass(menuItem, 'hover');
|
||||||
|
removeClassFromAllChildren(menuItem, 'selected');
|
||||||
|
removeClassFromAllChildren(menuItem, 'hover');
|
||||||
|
}
|
||||||
|
function selectMenuItem(menuItem) {
|
||||||
|
var menu = menuItem.parentElement;
|
||||||
|
removeClassFromAllChildren(menu, 'selected');
|
||||||
|
removeClassFromAllChildren(menu, 'hover');
|
||||||
|
if(isMenubar(menu)) {
|
||||||
|
addClass(menuItem, 'selected');
|
||||||
|
} else {
|
||||||
|
addClass(menuItem, 'hover');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function triggerMenuItem(menuItem) {
|
||||||
|
selectMenuItem(menuItem);
|
||||||
|
if(hasSubmenu(menuItem)) {
|
||||||
|
selectMenuItem(menuItem.querySelector('li:first-child'));
|
||||||
|
} else {
|
||||||
|
var parent = menuItem.parentElement;
|
||||||
|
while(!isMenubar(parent) && !isPopupMenu(parent)) {
|
||||||
|
parent = parent.parentElement;
|
||||||
|
}
|
||||||
|
removeClassFromAllChildren(parent, 'selected');
|
||||||
|
removeClassFromAllChildren(parent, 'hover');
|
||||||
|
if(isPopupMenu(parent)) {
|
||||||
|
parent.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItem.dispatchEvent(new Event('trigger'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu DOM traversal helpers */
|
||||||
|
function getMenuItem(element) {
|
||||||
|
if(!element) return;
|
||||||
|
if(element.tagName == 'U') {
|
||||||
|
element = element.parentElement;
|
||||||
|
}
|
||||||
|
if(hasClass(element, 'label')) {
|
||||||
|
return getMenuItem(element.parentElement);
|
||||||
|
} else if(element.tagName == 'LI' && isMenu(element.parentElement)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getMenu(element) {
|
||||||
|
if(!element) return;
|
||||||
|
if(isMenu(element)) {
|
||||||
|
return element;
|
||||||
|
} else {
|
||||||
|
var menuItem = getMenuItem(element);
|
||||||
|
if(menuItem && isMenu(menuItem.parentElement)) {
|
||||||
|
return menuItem.parentElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu behavior */
|
||||||
|
window.addEventListener('click', function(event) {
|
||||||
|
var menuItem = getMenuItem(event.target);
|
||||||
|
var menu = getMenu(menuItem);
|
||||||
|
if(menu && isMenubar(menu)) {
|
||||||
|
if(hasClass(menuItem, 'selected')) {
|
||||||
|
removeClass(menuItem, 'selected');
|
||||||
|
} else {
|
||||||
|
selectMenuItem(menuItem);
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
} else if(menu) {
|
||||||
|
if(!hasSubmenu(menuItem)) {
|
||||||
|
triggerMenuItem(menuItem);
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
document.querySelectorAll('.menu .selected, .menu .hover')
|
||||||
|
.forEach(function(menuItem) {
|
||||||
|
deselectMenuItem(menuItem);
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.menu.popup')
|
||||||
|
.forEach(function(menu) {
|
||||||
|
menu.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener("touchend", (event) => {
|
||||||
|
if (!isSameElementOnTouchstartAndTouchend(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var menuItem = getMenuItem(event.target);
|
||||||
|
var menu = getMenu(menuItem);
|
||||||
|
if(menu && isMenubar(menu)) {
|
||||||
|
if(hasClass(menuItem, 'selected')) {
|
||||||
|
removeClass(menuItem, 'selected');
|
||||||
|
} else {
|
||||||
|
selectMenuItem(menuItem);
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
} else if(menu) {
|
||||||
|
if(!hasSubmenu(menuItem)) {
|
||||||
|
triggerMenuItem(menuItem);
|
||||||
|
} else {
|
||||||
|
addClass(menuItem, "selected");
|
||||||
|
addClass(menuItem, "hover");
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
document.querySelectorAll('.menu .selected, .menu .hover')
|
||||||
|
.forEach(function(menuItem) {
|
||||||
|
deselectMenuItem(menuItem);
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.menu.popup')
|
||||||
|
.forEach(function(menu) {
|
||||||
|
menu.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('mouseover', function(event) {
|
||||||
|
var menuItem = getMenuItem(event.target);
|
||||||
|
var menu = getMenu(menuItem);
|
||||||
|
if(menu) {
|
||||||
|
var selected = menu.querySelectorAll('.selected, .hover');
|
||||||
|
if(isMenubar(menu)) {
|
||||||
|
if(selected.length > 0) {
|
||||||
|
selected.forEach(function(menuItem) {
|
||||||
|
if(selected != menuItem) {
|
||||||
|
deselectMenuItem(menuItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addClass(menuItem, 'selected');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(isMenuItemSelectable(menuItem)) {
|
||||||
|
selectMenuItem(menuItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('keydown', function(event) {
|
||||||
|
var allSelected = document.querySelectorAll('.menubar .selected, .menubar .hover,' +
|
||||||
|
'.menu.popup .selected, .menu.popup .hover');
|
||||||
|
if(allSelected.length == 0) return;
|
||||||
|
|
||||||
|
var selected = allSelected[allSelected.length - 1];
|
||||||
|
var outSelected, newSelected;
|
||||||
|
var isMenubarItem = isMenubar(getMenu(selected));
|
||||||
|
|
||||||
|
if(isMenubarItem && event.key == 'ArrowRight' ||
|
||||||
|
!isMenubarItem && event.key == 'ArrowDown') {
|
||||||
|
outSelected = selected;
|
||||||
|
newSelected = selected.nextElementSibling;
|
||||||
|
while(newSelected && !isMenuItemSelectable(newSelected)) {
|
||||||
|
newSelected = newSelected.nextElementSibling;
|
||||||
|
}
|
||||||
|
if(!newSelected) {
|
||||||
|
newSelected = outSelected.parentElement.firstElementChild;
|
||||||
|
}
|
||||||
|
} else if(isMenubarItem && event.key == 'ArrowLeft' ||
|
||||||
|
!isMenubarItem && event.key == 'ArrowUp') {
|
||||||
|
outSelected = selected;
|
||||||
|
newSelected = selected.previousElementSibling;
|
||||||
|
while(newSelected && !isMenuItemSelectable(newSelected)) {
|
||||||
|
newSelected = newSelected.previousElementSibling;
|
||||||
|
}
|
||||||
|
if(!newSelected) {
|
||||||
|
newSelected = outSelected.parentElement.lastElementChild;
|
||||||
|
}
|
||||||
|
} else if(!isMenubarItem && event.key == 'ArrowRight') {
|
||||||
|
if(hasSubmenu(selected)) {
|
||||||
|
selectMenuItem(selected.querySelector('li:first-child'));
|
||||||
|
} else {
|
||||||
|
outSelected = allSelected[0];
|
||||||
|
newSelected = outSelected.nextElementSibling;
|
||||||
|
if(!newSelected) {
|
||||||
|
newSelected = outSelected.parentElement.firstElementChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(!isMenubarItem && event.key == 'ArrowLeft') {
|
||||||
|
if(allSelected.length > 2) {
|
||||||
|
outSelected = selected;
|
||||||
|
} else {
|
||||||
|
outSelected = allSelected[0];
|
||||||
|
newSelected = outSelected.previousElementSibling;
|
||||||
|
if(!newSelected) {
|
||||||
|
newSelected = outSelected.parentElement.lastElementChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(isMenubarItem && event.key == 'ArrowDown') {
|
||||||
|
newSelected = selected.querySelector('li:first-child');
|
||||||
|
} else if(event.key == 'Enter') {
|
||||||
|
triggerMenuItem(selected);
|
||||||
|
} else if(event.key == 'Escape') {
|
||||||
|
outSelected = allSelected[0];
|
||||||
|
} else {
|
||||||
|
var withMnemonic = getMenu(selected).querySelector('.mnemonic-' + event.key);
|
||||||
|
if(withMnemonic) {
|
||||||
|
triggerMenuItem(withMnemonic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(outSelected) deselectMenuItem(outSelected);
|
||||||
|
if(newSelected) selectMenuItem(newSelected);
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Mnemonic behavior */
|
||||||
|
window.addEventListener('keydown', function(event) {
|
||||||
|
var withMnemonic;
|
||||||
|
if(event.altKey && event.key == 'Alt') {
|
||||||
|
addClass(document.body, 'mnemonic');
|
||||||
|
} else if(!isModal() && event.altKey && (withMnemonic =
|
||||||
|
document.querySelector('.menubar > .mnemonic-' + event.code))) {
|
||||||
|
triggerMenuItem(withMnemonic);
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
removeClass(document.body, 'mnemonic');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('keyup', function(event) {
|
||||||
|
if(event.key == 'Alt') {
|
||||||
|
removeClass(document.body, 'mnemonic');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME(emscripten): Should be implemnted in guihtmlcpp ?
|
||||||
|
class FileUploadHelper {
|
||||||
|
constructor() {
|
||||||
|
this.modalRoot = document.createElement("div");
|
||||||
|
addClass(this.modalRoot, "modal");
|
||||||
|
this.modalRoot.style.display = "none";
|
||||||
|
this.modalRoot.style.zIndex = 1000;
|
||||||
|
|
||||||
|
this.dialogRoot = document.createElement("div");
|
||||||
|
addClass(this.dialogRoot, "dialog");
|
||||||
|
this.modalRoot.appendChild(this.dialogRoot);
|
||||||
|
|
||||||
|
this.messageHeader = document.createElement("strong");
|
||||||
|
this.dialogRoot.appendChild(this.messageHeader);
|
||||||
|
|
||||||
|
this.descriptionParagraph = document.createElement("p");
|
||||||
|
this.dialogRoot.appendChild(this.descriptionParagraph);
|
||||||
|
|
||||||
|
this.currentFileListHeader = document.createElement("p");
|
||||||
|
this.currentFileListHeader.textContent = "Current uploaded files:";
|
||||||
|
this.dialogRoot.appendChild(this.currentFileListHeader);
|
||||||
|
|
||||||
|
this.currentFileList = document.createElement("div");
|
||||||
|
this.dialogRoot.appendChild(this.currentFileList);
|
||||||
|
|
||||||
|
this.fileInputContainer = document.createElement("div");
|
||||||
|
|
||||||
|
this.fileInputElement = document.createElement("input");
|
||||||
|
this.fileInputElement.setAttribute("type", "file");
|
||||||
|
this.fileInputElement.addEventListener("change", (ev)=> this.onFileInputChanged(ev));
|
||||||
|
this.fileInputContainer.appendChild(this.fileInputElement);
|
||||||
|
|
||||||
|
this.dialogRoot.appendChild(this.fileInputContainer);
|
||||||
|
|
||||||
|
this.buttonHolder = document.createElement("div");
|
||||||
|
addClass(this.buttonHolder, "buttons");
|
||||||
|
this.dialogRoot.appendChild(this.buttonHolder);
|
||||||
|
|
||||||
|
this.AddButton("OK", 0, false);
|
||||||
|
this.AddButton("Cancel", 1, true);
|
||||||
|
|
||||||
|
this.closeDialog();
|
||||||
|
|
||||||
|
document.querySelector("body").appendChild(this.modalRoot);
|
||||||
|
|
||||||
|
this.currentFilename = null;
|
||||||
|
|
||||||
|
// FIXME(emscripten): For debugging
|
||||||
|
this.title = "";
|
||||||
|
this.filename = "";
|
||||||
|
this.filters = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
document.querySelector("body").removeChild(this.modalRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddButton(label, response, isDefault) {
|
||||||
|
// FIXME(emscripten): implement
|
||||||
|
const buttonElem = document.createElement("div");
|
||||||
|
addClass(buttonElem, "button");
|
||||||
|
setLabelWithMnemonic(buttonElem, label);
|
||||||
|
if (isDefault) {
|
||||||
|
addClass(buttonElem, "default");
|
||||||
|
addClass(buttonElem, "selected");
|
||||||
|
}
|
||||||
|
buttonElem.addEventListener("click", () => {
|
||||||
|
this.closeDialog();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.buttonHolder.appendChild(buttonElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileEntries() {
|
||||||
|
const basePath = '/';
|
||||||
|
/** @type {Array<object} */
|
||||||
|
const nodes = FS.readdir(basePath);
|
||||||
|
const files = nodes.filter((nodename) => {
|
||||||
|
return FS.isFile(FS.lstat(basePath + nodename).mode);
|
||||||
|
}).map((filename) => {
|
||||||
|
return basePath + filename;
|
||||||
|
});
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateFileList() {
|
||||||
|
let filepaths = this.getFileEntries();
|
||||||
|
const listElem = document.createElement("ul");
|
||||||
|
for (let i = 0; i < filepaths.length; i++) {
|
||||||
|
const listitemElem = document.createElement("li");
|
||||||
|
const stat = FS.lstat(filepaths[i]);
|
||||||
|
const text = `"${filepaths[i]}" (${stat.size} bytes)`;
|
||||||
|
listitemElem.textContent = text;
|
||||||
|
listElem.appendChild(listitemElem);
|
||||||
|
}
|
||||||
|
return listElem;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFileList() {
|
||||||
|
this.currentFileList.innerHTML = "";
|
||||||
|
this.currentFileList.appendChild(this.generateFileList());
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileInputChanged(ev) {
|
||||||
|
const selectedFiles = ev.target.files;
|
||||||
|
if (selectedFiles.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedFile = selectedFiles[0];
|
||||||
|
const selectedFilename = selectedFile.name;
|
||||||
|
this.filename = selectedFilename;
|
||||||
|
this.currentFilename = selectedFilename;
|
||||||
|
|
||||||
|
// Prepare FileReader
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
const fileReaderReadAsArrayBufferPromise = new Promise((resolve, reject) => {
|
||||||
|
fileReader.addEventListener("load", (ev) => {
|
||||||
|
resolve(ev.target.result);
|
||||||
|
});
|
||||||
|
fileReader.addEventListener("abort", (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
fileReader.readAsArrayBuffer(selectedFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileReaderReadAsArrayBufferPromise
|
||||||
|
.then((arrayBuffer) => {
|
||||||
|
// Write selected file to FS
|
||||||
|
console.log(`Write uploaded file blob to filesystem. "${selectedFilename}" (${arrayBuffer.byteLength} bytes)`);
|
||||||
|
const u8array = new Uint8Array(arrayBuffer);
|
||||||
|
const fs = FS.open("/" + selectedFilename, "w");
|
||||||
|
FS.write(fs, u8array, 0, u8array.length, 0);
|
||||||
|
FS.close(fs);
|
||||||
|
|
||||||
|
// Update file list in dialog
|
||||||
|
this.updateFileList();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Error while fileReader.readAsArrayBuffer():", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog() {
|
||||||
|
this.updateFileList();
|
||||||
|
|
||||||
|
this.is_shown = true;
|
||||||
|
this.modalRoot.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDialog() {
|
||||||
|
this.is_shown = false;
|
||||||
|
this.modalRoot.style.display = "none";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME(emscripten): Workaround
|
||||||
|
function createFileUploadHelperInstance() {
|
||||||
|
return new FileUploadHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME(emscripten): Should be implemnted in guihtmlcpp ?
|
||||||
|
class FileDownloadHelper {
|
||||||
|
constructor() {
|
||||||
|
this.modalRoot = document.createElement("div");
|
||||||
|
addClass(this.modalRoot, "modal");
|
||||||
|
this.modalRoot.style.display = "none";
|
||||||
|
this.modalRoot.style.zIndex = 1000;
|
||||||
|
|
||||||
|
this.dialogRoot = document.createElement("div");
|
||||||
|
addClass(this.dialogRoot, "dialog");
|
||||||
|
this.modalRoot.appendChild(this.dialogRoot);
|
||||||
|
|
||||||
|
this.messageHeader = document.createElement("strong");
|
||||||
|
this.dialogRoot.appendChild(this.messageHeader);
|
||||||
|
|
||||||
|
this.descriptionParagraph = document.createElement("p");
|
||||||
|
this.dialogRoot.appendChild(this.descriptionParagraph);
|
||||||
|
|
||||||
|
this.buttonHolder = document.createElement("div");
|
||||||
|
addClass(this.buttonHolder, "buttons");
|
||||||
|
this.dialogRoot.appendChild(this.buttonHolder);
|
||||||
|
|
||||||
|
this.closeDialog();
|
||||||
|
|
||||||
|
document.querySelector("body").appendChild(this.modalRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
document.querySelector("body").removeChild(this.modalRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddButton(label, response, isDefault) {
|
||||||
|
// FIXME(emscripten): implement
|
||||||
|
const buttonElem = document.createElement("div");
|
||||||
|
addClass(buttonElem, "button");
|
||||||
|
setLabelWithMnemonic(buttonElem, label);
|
||||||
|
if (isDefault) {
|
||||||
|
addClass(buttonElem, "default");
|
||||||
|
addClass(buttonElem, "selected");
|
||||||
|
}
|
||||||
|
buttonElem.addEventListener("click", () => {
|
||||||
|
this.closeDialog();
|
||||||
|
this.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.buttonHolder.appendChild(buttonElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
createBlobURLFromArrayBuffer(arrayBuffer) {
|
||||||
|
const u8array = new Uint8Array(arrayBuffer);
|
||||||
|
let dataUrl = "data:application/octet-stream;base64,";
|
||||||
|
let binaryString = "";
|
||||||
|
for (let i = 0; i < u8array.length; i++) {
|
||||||
|
binaryString += String.fromCharCode(u8array[i]);
|
||||||
|
}
|
||||||
|
dataUrl += btoa(binaryString);
|
||||||
|
|
||||||
|
return dataUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareFile(filename) {
|
||||||
|
this.messageHeader.textContent = "Your file ready";
|
||||||
|
|
||||||
|
const stat = FS.lstat(filename);
|
||||||
|
const filesize = stat.size;
|
||||||
|
const fs = FS.open(filename, "r");
|
||||||
|
const readbuffer = new Uint8Array(filesize);
|
||||||
|
FS.read(fs, readbuffer, 0, filesize, 0);
|
||||||
|
FS.close(fs);
|
||||||
|
|
||||||
|
const blobURL = this.createBlobURLFromArrayBuffer(readbuffer.buffer);
|
||||||
|
|
||||||
|
this.descriptionParagraph.innerHTML = "";
|
||||||
|
const linkElem = document.createElement("a");
|
||||||
|
//let downloadfilename = "solvespace_browser-";
|
||||||
|
//downloadfilename += `${GetCurrentDateTimeString()}.slvs`;
|
||||||
|
let downloadfilename = filename;
|
||||||
|
linkElem.setAttribute("download", downloadfilename);
|
||||||
|
linkElem.setAttribute("href", blobURL);
|
||||||
|
// WORKAROUND: FIXME(emscripten)
|
||||||
|
linkElem.style.color = "lightblue";
|
||||||
|
linkElem.textContent = downloadfilename;
|
||||||
|
this.descriptionParagraph.appendChild(linkElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
showDialog() {
|
||||||
|
this.is_shown = true;
|
||||||
|
this.modalRoot.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDialog() {
|
||||||
|
this.is_shown = false;
|
||||||
|
this.modalRoot.style.display = "none";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function saveFileDone(filename, isSaveAs, isAutosave) {
|
||||||
|
console.log(`saveFileDone(${filename}, ${isSaveAs}, ${isAutosave})`);
|
||||||
|
if (isAutosave) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fileDownloadHelper = new FileDownloadHelper();
|
||||||
|
fileDownloadHelper.AddButton("OK", 0, true);
|
||||||
|
fileDownloadHelper.prepareFile(filename);
|
||||||
|
console.log(`Calling shoDialog()...`);
|
||||||
|
fileDownloadHelper.showDialog();
|
||||||
|
console.log(`shoDialog() finished.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ScrollbarHelper {
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} elementquery CSS query string for the element that has scrollbar.
|
||||||
|
*/
|
||||||
|
constructor(elementquery) {
|
||||||
|
this.target = document.querySelector(elementquery);
|
||||||
|
this.rangeMin = 0;
|
||||||
|
this.rangeMax = 0;
|
||||||
|
this.currentRatio = 0;
|
||||||
|
|
||||||
|
this.onScrollCallback = null;
|
||||||
|
this.onScrollCallbackTicking = false;
|
||||||
|
if (this.target) {
|
||||||
|
// console.log("addEventListner scroll");
|
||||||
|
this.target.parentElement.addEventListener('scroll', () => {
|
||||||
|
if (this.onScrollCallbackTicking) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
if (this.onScrollCallback) {
|
||||||
|
this.onScrollCallback();
|
||||||
|
}
|
||||||
|
this.onScrollCallbackTicking = false;
|
||||||
|
});
|
||||||
|
this.onScrollCallbackTicking = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} ratio how long against to the viewport height (1.0 to exact same as viewport's height)
|
||||||
|
*/
|
||||||
|
setScrollbarSize(ratio) {
|
||||||
|
// if (isNaN(ratio)) {
|
||||||
|
// console.warn(`setScrollbarSize(): ratio is Nan = ${ratio}`);
|
||||||
|
// }
|
||||||
|
// if (ratio < 0 || ratio > 1) {
|
||||||
|
// console.warn(`setScrollbarSize(): ratio is out of range 0-1 but ${ratio}`);
|
||||||
|
// }
|
||||||
|
// console.log(`ScrollbarHelper.setScrollbarSize(): ratio=${ratio}`);
|
||||||
|
this.target.style.height = `${100 * ratio}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getScrollbarPosition() {
|
||||||
|
const scrollbarElem = this.target.parentElement;
|
||||||
|
const scrollTopMin = 0;
|
||||||
|
const scrollTopMax = scrollbarElem.scrollHeight - scrollbarElem.clientHeight;
|
||||||
|
const ratioOnScrollbar = (scrollbarElem.scrollTop - scrollTopMin) / (scrollTopMax - scrollTopMin);
|
||||||
|
this.currentRatio = (scrollbarElem.scrollTop - scrollTopMin) / (scrollTopMax - scrollTopMin);
|
||||||
|
let pos = this.currentRatio * (this.rangeMax - this.pageSize - this.rangeMin) + this.rangeMin;
|
||||||
|
// console.log(`ScrollbarHelper.getScrollbarPosition(): ratio=${ratioOnScrollbar}, pos=${pos}, scrollTop=${scrollbarElem.scrollTop}, scrollTopMin=${scrollTopMin}, scrollTopMax=${scrollTopMax}, rangeMin=${this.rangeMin}, rangeMax=${this.rangeMax}, pageSize=${this.pageSize}`);
|
||||||
|
if (isNaN(pos)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} value in range of rangeMin and rangeMax
|
||||||
|
*/
|
||||||
|
setScrollbarPosition(position) {
|
||||||
|
const positionMin = this.rangeMin;
|
||||||
|
const positionMax = this.rangeMax - this.pageSize;
|
||||||
|
const currentPositionRatio = (position - positionMin) / (positionMax - positionMin);
|
||||||
|
|
||||||
|
const scrollbarElement = this.target.parentElement;
|
||||||
|
const scrollTopMin = 0;
|
||||||
|
const scrollTopMax = scrollbarElement.scrollHeight - scrollbarElement.clientHeight;
|
||||||
|
const scrollWidth = scrollTopMax - scrollTopMin;
|
||||||
|
const newScrollTop = currentPositionRatio * scrollWidth;
|
||||||
|
scrollbarElement.scrollTop = currentPositionRatio * scrollWidth;
|
||||||
|
|
||||||
|
// console.log(`ScrollbarHelper.setScrollbarPosition(): pos=${position}, currentPositionRatio=${currentPositionRatio}, calculated scrollTop=${newScrollTop}`);
|
||||||
|
|
||||||
|
if (false) {
|
||||||
|
// const ratio = (position - this.rangeMin) * ((this.rangeMax - this.pageSize) - this.rangeMin);
|
||||||
|
|
||||||
|
const scrollTopMin = 0;
|
||||||
|
const scrollTopMax = this.target.scrollHeight - this.target.clientHeight;
|
||||||
|
const scrollWidth = scrollTopMax - scrollTopMin;
|
||||||
|
const newScrollTop = ratio * scrollWidth;
|
||||||
|
// this.target.parentElement.scrollTop = ratio * scrollWidth;
|
||||||
|
this.target.scrollTop = ratio * scrollWidth;
|
||||||
|
|
||||||
|
console.log(`ScrollbarHelper.setScrollbarPosition(): pos=${position}, ratio=${ratio}, calculated scrollTop=${newScrollTop}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
setRange(min, max, pageSize) {
|
||||||
|
this.rangeMin = min;
|
||||||
|
this.rangeMax = max;
|
||||||
|
this.currentRatio = 0;
|
||||||
|
|
||||||
|
this.setPageSize(pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
setPageSize(pageSize) {
|
||||||
|
if (this.rangeMin == this.rangeMax) {
|
||||||
|
// console.log(`ScrollbarHelper::setPageSize(): size=${size}, but rangeMin == rangeMax`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.pageSize = pageSize;
|
||||||
|
const ratio = (this.rangeMax - this.rangeMin) / this.pageSize;
|
||||||
|
// console.log(`ScrollbarHelper::setPageSize(): pageSize=${pageSize}, ratio=${ratio}`);
|
||||||
|
this.setScrollbarSize(ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
setScrollbarEnabled(enabled) {
|
||||||
|
if (!enabled) {
|
||||||
|
this.target.style.height = "100%";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.ScrollbarHelper = ScrollbarHelper;
|
|
@ -516,6 +516,12 @@ static Platform::Path ResourcePath(const std::string &name) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#elif defined(__EMSCRIPTEN__)
|
||||||
|
|
||||||
|
static Platform::Path ResourcePath(const std::string &name) {
|
||||||
|
return Path::From("res/" + name);
|
||||||
|
}
|
||||||
|
|
||||||
#elif !defined(WIN32)
|
#elif !defined(WIN32)
|
||||||
|
|
||||||
# if defined(__linux__)
|
# if defined(__linux__)
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#ifndef SOLVESPACE_GL3SHADER_H
|
#ifndef SOLVESPACE_GL3SHADER_H
|
||||||
#define SOLVESPACE_GL3SHADER_H
|
#define SOLVESPACE_GL3SHADER_H
|
||||||
|
|
||||||
#if defined(WIN32)
|
#if defined(WIN32) || defined(__EMSCRIPTEN__)
|
||||||
# define GL_APICALL /*static linkage*/
|
# define GL_APICALL /*static linkage*/
|
||||||
# define GL_GLEXT_PROTOTYPES
|
# define GL_GLEXT_PROTOTYPES
|
||||||
# include <GLES2/gl2.h>
|
# include <GLES2/gl2.h>
|
||||||
|
|
|
@ -125,12 +125,8 @@ void SolveSpaceUI::Init() {
|
||||||
SetLocale(locale);
|
SetLocale(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
generateAllTimer = Platform::CreateTimer();
|
refreshTimer = Platform::CreateTimer();
|
||||||
generateAllTimer->onTimeout = std::bind(&SolveSpaceUI::GenerateAll, &SS, Generate::DIRTY,
|
refreshTimer->onTimeout = std::bind(&SolveSpaceUI::Refresh, &SS);
|
||||||
/*andFindFree=*/false, /*genForBBox=*/false);
|
|
||||||
|
|
||||||
showTWTimer = Platform::CreateTimer();
|
|
||||||
showTWTimer->onTimeout = std::bind(&TextWindow::Show, &TW);
|
|
||||||
|
|
||||||
autosaveTimer = Platform::CreateTimer();
|
autosaveTimer = Platform::CreateTimer();
|
||||||
autosaveTimer->onTimeout = std::bind(&SolveSpaceUI::Autosave, &SS);
|
autosaveTimer->onTimeout = std::bind(&SolveSpaceUI::Autosave, &SS);
|
||||||
|
@ -302,12 +298,26 @@ void SolveSpaceUI::Exit() {
|
||||||
Platform::ExitGui();
|
Platform::ExitGui();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SolveSpaceUI::Refresh() {
|
||||||
|
// generateAll must happen bfore updating displays
|
||||||
|
if(scheduledGenerateAll) {
|
||||||
|
GenerateAll(Generate::DIRTY, /*andFindFree=*/false, /*genForBBox=*/false);
|
||||||
|
scheduledGenerateAll = false;
|
||||||
|
}
|
||||||
|
if(scheduledShowTW) {
|
||||||
|
TW.Show();
|
||||||
|
scheduledShowTW = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SolveSpaceUI::ScheduleGenerateAll() {
|
void SolveSpaceUI::ScheduleGenerateAll() {
|
||||||
generateAllTimer->RunAfterProcessingEvents();
|
scheduledGenerateAll = true;
|
||||||
|
refreshTimer->RunAfterProcessingEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SolveSpaceUI::ScheduleShowTW() {
|
void SolveSpaceUI::ScheduleShowTW() {
|
||||||
showTWTimer->RunAfterProcessingEvents();
|
scheduledShowTW = true;
|
||||||
|
refreshTimer->RunAfterProcessingEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SolveSpaceUI::ScheduleAutosave() {
|
void SolveSpaceUI::ScheduleAutosave() {
|
||||||
|
@ -550,12 +560,18 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) {
|
||||||
|
|
||||||
if(saveAs || saveFile.IsEmpty()) {
|
if(saveAs || saveFile.IsEmpty()) {
|
||||||
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(GW.window);
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(GW.window);
|
||||||
|
// FIXME(emscripten):
|
||||||
|
dbp("Calling AddFilter()...");
|
||||||
dialog->AddFilter(C_("file-type", "SolveSpace models"), { SKETCH_EXT });
|
dialog->AddFilter(C_("file-type", "SolveSpace models"), { SKETCH_EXT });
|
||||||
|
dbp("Calling ThawChoices()...");
|
||||||
dialog->ThawChoices(settings, "Sketch");
|
dialog->ThawChoices(settings, "Sketch");
|
||||||
if(!newSaveFile.IsEmpty()) {
|
if(!newSaveFile.IsEmpty()) {
|
||||||
|
dbp("Calling SetFilename()...");
|
||||||
dialog->SetFilename(newSaveFile);
|
dialog->SetFilename(newSaveFile);
|
||||||
}
|
}
|
||||||
|
dbp("Calling RunModal()...");
|
||||||
if(dialog->RunModal()) {
|
if(dialog->RunModal()) {
|
||||||
|
dbp("Calling FreezeChoices()...");
|
||||||
dialog->FreezeChoices(settings, "Sketch");
|
dialog->FreezeChoices(settings, "Sketch");
|
||||||
newSaveFile = dialog->GetFilename();
|
newSaveFile = dialog->GetFilename();
|
||||||
} else {
|
} else {
|
||||||
|
@ -568,6 +584,9 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) {
|
||||||
RemoveAutosave();
|
RemoveAutosave();
|
||||||
saveFile = newSaveFile;
|
saveFile = newSaveFile;
|
||||||
unsaved = false;
|
unsaved = false;
|
||||||
|
if (this->OnSaveFinished) {
|
||||||
|
this->OnSaveFinished(newSaveFile, saveAs, false);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -579,7 +598,11 @@ void SolveSpaceUI::Autosave()
|
||||||
ScheduleAutosave();
|
ScheduleAutosave();
|
||||||
|
|
||||||
if(!saveFile.IsEmpty() && unsaved) {
|
if(!saveFile.IsEmpty() && unsaved) {
|
||||||
SaveToFile(saveFile.WithExtension(BACKUP_EXT));
|
Platform::Path saveFileName = saveFile.WithExtension(BACKUP_EXT);
|
||||||
|
SaveToFile(saveFileName);
|
||||||
|
if (this->OnSaveFinished) {
|
||||||
|
this->OnSaveFinished(saveFileName, false, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -679,6 +702,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
if(dialog->RunModal()) {
|
if(dialog->RunModal()) {
|
||||||
dialog->FreezeChoices(settings, "ExportImage");
|
dialog->FreezeChoices(settings, "ExportImage");
|
||||||
SS.ExportAsPngTo(dialog->GetFilename());
|
SS.ExportAsPngTo(dialog->GetFilename());
|
||||||
|
if (SS.OnSaveFinished) {
|
||||||
|
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -704,6 +730,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe=*/false);
|
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe=*/false);
|
||||||
|
if (SS.OnSaveFinished) {
|
||||||
|
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -716,6 +745,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
dialog->FreezeChoices(settings, "ExportWireframe");
|
dialog->FreezeChoices(settings, "ExportWireframe");
|
||||||
|
|
||||||
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe*/true);
|
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe*/true);
|
||||||
|
if (SS.OnSaveFinished) {
|
||||||
|
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -728,6 +760,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
dialog->FreezeChoices(settings, "ExportSection");
|
dialog->FreezeChoices(settings, "ExportSection");
|
||||||
|
|
||||||
SS.ExportSectionTo(dialog->GetFilename());
|
SS.ExportSectionTo(dialog->GetFilename());
|
||||||
|
if (SS.OnSaveFinished) {
|
||||||
|
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -740,6 +775,10 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
dialog->FreezeChoices(settings, "ExportMesh");
|
dialog->FreezeChoices(settings, "ExportMesh");
|
||||||
|
|
||||||
SS.ExportMeshTo(dialog->GetFilename());
|
SS.ExportMeshTo(dialog->GetFilename());
|
||||||
|
if (SS.OnSaveFinished) {
|
||||||
|
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -753,6 +792,9 @@ void SolveSpaceUI::MenuFile(Command id) {
|
||||||
|
|
||||||
StepFileWriter sfw = {};
|
StepFileWriter sfw = {};
|
||||||
sfw.ExportSurfacesTo(dialog->GetFilename());
|
sfw.ExportSurfacesTo(dialog->GetFilename());
|
||||||
|
if (SS.OnSaveFinished) {
|
||||||
|
SS.OnSaveFinished(dialog->GetFilename(), false, false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -683,6 +683,7 @@ public:
|
||||||
void NewFile();
|
void NewFile();
|
||||||
bool SaveToFile(const Platform::Path &filename);
|
bool SaveToFile(const Platform::Path &filename);
|
||||||
bool LoadAutosaveFor(const Platform::Path &filename);
|
bool LoadAutosaveFor(const Platform::Path &filename);
|
||||||
|
std::function<void(const Platform::Path &filename, bool is_saveAs, bool is_autosave)> OnSaveFinished;
|
||||||
bool LoadFromFile(const Platform::Path &filename, bool canCancel = false);
|
bool LoadFromFile(const Platform::Path &filename, bool canCancel = false);
|
||||||
void UpgradeLegacyData();
|
void UpgradeLegacyData();
|
||||||
bool LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le,
|
bool LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le,
|
||||||
|
@ -793,9 +794,11 @@ public:
|
||||||
// the sketch!
|
// the sketch!
|
||||||
bool allConsistent;
|
bool allConsistent;
|
||||||
|
|
||||||
Platform::TimerRef showTWTimer;
|
bool scheduledGenerateAll;
|
||||||
Platform::TimerRef generateAllTimer;
|
bool scheduledShowTW;
|
||||||
|
Platform::TimerRef refreshTimer;
|
||||||
Platform::TimerRef autosaveTimer;
|
Platform::TimerRef autosaveTimer;
|
||||||
|
void Refresh();
|
||||||
void ScheduleShowTW();
|
void ScheduleShowTW();
|
||||||
void ScheduleGenerateAll();
|
void ScheduleGenerateAll();
|
||||||
void ScheduleAutosave();
|
void ScheduleAutosave();
|
||||||
|
|
|
@ -0,0 +1,614 @@
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Anything involving NURBS shells (i.e., shells); except
|
||||||
|
// for the real math, which is in ratpoly.cpp.
|
||||||
|
//
|
||||||
|
// Copyright 2008-2013 Jonathan Westhues.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
#include "../solvespace.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
hSCurve hc;
|
||||||
|
hSSurface hs;
|
||||||
|
} TrimLine;
|
||||||
|
|
||||||
|
|
||||||
|
void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, RgbaColor color)
|
||||||
|
{
|
||||||
|
// Make the extrusion direction consistent with respect to the normal
|
||||||
|
// of the sketch we're extruding.
|
||||||
|
if((t0.Minus(t1)).Dot(sbls->normal) < 0) {
|
||||||
|
swap(t0, t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a coordinate system to contain the original sketch, and get
|
||||||
|
// a bounding box in that csys
|
||||||
|
Vector n = sbls->normal.ScaledBy(-1);
|
||||||
|
Vector u = n.Normal(0), v = n.Normal(1);
|
||||||
|
Vector orig = sbls->point;
|
||||||
|
double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
|
||||||
|
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
||||||
|
double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;
|
||||||
|
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
||||||
|
// and now fix things up so that all u and v lie between 0 and 1
|
||||||
|
orig = orig.Plus(u.ScaledBy(umin));
|
||||||
|
orig = orig.Plus(v.ScaledBy(vmin));
|
||||||
|
u = u.ScaledBy(umax - umin);
|
||||||
|
v = v.ScaledBy(vmax - vmin);
|
||||||
|
|
||||||
|
// So we can now generate the top and bottom surfaces of the extrusion,
|
||||||
|
// planes within a translated (and maybe mirrored) version of that csys.
|
||||||
|
SSurface s0, s1;
|
||||||
|
s0 = SSurface::FromPlane(orig.Plus(t0), u, v);
|
||||||
|
s0.color = color;
|
||||||
|
s1 = SSurface::FromPlane(orig.Plus(t1).Plus(u), u.ScaledBy(-1), v);
|
||||||
|
s1.color = color;
|
||||||
|
hSSurface hs0 = surface.AddAndAssignId(&s0),
|
||||||
|
hs1 = surface.AddAndAssignId(&s1);
|
||||||
|
|
||||||
|
// Now go through the input curves. For each one, generate its surface
|
||||||
|
// of extrusion, its two translated trim curves, and one trim line. We
|
||||||
|
// go through by loops so that we can assign the lines correctly.
|
||||||
|
SBezierLoop *sbl;
|
||||||
|
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
||||||
|
SBezier *sb;
|
||||||
|
List<TrimLine> trimLines = {};
|
||||||
|
|
||||||
|
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
||||||
|
// Generate the surface of extrusion of this curve, and add
|
||||||
|
// it to the list
|
||||||
|
SSurface ss = SSurface::FromExtrusionOf(sb, t0, t1);
|
||||||
|
ss.color = color;
|
||||||
|
hSSurface hsext = surface.AddAndAssignId(&ss);
|
||||||
|
|
||||||
|
// Translate the curve by t0 and t1 to produce two trim curves
|
||||||
|
SCurve sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY, 1.0);
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts));
|
||||||
|
sc.surfA = hs0;
|
||||||
|
sc.surfB = hsext;
|
||||||
|
hSCurve hc0 = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY, 1.0);
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts));
|
||||||
|
sc.surfA = hs1;
|
||||||
|
sc.surfB = hsext;
|
||||||
|
hSCurve hc1 = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
STrimBy stb0, stb1;
|
||||||
|
// The translated curves trim the flat top and bottom surfaces.
|
||||||
|
stb0 = STrimBy::EntireCurve(this, hc0, /*backwards=*/false);
|
||||||
|
stb1 = STrimBy::EntireCurve(this, hc1, /*backwards=*/true);
|
||||||
|
(surface.FindById(hs0))->trim.Add(&stb0);
|
||||||
|
(surface.FindById(hs1))->trim.Add(&stb1);
|
||||||
|
|
||||||
|
// The translated curves also trim the surface of extrusion.
|
||||||
|
stb0 = STrimBy::EntireCurve(this, hc0, /*backwards=*/true);
|
||||||
|
stb1 = STrimBy::EntireCurve(this, hc1, /*backwards=*/false);
|
||||||
|
(surface.FindById(hsext))->trim.Add(&stb0);
|
||||||
|
(surface.FindById(hsext))->trim.Add(&stb1);
|
||||||
|
|
||||||
|
// And form the trim line
|
||||||
|
Vector pt = sb->Finish();
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = SBezier::From(pt.Plus(t0), pt.Plus(t1));
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts));
|
||||||
|
hSCurve hl = curve.AddAndAssignId(&sc);
|
||||||
|
// save this for later
|
||||||
|
TrimLine tl;
|
||||||
|
tl.hc = hl;
|
||||||
|
tl.hs = hsext;
|
||||||
|
trimLines.Add(&tl);
|
||||||
|
}
|
||||||
|
|
||||||
|
int i;
|
||||||
|
for(i = 0; i < trimLines.n; i++) {
|
||||||
|
TrimLine *tl = &(trimLines[i]);
|
||||||
|
SSurface *ss = surface.FindById(tl->hs);
|
||||||
|
|
||||||
|
TrimLine *tlp = &(trimLines[WRAP(i-1, trimLines.n)]);
|
||||||
|
|
||||||
|
STrimBy stb;
|
||||||
|
stb = STrimBy::EntireCurve(this, tl->hc, /*backwards=*/true);
|
||||||
|
ss->trim.Add(&stb);
|
||||||
|
stb = STrimBy::EntireCurve(this, tlp->hc, /*backwards=*/false);
|
||||||
|
ss->trim.Add(&stb);
|
||||||
|
|
||||||
|
(curve.FindById(tl->hc))->surfA = ss->h;
|
||||||
|
(curve.FindById(tlp->hc))->surfB = ss->h;
|
||||||
|
}
|
||||||
|
trimLines.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SShell::CheckNormalAxisRelationship(SBezierLoopSet *sbls, Vector pt, Vector axis, double da, double dx)
|
||||||
|
// Check that the direction of revolution/extrusion ends up parallel to the normal of
|
||||||
|
// the sketch, on the side of the axis where the sketch is.
|
||||||
|
{
|
||||||
|
SBezierLoop *sbl;
|
||||||
|
Vector pto;
|
||||||
|
double md = VERY_NEGATIVE;
|
||||||
|
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
||||||
|
SBezier *sb;
|
||||||
|
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
||||||
|
// Choose the point farthest from the axis; we'll get garbage
|
||||||
|
// if we choose a point that lies on the axis, for example.
|
||||||
|
// (And our surface will be self-intersecting if the sketch
|
||||||
|
// spans the axis, so don't worry about that.)
|
||||||
|
for(int i = 0; i <= sb->deg; i++) {
|
||||||
|
Vector p = sb->ctrl[i];
|
||||||
|
double d = p.DistanceToLine(pt, axis);
|
||||||
|
if(d > md) {
|
||||||
|
md = d;
|
||||||
|
pto = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vector ptc = pto.ClosestPointOnLine(pt, axis),
|
||||||
|
up = axis.Cross(pto.Minus(ptc)).ScaledBy(da),
|
||||||
|
vp = up.Plus(axis.ScaledBy(dx));
|
||||||
|
|
||||||
|
return (vp.Dot(sbls->normal) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sketch must not contain the axis of revolution as a non-construction line for helix
|
||||||
|
void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis,
|
||||||
|
RgbaColor color, Group *group, double angles,
|
||||||
|
double anglef, double dists, double distf) {
|
||||||
|
int i0 = surface.n; // number of pre-existing surfaces
|
||||||
|
SBezierLoop *sbl;
|
||||||
|
// for testing - hard code the axial distance, and number of sections.
|
||||||
|
// distance will need to be parameters in the future.
|
||||||
|
double dist = distf - dists;
|
||||||
|
int sections = (int)(fabs(anglef - angles) / (PI / 2) + 1);
|
||||||
|
double wedge = (anglef - angles) / sections;
|
||||||
|
int startMapping = Group::REMAP_LATHE_START, endMapping = Group::REMAP_LATHE_END;
|
||||||
|
|
||||||
|
if(CheckNormalAxisRelationship(sbls, pt, axis, anglef-angles, distf-dists)) {
|
||||||
|
swap(angles, anglef);
|
||||||
|
swap(dists, distf);
|
||||||
|
dist = -dist;
|
||||||
|
wedge = -wedge;
|
||||||
|
swap(startMapping, endMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a coordinate system to contain the original sketch, and get
|
||||||
|
// a bounding box in that csys
|
||||||
|
Vector n = sbls->normal.ScaledBy(-1);
|
||||||
|
Vector u = n.Normal(0), v = n.Normal(1);
|
||||||
|
Vector orig = sbls->point;
|
||||||
|
double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
|
||||||
|
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
||||||
|
double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;
|
||||||
|
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
||||||
|
// and now fix things up so that all u and v lie between 0 and 1
|
||||||
|
orig = orig.Plus(u.ScaledBy(umin));
|
||||||
|
orig = orig.Plus(v.ScaledBy(vmin));
|
||||||
|
u = u.ScaledBy(umax - umin);
|
||||||
|
v = v.ScaledBy(vmax - vmin);
|
||||||
|
|
||||||
|
// So we can now generate the end caps of the extrusion within
|
||||||
|
// a translated and rotated (and maybe mirrored) version of that csys.
|
||||||
|
SSurface s0, s1;
|
||||||
|
s0 = SSurface::FromPlane(orig.RotatedAbout(pt, axis, angles).Plus(axis.ScaledBy(dists)),
|
||||||
|
u.RotatedAbout(axis, angles), v.RotatedAbout(axis, angles));
|
||||||
|
s0.color = color;
|
||||||
|
|
||||||
|
hEntity face0 = group->Remap(Entity::NO_ENTITY, startMapping);
|
||||||
|
s0.face = face0.v;
|
||||||
|
|
||||||
|
s1 = SSurface::FromPlane(
|
||||||
|
orig.Plus(u).RotatedAbout(pt, axis, anglef).Plus(axis.ScaledBy(distf)),
|
||||||
|
u.ScaledBy(-1).RotatedAbout(axis, anglef), v.RotatedAbout(axis, anglef));
|
||||||
|
s1.color = color;
|
||||||
|
|
||||||
|
hEntity face1 = group->Remap(Entity::NO_ENTITY, endMapping);
|
||||||
|
s1.face = face1.v;
|
||||||
|
|
||||||
|
hSSurface hs0 = surface.AddAndAssignId(&s0);
|
||||||
|
hSSurface hs1 = surface.AddAndAssignId(&s1);
|
||||||
|
|
||||||
|
// Now we actually build and trim the swept surfaces. One loop at a time.
|
||||||
|
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
||||||
|
int i, j;
|
||||||
|
SBezier *sb;
|
||||||
|
List<std::vector<hSSurface>> hsl = {};
|
||||||
|
|
||||||
|
// This is where all the NURBS are created and Remapped to the generating curve
|
||||||
|
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
||||||
|
std::vector<hSSurface> revs(sections);
|
||||||
|
for(j = 0; j < sections; j++) {
|
||||||
|
if((dist == 0) && sb->deg == 1 &&
|
||||||
|
(sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS &&
|
||||||
|
(sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS) {
|
||||||
|
// This is a line on the axis of revolution; it does
|
||||||
|
// not contribute a surface.
|
||||||
|
revs[j].v = 0;
|
||||||
|
} else {
|
||||||
|
SSurface ss = SSurface::FromRevolutionOf(
|
||||||
|
sb, pt, axis, angles + (wedge)*j, angles + (wedge) * (j + 1),
|
||||||
|
dists + j * dist / sections, dists + (j + 1) * dist / sections);
|
||||||
|
ss.color = color;
|
||||||
|
if(sb->entity != 0) {
|
||||||
|
hEntity he;
|
||||||
|
he.v = sb->entity;
|
||||||
|
hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE);
|
||||||
|
if(SK.entity.FindByIdNoOops(hface) != NULL) {
|
||||||
|
ss.face = hface.v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
revs[j] = surface.AddAndAssignId(&ss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hsl.Add(&revs);
|
||||||
|
}
|
||||||
|
// Still the same loop. Need to create trim curves
|
||||||
|
for(i = 0; i < sbl->l.n; i++) {
|
||||||
|
std::vector<hSSurface> revs = hsl[i], revsp = hsl[WRAP(i - 1, sbl->l.n)];
|
||||||
|
|
||||||
|
sb = &(sbl->l[i]);
|
||||||
|
|
||||||
|
// we will need the grid t-values for this entire row of surfaces
|
||||||
|
List<double> t_values;
|
||||||
|
t_values = {};
|
||||||
|
if (revs[0].v) {
|
||||||
|
double ps = 0.0;
|
||||||
|
t_values.Add(&ps);
|
||||||
|
(surface.FindById(revs[0]))->MakeTriangulationGridInto(
|
||||||
|
&t_values, 0.0, 1.0, true, 0);
|
||||||
|
}
|
||||||
|
// we generate one more curve than we did surfaces
|
||||||
|
for(j = 0; j <= sections; j++) {
|
||||||
|
SCurve sc;
|
||||||
|
Quaternion qs = Quaternion::From(axis, angles + wedge * j);
|
||||||
|
// we want Q*(x - p) + p = Q*x + (p - Q*p)
|
||||||
|
Vector ts =
|
||||||
|
pt.Minus(qs.Rotate(pt)).Plus(axis.ScaledBy(dists + j * dist / sections));
|
||||||
|
|
||||||
|
// If this input curve generated a surface, then trim that
|
||||||
|
// surface with the rotated version of the input curve.
|
||||||
|
if(revs[0].v) { // not d[j] because crash on j==sections
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = sb->TransformedBy(ts, qs, 1.0);
|
||||||
|
// make the PWL for the curve based on t value list
|
||||||
|
for(int x = 0; x < t_values.n; x++) {
|
||||||
|
SCurvePt scpt;
|
||||||
|
scpt.tag = 0;
|
||||||
|
scpt.p = sc.exact.PointAt(t_values[x]);
|
||||||
|
scpt.vertex = (x == 0) || (x == (t_values.n - 1));
|
||||||
|
sc.pts.Add(&scpt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the surfaces already exists so trim with this curve
|
||||||
|
if(j < sections) {
|
||||||
|
sc.surfA = revs[j];
|
||||||
|
} else {
|
||||||
|
sc.surfA = hs1; // end cap
|
||||||
|
}
|
||||||
|
|
||||||
|
if(j > 0) {
|
||||||
|
sc.surfB = revs[j - 1];
|
||||||
|
} else {
|
||||||
|
sc.surfB = hs0; // staring cap
|
||||||
|
}
|
||||||
|
|
||||||
|
hSCurve hcb = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
STrimBy stb;
|
||||||
|
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);
|
||||||
|
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
||||||
|
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);
|
||||||
|
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
||||||
|
} else if(j == 0) { // curve was on the rotation axis and is shared by the end caps.
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = sb->TransformedBy(ts, qs, 1.0);
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts));
|
||||||
|
sc.surfA = hs1; // end cap
|
||||||
|
sc.surfB = hs0; // staring cap
|
||||||
|
hSCurve hcb = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
STrimBy stb;
|
||||||
|
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);
|
||||||
|
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
||||||
|
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);
|
||||||
|
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And if this input curve and the one after it both generated
|
||||||
|
// surfaces, then trim both of those by the appropriate
|
||||||
|
// curve based on the control points.
|
||||||
|
if((j < sections) && revs[j].v && revsp[j].v) {
|
||||||
|
SSurface *ss = surface.FindById(revs[j]);
|
||||||
|
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = SBezier::From(ss->ctrl[0][0], ss->ctrl[0][1], ss->ctrl[0][2]);
|
||||||
|
sc.exact.weight[1] = ss->weight[0][1];
|
||||||
|
double max_dt = 0.5;
|
||||||
|
if (sc.exact.deg > 1) max_dt = 0.125;
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts), 0.0, max_dt);
|
||||||
|
sc.surfA = revs[j];
|
||||||
|
sc.surfB = revsp[j];
|
||||||
|
|
||||||
|
hSCurve hcc = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
STrimBy stb;
|
||||||
|
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false);
|
||||||
|
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
||||||
|
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true);
|
||||||
|
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t_values.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
hsl.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dist == 0) {
|
||||||
|
MakeFirstOrderRevolvedSurfaces(pt, axis, i0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color,
|
||||||
|
Group *group) {
|
||||||
|
int i0 = surface.n; // number of pre-existing surfaces
|
||||||
|
SBezierLoop *sbl;
|
||||||
|
|
||||||
|
if(CheckNormalAxisRelationship(sbls, pt, axis, 1.0, 0.0)) {
|
||||||
|
axis = axis.ScaledBy(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we actually build and trim the surfaces.
|
||||||
|
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
||||||
|
int i, j;
|
||||||
|
SBezier *sb;
|
||||||
|
List<std::vector<hSSurface>> hsl = {};
|
||||||
|
|
||||||
|
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
||||||
|
std::vector<hSSurface> revs(4);
|
||||||
|
for(j = 0; j < 4; j++) {
|
||||||
|
if(sb->deg == 1 &&
|
||||||
|
(sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS &&
|
||||||
|
(sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS)
|
||||||
|
{
|
||||||
|
// This is a line on the axis of revolution; it does
|
||||||
|
// not contribute a surface.
|
||||||
|
revs[j].v = 0;
|
||||||
|
} else {
|
||||||
|
SSurface ss = SSurface::FromRevolutionOf(sb, pt, axis, (PI / 2) * j,
|
||||||
|
(PI / 2) * (j + 1), 0.0, 0.0);
|
||||||
|
ss.color = color;
|
||||||
|
if(sb->entity != 0) {
|
||||||
|
hEntity he;
|
||||||
|
he.v = sb->entity;
|
||||||
|
hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE);
|
||||||
|
if(SK.entity.FindByIdNoOops(hface) != NULL) {
|
||||||
|
ss.face = hface.v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
revs[j] = surface.AddAndAssignId(&ss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hsl.Add(&revs);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = 0; i < sbl->l.n; i++) {
|
||||||
|
std::vector<hSSurface> revs = hsl[i],
|
||||||
|
revsp = hsl[WRAP(i-1, sbl->l.n)];
|
||||||
|
|
||||||
|
sb = &(sbl->l[i]);
|
||||||
|
|
||||||
|
for(j = 0; j < 4; j++) {
|
||||||
|
SCurve sc;
|
||||||
|
Quaternion qs = Quaternion::From(axis, (PI/2)*j);
|
||||||
|
// we want Q*(x - p) + p = Q*x + (p - Q*p)
|
||||||
|
Vector ts = pt.Minus(qs.Rotate(pt));
|
||||||
|
|
||||||
|
// If this input curve generate a surface, then trim that
|
||||||
|
// surface with the rotated version of the input curve.
|
||||||
|
if(revs[j].v) {
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = sb->TransformedBy(ts, qs, 1.0);
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts));
|
||||||
|
sc.surfA = revs[j];
|
||||||
|
sc.surfB = revs[WRAP(j-1, 4)];
|
||||||
|
|
||||||
|
hSCurve hcb = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
STrimBy stb;
|
||||||
|
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);
|
||||||
|
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
||||||
|
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);
|
||||||
|
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And if this input curve and the one after it both generated
|
||||||
|
// surfaces, then trim both of those by the appropriate
|
||||||
|
// circle.
|
||||||
|
if(revs[j].v && revsp[j].v) {
|
||||||
|
SSurface *ss = surface.FindById(revs[j]);
|
||||||
|
|
||||||
|
sc = {};
|
||||||
|
sc.isExact = true;
|
||||||
|
sc.exact = SBezier::From(ss->ctrl[0][0],
|
||||||
|
ss->ctrl[0][1],
|
||||||
|
ss->ctrl[0][2]);
|
||||||
|
sc.exact.weight[1] = ss->weight[0][1];
|
||||||
|
(sc.exact).MakePwlInto(&(sc.pts));
|
||||||
|
sc.surfA = revs[j];
|
||||||
|
sc.surfB = revsp[j];
|
||||||
|
|
||||||
|
hSCurve hcc = curve.AddAndAssignId(&sc);
|
||||||
|
|
||||||
|
STrimBy stb;
|
||||||
|
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false);
|
||||||
|
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
||||||
|
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true);
|
||||||
|
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hsl.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeFirstOrderRevolvedSurfaces(pt, axis, i0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::MakeFirstOrderRevolvedSurfaces(Vector pt, Vector axis, int i0) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = i0; i < surface.n; i++) {
|
||||||
|
SSurface *srf = &(surface[i]);
|
||||||
|
|
||||||
|
// Revolution of a line; this is potentially a plane, which we can
|
||||||
|
// rewrite to have degree (1, 1).
|
||||||
|
if(srf->degm == 1 && srf->degn == 2) {
|
||||||
|
// close start, far start, far finish
|
||||||
|
Vector cs, fs, ff;
|
||||||
|
double d0, d1;
|
||||||
|
d0 = (srf->ctrl[0][0]).DistanceToLine(pt, axis);
|
||||||
|
d1 = (srf->ctrl[1][0]).DistanceToLine(pt, axis);
|
||||||
|
|
||||||
|
if(d0 > d1) {
|
||||||
|
cs = srf->ctrl[1][0];
|
||||||
|
fs = srf->ctrl[0][0];
|
||||||
|
ff = srf->ctrl[0][2];
|
||||||
|
} else {
|
||||||
|
cs = srf->ctrl[0][0];
|
||||||
|
fs = srf->ctrl[1][0];
|
||||||
|
ff = srf->ctrl[1][2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// origin close, origin far
|
||||||
|
Vector oc = cs.ClosestPointOnLine(pt, axis),
|
||||||
|
of = fs.ClosestPointOnLine(pt, axis);
|
||||||
|
|
||||||
|
if(oc.Equals(of)) {
|
||||||
|
// This is a plane, not a (non-degenerate) cone.
|
||||||
|
Vector oldn = srf->NormalAt(0.5, 0.5);
|
||||||
|
|
||||||
|
Vector u = fs.Minus(of), v;
|
||||||
|
|
||||||
|
v = (axis.Cross(u)).WithMagnitude(1);
|
||||||
|
|
||||||
|
double vm = (ff.Minus(of)).Dot(v);
|
||||||
|
v = v.ScaledBy(vm);
|
||||||
|
|
||||||
|
srf->degm = 1;
|
||||||
|
srf->degn = 1;
|
||||||
|
srf->ctrl[0][0] = of;
|
||||||
|
srf->ctrl[0][1] = of.Plus(u);
|
||||||
|
srf->ctrl[1][0] = of.Plus(v);
|
||||||
|
srf->ctrl[1][1] = of.Plus(u).Plus(v);
|
||||||
|
srf->weight[0][0] = 1;
|
||||||
|
srf->weight[0][1] = 1;
|
||||||
|
srf->weight[1][0] = 1;
|
||||||
|
srf->weight[1][1] = 1;
|
||||||
|
|
||||||
|
if(oldn.Dot(srf->NormalAt(0.5, 0.5)) < 0) {
|
||||||
|
swap(srf->ctrl[0][0], srf->ctrl[1][0]);
|
||||||
|
swap(srf->ctrl[0][1], srf->ctrl[1][1]);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fabs(d0 - d1) < LENGTH_EPS) {
|
||||||
|
// This is a cylinder; so transpose it so that we'll recognize
|
||||||
|
// it as a surface of extrusion.
|
||||||
|
SSurface sn = *srf;
|
||||||
|
|
||||||
|
// Transposing u and v flips the normal, so reverse u to
|
||||||
|
// flip it again and put it back where we started.
|
||||||
|
sn.degm = 2;
|
||||||
|
sn.degn = 1;
|
||||||
|
int dm, dn;
|
||||||
|
for(dm = 0; dm <= 1; dm++) {
|
||||||
|
for(dn = 0; dn <= 2; dn++) {
|
||||||
|
sn.ctrl [dn][dm] = srf->ctrl [1-dm][dn];
|
||||||
|
sn.weight[dn][dm] = srf->weight[1-dm][dn];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*srf = sn;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::MakeFromCopyOf(SShell *a) {
|
||||||
|
ssassert(this != a, "Can't make from copy of self");
|
||||||
|
MakeFromTransformationOf(a,
|
||||||
|
Vector::From(0, 0, 0), Quaternion::IDENTITY, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::MakeFromTransformationOf(SShell *a,
|
||||||
|
Vector t, Quaternion q, double scale)
|
||||||
|
{
|
||||||
|
booleanFailed = false;
|
||||||
|
surface.ReserveMore(a->surface.n);
|
||||||
|
for(SSurface &s : a->surface) {
|
||||||
|
SSurface n;
|
||||||
|
n = SSurface::FromTransformationOf(&s, t, q, scale, /*includingTrims=*/true);
|
||||||
|
surface.Add(&n); // keeping the old ID
|
||||||
|
}
|
||||||
|
|
||||||
|
curve.ReserveMore(a->curve.n);
|
||||||
|
for(SCurve &c : a->curve) {
|
||||||
|
SCurve n;
|
||||||
|
n = SCurve::FromTransformationOf(&c, t, q, scale);
|
||||||
|
curve.Add(&n); // keeping the old ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::MakeEdgesInto(SEdgeList *sel) {
|
||||||
|
for(SSurface &s : surface) {
|
||||||
|
s.MakeEdgesInto(this, sel, SSurface::MakeAs::XYZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::MakeSectionEdgesInto(Vector n, double d, SEdgeList *sel, SBezierList *sbl)
|
||||||
|
{
|
||||||
|
for(SSurface &s : surface) {
|
||||||
|
if(s.CoincidentWithPlane(n, d)) {
|
||||||
|
s.MakeSectionEdgesInto(this, sel, sbl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::TriangulateInto(SMesh *sm) {
|
||||||
|
#pragma omp parallel for
|
||||||
|
for(int i=0; i<surface.n; i++) {
|
||||||
|
SSurface *s = &surface[i];
|
||||||
|
SMesh m;
|
||||||
|
s->TriangulateInto(this, &m);
|
||||||
|
#pragma omp critical
|
||||||
|
sm->MakeFromCopyOf(&m);
|
||||||
|
m.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SShell::IsEmpty() const {
|
||||||
|
return surface.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SShell::Clear() {
|
||||||
|
for(SSurface &s : surface) {
|
||||||
|
s.Clear();
|
||||||
|
}
|
||||||
|
surface.Clear();
|
||||||
|
|
||||||
|
for(SCurve &c : curve) {
|
||||||
|
c.Clear();
|
||||||
|
}
|
||||||
|
curve.Clear();
|
||||||
|
}
|
|
@ -489,608 +489,4 @@ void SSurface::Clear() {
|
||||||
trim.Clear();
|
trim.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
hSCurve hc;
|
|
||||||
hSSurface hs;
|
|
||||||
} TrimLine;
|
|
||||||
|
|
||||||
void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, RgbaColor color)
|
|
||||||
{
|
|
||||||
// Make the extrusion direction consistent with respect to the normal
|
|
||||||
// of the sketch we're extruding.
|
|
||||||
if((t0.Minus(t1)).Dot(sbls->normal) < 0) {
|
|
||||||
swap(t0, t1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define a coordinate system to contain the original sketch, and get
|
|
||||||
// a bounding box in that csys
|
|
||||||
Vector n = sbls->normal.ScaledBy(-1);
|
|
||||||
Vector u = n.Normal(0), v = n.Normal(1);
|
|
||||||
Vector orig = sbls->point;
|
|
||||||
double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
|
|
||||||
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
|
||||||
double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;
|
|
||||||
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
|
||||||
// and now fix things up so that all u and v lie between 0 and 1
|
|
||||||
orig = orig.Plus(u.ScaledBy(umin));
|
|
||||||
orig = orig.Plus(v.ScaledBy(vmin));
|
|
||||||
u = u.ScaledBy(umax - umin);
|
|
||||||
v = v.ScaledBy(vmax - vmin);
|
|
||||||
|
|
||||||
// So we can now generate the top and bottom surfaces of the extrusion,
|
|
||||||
// planes within a translated (and maybe mirrored) version of that csys.
|
|
||||||
SSurface s0, s1;
|
|
||||||
s0 = SSurface::FromPlane(orig.Plus(t0), u, v);
|
|
||||||
s0.color = color;
|
|
||||||
s1 = SSurface::FromPlane(orig.Plus(t1).Plus(u), u.ScaledBy(-1), v);
|
|
||||||
s1.color = color;
|
|
||||||
hSSurface hs0 = surface.AddAndAssignId(&s0),
|
|
||||||
hs1 = surface.AddAndAssignId(&s1);
|
|
||||||
|
|
||||||
// Now go through the input curves. For each one, generate its surface
|
|
||||||
// of extrusion, its two translated trim curves, and one trim line. We
|
|
||||||
// go through by loops so that we can assign the lines correctly.
|
|
||||||
SBezierLoop *sbl;
|
|
||||||
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
|
||||||
SBezier *sb;
|
|
||||||
List<TrimLine> trimLines = {};
|
|
||||||
|
|
||||||
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
|
||||||
// Generate the surface of extrusion of this curve, and add
|
|
||||||
// it to the list
|
|
||||||
SSurface ss = SSurface::FromExtrusionOf(sb, t0, t1);
|
|
||||||
ss.color = color;
|
|
||||||
hSSurface hsext = surface.AddAndAssignId(&ss);
|
|
||||||
|
|
||||||
// Translate the curve by t0 and t1 to produce two trim curves
|
|
||||||
SCurve sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY, 1.0);
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts));
|
|
||||||
sc.surfA = hs0;
|
|
||||||
sc.surfB = hsext;
|
|
||||||
hSCurve hc0 = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY, 1.0);
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts));
|
|
||||||
sc.surfA = hs1;
|
|
||||||
sc.surfB = hsext;
|
|
||||||
hSCurve hc1 = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
STrimBy stb0, stb1;
|
|
||||||
// The translated curves trim the flat top and bottom surfaces.
|
|
||||||
stb0 = STrimBy::EntireCurve(this, hc0, /*backwards=*/false);
|
|
||||||
stb1 = STrimBy::EntireCurve(this, hc1, /*backwards=*/true);
|
|
||||||
(surface.FindById(hs0))->trim.Add(&stb0);
|
|
||||||
(surface.FindById(hs1))->trim.Add(&stb1);
|
|
||||||
|
|
||||||
// The translated curves also trim the surface of extrusion.
|
|
||||||
stb0 = STrimBy::EntireCurve(this, hc0, /*backwards=*/true);
|
|
||||||
stb1 = STrimBy::EntireCurve(this, hc1, /*backwards=*/false);
|
|
||||||
(surface.FindById(hsext))->trim.Add(&stb0);
|
|
||||||
(surface.FindById(hsext))->trim.Add(&stb1);
|
|
||||||
|
|
||||||
// And form the trim line
|
|
||||||
Vector pt = sb->Finish();
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = SBezier::From(pt.Plus(t0), pt.Plus(t1));
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts));
|
|
||||||
hSCurve hl = curve.AddAndAssignId(&sc);
|
|
||||||
// save this for later
|
|
||||||
TrimLine tl;
|
|
||||||
tl.hc = hl;
|
|
||||||
tl.hs = hsext;
|
|
||||||
trimLines.Add(&tl);
|
|
||||||
}
|
|
||||||
|
|
||||||
int i;
|
|
||||||
for(i = 0; i < trimLines.n; i++) {
|
|
||||||
TrimLine *tl = &(trimLines[i]);
|
|
||||||
SSurface *ss = surface.FindById(tl->hs);
|
|
||||||
|
|
||||||
TrimLine *tlp = &(trimLines[WRAP(i-1, trimLines.n)]);
|
|
||||||
|
|
||||||
STrimBy stb;
|
|
||||||
stb = STrimBy::EntireCurve(this, tl->hc, /*backwards=*/true);
|
|
||||||
ss->trim.Add(&stb);
|
|
||||||
stb = STrimBy::EntireCurve(this, tlp->hc, /*backwards=*/false);
|
|
||||||
ss->trim.Add(&stb);
|
|
||||||
|
|
||||||
(curve.FindById(tl->hc))->surfA = ss->h;
|
|
||||||
(curve.FindById(tlp->hc))->surfB = ss->h;
|
|
||||||
}
|
|
||||||
trimLines.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SShell::CheckNormalAxisRelationship(SBezierLoopSet *sbls, Vector pt, Vector axis, double da, double dx)
|
|
||||||
// Check that the direction of revolution/extrusion ends up parallel to the normal of
|
|
||||||
// the sketch, on the side of the axis where the sketch is.
|
|
||||||
{
|
|
||||||
SBezierLoop *sbl;
|
|
||||||
Vector pto;
|
|
||||||
double md = VERY_NEGATIVE;
|
|
||||||
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
|
||||||
SBezier *sb;
|
|
||||||
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
|
||||||
// Choose the point farthest from the axis; we'll get garbage
|
|
||||||
// if we choose a point that lies on the axis, for example.
|
|
||||||
// (And our surface will be self-intersecting if the sketch
|
|
||||||
// spans the axis, so don't worry about that.)
|
|
||||||
for(int i = 0; i <= sb->deg; i++) {
|
|
||||||
Vector p = sb->ctrl[i];
|
|
||||||
double d = p.DistanceToLine(pt, axis);
|
|
||||||
if(d > md) {
|
|
||||||
md = d;
|
|
||||||
pto = p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vector ptc = pto.ClosestPointOnLine(pt, axis),
|
|
||||||
up = axis.Cross(pto.Minus(ptc)).ScaledBy(da),
|
|
||||||
vp = up.Plus(axis.ScaledBy(dx));
|
|
||||||
|
|
||||||
return (vp.Dot(sbls->normal) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// sketch must not contain the axis of revolution as a non-construction line for helix
|
|
||||||
void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis,
|
|
||||||
RgbaColor color, Group *group, double angles,
|
|
||||||
double anglef, double dists, double distf) {
|
|
||||||
int i0 = surface.n; // number of pre-existing surfaces
|
|
||||||
SBezierLoop *sbl;
|
|
||||||
// for testing - hard code the axial distance, and number of sections.
|
|
||||||
// distance will need to be parameters in the future.
|
|
||||||
double dist = distf - dists;
|
|
||||||
int sections = (int)(fabs(anglef - angles) / (PI / 2) + 1);
|
|
||||||
double wedge = (anglef - angles) / sections;
|
|
||||||
int startMapping = Group::REMAP_LATHE_START, endMapping = Group::REMAP_LATHE_END;
|
|
||||||
|
|
||||||
if(CheckNormalAxisRelationship(sbls, pt, axis, anglef-angles, distf-dists)) {
|
|
||||||
swap(angles, anglef);
|
|
||||||
swap(dists, distf);
|
|
||||||
dist = -dist;
|
|
||||||
wedge = -wedge;
|
|
||||||
swap(startMapping, endMapping);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define a coordinate system to contain the original sketch, and get
|
|
||||||
// a bounding box in that csys
|
|
||||||
Vector n = sbls->normal.ScaledBy(-1);
|
|
||||||
Vector u = n.Normal(0), v = n.Normal(1);
|
|
||||||
Vector orig = sbls->point;
|
|
||||||
double umax = VERY_NEGATIVE, umin = VERY_POSITIVE;
|
|
||||||
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
|
||||||
double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE;
|
|
||||||
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
|
||||||
// and now fix things up so that all u and v lie between 0 and 1
|
|
||||||
orig = orig.Plus(u.ScaledBy(umin));
|
|
||||||
orig = orig.Plus(v.ScaledBy(vmin));
|
|
||||||
u = u.ScaledBy(umax - umin);
|
|
||||||
v = v.ScaledBy(vmax - vmin);
|
|
||||||
|
|
||||||
// So we can now generate the end caps of the extrusion within
|
|
||||||
// a translated and rotated (and maybe mirrored) version of that csys.
|
|
||||||
SSurface s0, s1;
|
|
||||||
s0 = SSurface::FromPlane(orig.RotatedAbout(pt, axis, angles).Plus(axis.ScaledBy(dists)),
|
|
||||||
u.RotatedAbout(axis, angles), v.RotatedAbout(axis, angles));
|
|
||||||
s0.color = color;
|
|
||||||
|
|
||||||
hEntity face0 = group->Remap(Entity::NO_ENTITY, startMapping);
|
|
||||||
s0.face = face0.v;
|
|
||||||
|
|
||||||
s1 = SSurface::FromPlane(
|
|
||||||
orig.Plus(u).RotatedAbout(pt, axis, anglef).Plus(axis.ScaledBy(distf)),
|
|
||||||
u.ScaledBy(-1).RotatedAbout(axis, anglef), v.RotatedAbout(axis, anglef));
|
|
||||||
s1.color = color;
|
|
||||||
|
|
||||||
hEntity face1 = group->Remap(Entity::NO_ENTITY, endMapping);
|
|
||||||
s1.face = face1.v;
|
|
||||||
|
|
||||||
hSSurface hs0 = surface.AddAndAssignId(&s0);
|
|
||||||
hSSurface hs1 = surface.AddAndAssignId(&s1);
|
|
||||||
|
|
||||||
// Now we actually build and trim the swept surfaces. One loop at a time.
|
|
||||||
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
|
||||||
int i, j;
|
|
||||||
SBezier *sb;
|
|
||||||
List<std::vector<hSSurface>> hsl = {};
|
|
||||||
|
|
||||||
// This is where all the NURBS are created and Remapped to the generating curve
|
|
||||||
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
|
||||||
std::vector<hSSurface> revs(sections);
|
|
||||||
for(j = 0; j < sections; j++) {
|
|
||||||
if((dist == 0) && sb->deg == 1 &&
|
|
||||||
(sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS &&
|
|
||||||
(sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS) {
|
|
||||||
// This is a line on the axis of revolution; it does
|
|
||||||
// not contribute a surface.
|
|
||||||
revs[j].v = 0;
|
|
||||||
} else {
|
|
||||||
SSurface ss = SSurface::FromRevolutionOf(
|
|
||||||
sb, pt, axis, angles + (wedge)*j, angles + (wedge) * (j + 1),
|
|
||||||
dists + j * dist / sections, dists + (j + 1) * dist / sections);
|
|
||||||
ss.color = color;
|
|
||||||
if(sb->entity != 0) {
|
|
||||||
hEntity he;
|
|
||||||
he.v = sb->entity;
|
|
||||||
hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE);
|
|
||||||
if(SK.entity.FindByIdNoOops(hface) != NULL) {
|
|
||||||
ss.face = hface.v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
revs[j] = surface.AddAndAssignId(&ss);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hsl.Add(&revs);
|
|
||||||
}
|
|
||||||
// Still the same loop. Need to create trim curves
|
|
||||||
for(i = 0; i < sbl->l.n; i++) {
|
|
||||||
std::vector<hSSurface> revs = hsl[i], revsp = hsl[WRAP(i - 1, sbl->l.n)];
|
|
||||||
|
|
||||||
sb = &(sbl->l[i]);
|
|
||||||
|
|
||||||
// we will need the grid t-values for this entire row of surfaces
|
|
||||||
List<double> t_values;
|
|
||||||
t_values = {};
|
|
||||||
if (revs[0].v) {
|
|
||||||
double ps = 0.0;
|
|
||||||
t_values.Add(&ps);
|
|
||||||
(surface.FindById(revs[0]))->MakeTriangulationGridInto(
|
|
||||||
&t_values, 0.0, 1.0, true, 0);
|
|
||||||
}
|
|
||||||
// we generate one more curve than we did surfaces
|
|
||||||
for(j = 0; j <= sections; j++) {
|
|
||||||
SCurve sc;
|
|
||||||
Quaternion qs = Quaternion::From(axis, angles + wedge * j);
|
|
||||||
// we want Q*(x - p) + p = Q*x + (p - Q*p)
|
|
||||||
Vector ts =
|
|
||||||
pt.Minus(qs.Rotate(pt)).Plus(axis.ScaledBy(dists + j * dist / sections));
|
|
||||||
|
|
||||||
// If this input curve generated a surface, then trim that
|
|
||||||
// surface with the rotated version of the input curve.
|
|
||||||
if(revs[0].v) { // not d[j] because crash on j==sections
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = sb->TransformedBy(ts, qs, 1.0);
|
|
||||||
// make the PWL for the curve based on t value list
|
|
||||||
for(int x = 0; x < t_values.n; x++) {
|
|
||||||
SCurvePt scpt;
|
|
||||||
scpt.tag = 0;
|
|
||||||
scpt.p = sc.exact.PointAt(t_values[x]);
|
|
||||||
scpt.vertex = (x == 0) || (x == (t_values.n - 1));
|
|
||||||
sc.pts.Add(&scpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
// the surfaces already exists so trim with this curve
|
|
||||||
if(j < sections) {
|
|
||||||
sc.surfA = revs[j];
|
|
||||||
} else {
|
|
||||||
sc.surfA = hs1; // end cap
|
|
||||||
}
|
|
||||||
|
|
||||||
if(j > 0) {
|
|
||||||
sc.surfB = revs[j - 1];
|
|
||||||
} else {
|
|
||||||
sc.surfB = hs0; // staring cap
|
|
||||||
}
|
|
||||||
|
|
||||||
hSCurve hcb = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
STrimBy stb;
|
|
||||||
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);
|
|
||||||
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
|
||||||
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);
|
|
||||||
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
|
||||||
} else if(j == 0) { // curve was on the rotation axis and is shared by the end caps.
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = sb->TransformedBy(ts, qs, 1.0);
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts));
|
|
||||||
sc.surfA = hs1; // end cap
|
|
||||||
sc.surfB = hs0; // staring cap
|
|
||||||
hSCurve hcb = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
STrimBy stb;
|
|
||||||
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);
|
|
||||||
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
|
||||||
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);
|
|
||||||
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// And if this input curve and the one after it both generated
|
|
||||||
// surfaces, then trim both of those by the appropriate
|
|
||||||
// curve based on the control points.
|
|
||||||
if((j < sections) && revs[j].v && revsp[j].v) {
|
|
||||||
SSurface *ss = surface.FindById(revs[j]);
|
|
||||||
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = SBezier::From(ss->ctrl[0][0], ss->ctrl[0][1], ss->ctrl[0][2]);
|
|
||||||
sc.exact.weight[1] = ss->weight[0][1];
|
|
||||||
double max_dt = 0.5;
|
|
||||||
if (sc.exact.deg > 1) max_dt = 0.125;
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts), 0.0, max_dt);
|
|
||||||
sc.surfA = revs[j];
|
|
||||||
sc.surfB = revsp[j];
|
|
||||||
|
|
||||||
hSCurve hcc = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
STrimBy stb;
|
|
||||||
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false);
|
|
||||||
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
|
||||||
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true);
|
|
||||||
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t_values.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
hsl.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(dist == 0) {
|
|
||||||
MakeFirstOrderRevolvedSurfaces(pt, axis, i0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color,
|
|
||||||
Group *group) {
|
|
||||||
int i0 = surface.n; // number of pre-existing surfaces
|
|
||||||
SBezierLoop *sbl;
|
|
||||||
|
|
||||||
if(CheckNormalAxisRelationship(sbls, pt, axis, 1.0, 0.0)) {
|
|
||||||
axis = axis.ScaledBy(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we actually build and trim the surfaces.
|
|
||||||
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
|
||||||
int i, j;
|
|
||||||
SBezier *sb;
|
|
||||||
List<std::vector<hSSurface>> hsl = {};
|
|
||||||
|
|
||||||
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
|
||||||
std::vector<hSSurface> revs(4);
|
|
||||||
for(j = 0; j < 4; j++) {
|
|
||||||
if(sb->deg == 1 &&
|
|
||||||
(sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS &&
|
|
||||||
(sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS)
|
|
||||||
{
|
|
||||||
// This is a line on the axis of revolution; it does
|
|
||||||
// not contribute a surface.
|
|
||||||
revs[j].v = 0;
|
|
||||||
} else {
|
|
||||||
SSurface ss = SSurface::FromRevolutionOf(sb, pt, axis, (PI / 2) * j,
|
|
||||||
(PI / 2) * (j + 1), 0.0, 0.0);
|
|
||||||
ss.color = color;
|
|
||||||
if(sb->entity != 0) {
|
|
||||||
hEntity he;
|
|
||||||
he.v = sb->entity;
|
|
||||||
hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE);
|
|
||||||
if(SK.entity.FindByIdNoOops(hface) != NULL) {
|
|
||||||
ss.face = hface.v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
revs[j] = surface.AddAndAssignId(&ss);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hsl.Add(&revs);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(i = 0; i < sbl->l.n; i++) {
|
|
||||||
std::vector<hSSurface> revs = hsl[i],
|
|
||||||
revsp = hsl[WRAP(i-1, sbl->l.n)];
|
|
||||||
|
|
||||||
sb = &(sbl->l[i]);
|
|
||||||
|
|
||||||
for(j = 0; j < 4; j++) {
|
|
||||||
SCurve sc;
|
|
||||||
Quaternion qs = Quaternion::From(axis, (PI/2)*j);
|
|
||||||
// we want Q*(x - p) + p = Q*x + (p - Q*p)
|
|
||||||
Vector ts = pt.Minus(qs.Rotate(pt));
|
|
||||||
|
|
||||||
// If this input curve generate a surface, then trim that
|
|
||||||
// surface with the rotated version of the input curve.
|
|
||||||
if(revs[j].v) {
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = sb->TransformedBy(ts, qs, 1.0);
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts));
|
|
||||||
sc.surfA = revs[j];
|
|
||||||
sc.surfB = revs[WRAP(j-1, 4)];
|
|
||||||
|
|
||||||
hSCurve hcb = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
STrimBy stb;
|
|
||||||
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true);
|
|
||||||
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
|
||||||
stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false);
|
|
||||||
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// And if this input curve and the one after it both generated
|
|
||||||
// surfaces, then trim both of those by the appropriate
|
|
||||||
// circle.
|
|
||||||
if(revs[j].v && revsp[j].v) {
|
|
||||||
SSurface *ss = surface.FindById(revs[j]);
|
|
||||||
|
|
||||||
sc = {};
|
|
||||||
sc.isExact = true;
|
|
||||||
sc.exact = SBezier::From(ss->ctrl[0][0],
|
|
||||||
ss->ctrl[0][1],
|
|
||||||
ss->ctrl[0][2]);
|
|
||||||
sc.exact.weight[1] = ss->weight[0][1];
|
|
||||||
(sc.exact).MakePwlInto(&(sc.pts));
|
|
||||||
sc.surfA = revs[j];
|
|
||||||
sc.surfB = revsp[j];
|
|
||||||
|
|
||||||
hSCurve hcc = curve.AddAndAssignId(&sc);
|
|
||||||
|
|
||||||
STrimBy stb;
|
|
||||||
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false);
|
|
||||||
(surface.FindById(sc.surfA))->trim.Add(&stb);
|
|
||||||
stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true);
|
|
||||||
(surface.FindById(sc.surfB))->trim.Add(&stb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hsl.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
MakeFirstOrderRevolvedSurfaces(pt, axis, i0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::MakeFirstOrderRevolvedSurfaces(Vector pt, Vector axis, int i0) {
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for(i = i0; i < surface.n; i++) {
|
|
||||||
SSurface *srf = &(surface[i]);
|
|
||||||
|
|
||||||
// Revolution of a line; this is potentially a plane, which we can
|
|
||||||
// rewrite to have degree (1, 1).
|
|
||||||
if(srf->degm == 1 && srf->degn == 2) {
|
|
||||||
// close start, far start, far finish
|
|
||||||
Vector cs, fs, ff;
|
|
||||||
double d0, d1;
|
|
||||||
d0 = (srf->ctrl[0][0]).DistanceToLine(pt, axis);
|
|
||||||
d1 = (srf->ctrl[1][0]).DistanceToLine(pt, axis);
|
|
||||||
|
|
||||||
if(d0 > d1) {
|
|
||||||
cs = srf->ctrl[1][0];
|
|
||||||
fs = srf->ctrl[0][0];
|
|
||||||
ff = srf->ctrl[0][2];
|
|
||||||
} else {
|
|
||||||
cs = srf->ctrl[0][0];
|
|
||||||
fs = srf->ctrl[1][0];
|
|
||||||
ff = srf->ctrl[1][2];
|
|
||||||
}
|
|
||||||
|
|
||||||
// origin close, origin far
|
|
||||||
Vector oc = cs.ClosestPointOnLine(pt, axis),
|
|
||||||
of = fs.ClosestPointOnLine(pt, axis);
|
|
||||||
|
|
||||||
if(oc.Equals(of)) {
|
|
||||||
// This is a plane, not a (non-degenerate) cone.
|
|
||||||
Vector oldn = srf->NormalAt(0.5, 0.5);
|
|
||||||
|
|
||||||
Vector u = fs.Minus(of), v;
|
|
||||||
|
|
||||||
v = (axis.Cross(u)).WithMagnitude(1);
|
|
||||||
|
|
||||||
double vm = (ff.Minus(of)).Dot(v);
|
|
||||||
v = v.ScaledBy(vm);
|
|
||||||
|
|
||||||
srf->degm = 1;
|
|
||||||
srf->degn = 1;
|
|
||||||
srf->ctrl[0][0] = of;
|
|
||||||
srf->ctrl[0][1] = of.Plus(u);
|
|
||||||
srf->ctrl[1][0] = of.Plus(v);
|
|
||||||
srf->ctrl[1][1] = of.Plus(u).Plus(v);
|
|
||||||
srf->weight[0][0] = 1;
|
|
||||||
srf->weight[0][1] = 1;
|
|
||||||
srf->weight[1][0] = 1;
|
|
||||||
srf->weight[1][1] = 1;
|
|
||||||
|
|
||||||
if(oldn.Dot(srf->NormalAt(0.5, 0.5)) < 0) {
|
|
||||||
swap(srf->ctrl[0][0], srf->ctrl[1][0]);
|
|
||||||
swap(srf->ctrl[0][1], srf->ctrl[1][1]);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(fabs(d0 - d1) < LENGTH_EPS) {
|
|
||||||
// This is a cylinder; so transpose it so that we'll recognize
|
|
||||||
// it as a surface of extrusion.
|
|
||||||
SSurface sn = *srf;
|
|
||||||
|
|
||||||
// Transposing u and v flips the normal, so reverse u to
|
|
||||||
// flip it again and put it back where we started.
|
|
||||||
sn.degm = 2;
|
|
||||||
sn.degn = 1;
|
|
||||||
int dm, dn;
|
|
||||||
for(dm = 0; dm <= 1; dm++) {
|
|
||||||
for(dn = 0; dn <= 2; dn++) {
|
|
||||||
sn.ctrl [dn][dm] = srf->ctrl [1-dm][dn];
|
|
||||||
sn.weight[dn][dm] = srf->weight[1-dm][dn];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*srf = sn;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::MakeFromCopyOf(SShell *a) {
|
|
||||||
ssassert(this != a, "Can't make from copy of self");
|
|
||||||
MakeFromTransformationOf(a,
|
|
||||||
Vector::From(0, 0, 0), Quaternion::IDENTITY, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::MakeFromTransformationOf(SShell *a,
|
|
||||||
Vector t, Quaternion q, double scale)
|
|
||||||
{
|
|
||||||
booleanFailed = false;
|
|
||||||
surface.ReserveMore(a->surface.n);
|
|
||||||
for(SSurface &s : a->surface) {
|
|
||||||
SSurface n;
|
|
||||||
n = SSurface::FromTransformationOf(&s, t, q, scale, /*includingTrims=*/true);
|
|
||||||
surface.Add(&n); // keeping the old ID
|
|
||||||
}
|
|
||||||
|
|
||||||
curve.ReserveMore(a->curve.n);
|
|
||||||
for(SCurve &c : a->curve) {
|
|
||||||
SCurve n;
|
|
||||||
n = SCurve::FromTransformationOf(&c, t, q, scale);
|
|
||||||
curve.Add(&n); // keeping the old ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::MakeEdgesInto(SEdgeList *sel) {
|
|
||||||
for(SSurface &s : surface) {
|
|
||||||
s.MakeEdgesInto(this, sel, SSurface::MakeAs::XYZ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::MakeSectionEdgesInto(Vector n, double d, SEdgeList *sel, SBezierList *sbl)
|
|
||||||
{
|
|
||||||
for(SSurface &s : surface) {
|
|
||||||
if(s.CoincidentWithPlane(n, d)) {
|
|
||||||
s.MakeSectionEdgesInto(this, sel, sbl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::TriangulateInto(SMesh *sm) {
|
|
||||||
#pragma omp parallel for
|
|
||||||
for(int i=0; i<surface.n; i++) {
|
|
||||||
SSurface *s = &surface[i];
|
|
||||||
SMesh m;
|
|
||||||
s->TriangulateInto(this, &m);
|
|
||||||
#pragma omp critical
|
|
||||||
sm->MakeFromCopyOf(&m);
|
|
||||||
m.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SShell::IsEmpty() const {
|
|
||||||
return surface.IsEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SShell::Clear() {
|
|
||||||
for(SSurface &s : surface) {
|
|
||||||
s.Clear();
|
|
||||||
}
|
|
||||||
surface.Clear();
|
|
||||||
|
|
||||||
for(SCurve &c : curve) {
|
|
||||||
c.Clear();
|
|
||||||
}
|
|
||||||
curve.Clear();
|
|
||||||
}
|
|
||||||
|
|
3
src/ui.h
3
src/ui.h
|
@ -622,6 +622,7 @@ public:
|
||||||
void HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin,
|
void HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin,
|
||||||
double *wmin, bool usePerspective,
|
double *wmin, bool usePerspective,
|
||||||
const Camera &camera);
|
const Camera &camera);
|
||||||
|
void ZoomToMouse(double delta);
|
||||||
void LoopOverPoints(const std::vector<Entity *> &entities,
|
void LoopOverPoints(const std::vector<Entity *> &entities,
|
||||||
const std::vector<Constraint *> &constraints,
|
const std::vector<Constraint *> &constraints,
|
||||||
const std::vector<hEntity> &faces,
|
const std::vector<hEntity> &faces,
|
||||||
|
@ -842,7 +843,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, double delta);
|
void MouseScroll(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);
|
||||||
|
|
|
@ -76,6 +76,9 @@ target_link_libraries(solvespace-testsuite
|
||||||
solvespace-headless
|
solvespace-headless
|
||||||
${COVERAGE_LIBRARY})
|
${COVERAGE_LIBRARY})
|
||||||
|
|
||||||
|
target_include_directories(solvespace-testsuite
|
||||||
|
PRIVATE
|
||||||
|
${EIGEN3_INCLUDE_DIRS})
|
||||||
add_dependencies(solvespace-testsuite
|
add_dependencies(solvespace-testsuite
|
||||||
resources)
|
resources)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue