diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index e9d7df97b..640e5b536 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,6 +1,6 @@ ### System information -- **SolveSpace version:** +- **SolveSpace version:** - **Operating system:** ### Expected behavior diff --git a/.github/scripts/build-macos.sh b/.github/scripts/build-macos.sh index fae7e27ff..7111afd67 100755 --- a/.github/scripts/build-macos.sh +++ b/.github/scripts/build-macos.sh @@ -14,13 +14,12 @@ CMAKE_GENERATOR="Unix Makefiles" CMAKE_PREFIX_PATH="" if [ "$2" = "arm64" ]; then OSX_ARCHITECTURE="arm64" - CMAKE_PREFIX_PATH="/tmp/libomp-arm64/libomp/11.0.1" - git apply cmake/libpng-macos-arm64.patch || echo "Could not apply patch, probably already patched..." + CMAKE_PREFIX_PATH=$(find /tmp/libomp-arm64/libomp -depth 1) mkdir build-arm64 || true cd build-arm64 elif [ "$2" = "x86_64" ]; then OSX_ARCHITECTURE="x86_64" - CMAKE_PREFIX_PATH="/tmp/libomp-x86_64/libomp/11.0.1" + CMAKE_PREFIX_PATH=$(find /tmp/libomp-x86_64/libomp -depth 1) mkdir build || true cd build else @@ -49,4 +48,4 @@ else if [ $(uname -m) = "$2" ]; then make -j$(sysctl -n hw.logicalcpu) test_solvespace fi -fi \ No newline at end of file +fi diff --git a/.github/scripts/build-wasmlib.sh b/.github/scripts/build-wasmlib.sh new file mode 100755 index 000000000..842d1d11e --- /dev/null +++ b/.github/scripts/build-wasmlib.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +cd .. +git clone https://github.com/emscripten-core/emsdk.git --depth 1 +cd emsdk +./emsdk install latest +./emsdk activate latest +cd ../solvespace +source ../emsdk/emsdk_env.sh +mkdir -p build-wasmlib +cd build-wasmlib +emcmake cmake .. \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DENABLE_GUI="OFF" \ + -DENABLE_CLI="OFF" \ + -DENABLE_TESTS="OFF" \ + -DENABLE_COVERAGE="OFF" \ + -DENABLE_OPENMP="OFF" \ + -DFORCE_VENDORED_Eigen3="ON" \ + -DENABLE_LTO="ON" +cmake --build . -j$(nproc) --target slvs-wasm diff --git a/.github/scripts/build-web.sh b/.github/scripts/build-web.sh new file mode 100755 index 000000000..6e61338de --- /dev/null +++ b/.github/scripts/build-web.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +if [ "$1" = "release" ]; then + BUILD_TYPE="Release" + ENABLE_LTO="ON" +else + BUILD_TYPE="Debug" + ENABLE_LTO="OFF" +fi + +cd .. +git clone https://github.com/emscripten-core/emsdk.git --depth 1 +cd emsdk +./emsdk install latest +./emsdk activate latest +cd ../solvespace +source ../emsdk/emsdk_env.sh +mkdir build +cd build +emcmake cmake .. \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -DENABLE_CLI="OFF" \ + -DENABLE_TESTS="OFF" \ + -DENABLE_COVERAGE="OFF" \ + -DENABLE_OPENMP="OFF" \ + -DENABLE_LTO="ON" +cmake --build . --config "${BUILD_TYPE}" -j$(nproc) --target solvespace diff --git a/.github/scripts/build-windows.sh b/.github/scripts/build-windows.sh index e81fb2ac8..2b82a00fe 100755 --- a/.github/scripts/build-windows.sh +++ b/.github/scripts/build-windows.sh @@ -9,28 +9,45 @@ if [ "$1" = "release" ]; then else ENABLE_OPENMP="OFF" fi + + if [ "$3" = "x64" ]; then + PLATFORM="$3" + else + PLATFORM="Win32" + fi + BUILD_TYPE=RelWithDebInfo cmake \ - -G "Visual Studio 16 2019" \ + -G "Visual Studio 17 2022" \ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ -DENABLE_OPENMP="${ENABLE_OPENMP}" \ -DENABLE_LTO=ON \ - -DCMAKE_GENERATOR_PLATFORM="Win32" \ + -DCMAKE_GENERATOR_PLATFORM="${PLATFORM}" \ .. else BUILD_TYPE=Debug cmake \ - -G "Visual Studio 16 2019" \ + -G "Visual Studio 17 2022" \ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ -DENABLE_OPENMP="ON" \ + -DENABLE_SANITIZERS="ON" \ -DCMAKE_GENERATOR_PLATFORM="Win32" \ .. fi cmake --build . --config "${BUILD_TYPE}" -- -maxcpucount -bin/$BUILD_TYPE/solvespace-testsuite.exe +# Run the tests in the proper environment (required for having the ASan libraries available) +cmake --build . --config "${BUILD_TYPE}" -t test_solvespace -- -maxcpucount -if [ "$2" = "openmp" ]; then - mv bin/$BUILD_TYPE/solvespace.exe bin/$BUILD_TYPE/solvespace-openmp.exe +if [ "$3" = "x64" ]; then + if [ "$2" != "openmp" ]; then + mv bin/$BUILD_TYPE/solvespace.exe bin/$BUILD_TYPE/solvespace_single_core_x64.exe + else + mv bin/$BUILD_TYPE/solvespace.exe bin/$BUILD_TYPE/solvespace_x64.exe + fi +else + if [ "$2" != "openmp" ]; then + mv bin/$BUILD_TYPE/solvespace.exe bin/$BUILD_TYPE/solvespace_single_core.exe + fi fi diff --git a/.github/scripts/install-macos.sh b/.github/scripts/install-macos.sh index c6ec104d4..8457ad7e4 100755 --- a/.github/scripts/install-macos.sh +++ b/.github/scripts/install-macos.sh @@ -1,14 +1,16 @@ #!/bin/sh -xe if [ "$1" = "ci" ]; then - curl -L https://bintray.com/homebrew/bottles/download_file?file_path=libomp-11.0.1.arm64_big_sur.bottle.tar.gz --output /tmp/libomp-arm64.tar.gz + armloc=$(brew fetch --bottle-tag=arm64_sequoia libomp | grep -i downloaded | grep tar.gz | cut -f2 -d:) + x64loc=$(brew fetch --bottle-tag=sequoia libomp | grep -i downloaded | grep tar.gz | cut -f2 -d:) + cp $armloc /tmp/libomp-arm64.tar.gz mkdir /tmp/libomp-arm64 || true tar -xzvf /tmp/libomp-arm64.tar.gz -C /tmp/libomp-arm64 - curl -L https://bintray.com/homebrew/bottles/download_file?file_path=libomp-11.0.1.big_sur.bottle.tar.gz --output /tmp/libomp-x86_64.tar.gz + cp $x64loc /tmp/libomp-x86_64.tar.gz mkdir /tmp/libomp-x86_64 || true tar -xzvf /tmp/libomp-x86_64.tar.gz -C /tmp/libomp-x86_64 else brew install libomp fi -git submodule update --init extlib/cairo extlib/freetype extlib/libdxfrw extlib/libpng extlib/mimalloc extlib/pixman extlib/zlib +git submodule update --init extlib/cairo extlib/freetype extlib/libdxfrw extlib/libpng extlib/mimalloc extlib/pixman extlib/zlib extlib/eigen diff --git a/.github/scripts/install-ubuntu.sh b/.github/scripts/install-ubuntu.sh index 53f88b273..6033f7351 100755 --- a/.github/scripts/install-ubuntu.sh +++ b/.github/scripts/install-ubuntu.sh @@ -5,6 +5,6 @@ sudo apt-get update -qq sudo apt-get install -q -y \ zlib1g-dev libpng-dev libcairo2-dev libfreetype6-dev libjson-c-dev \ libfontconfig1-dev libgtkmm-3.0-dev libpangomm-1.4-dev libgl-dev \ - libgl-dev libglu-dev libspnav-dev + libgl-dev libglu-dev libspnav-dev -git submodule update --init extlib/libdxfrw extlib/mimalloc +git submodule update --init extlib/libdxfrw extlib/mimalloc extlib/eigen diff --git a/.github/scripts/install-web.sh b/.github/scripts/install-web.sh new file mode 100755 index 000000000..738d67dd7 --- /dev/null +++ b/.github/scripts/install-web.sh @@ -0,0 +1,3 @@ +#!/bin/sh -xe + +git submodule update --init diff --git a/.github/scripts/sign-macos.sh b/.github/scripts/sign-macos.sh index 8e944d3e7..63f5734a7 100755 --- a/.github/scripts/sign-macos.sh +++ b/.github/scripts/sign-macos.sh @@ -58,34 +58,29 @@ hdiutil create -srcfolder "${app}" "${dmg}" # sign the .dmg codesign -s "${MACOS_DEVELOPER_ID}" --timestamp --options runtime -f --deep "${dmg}" -# notarize and store request uuid in variable -notarize_uuid=$(xcrun altool --notarize-app --primary-bundle-id "${bundle_id}" --username "${MACOS_APPSTORE_USERNAME}" --password "${MACOS_APPSTORE_APP_PASSWORD}" --file "${dmg}" 2>&1 | grep RequestUUID | awk '{print $3'}) - -echo $notarize_uuid - -# wait a bit so we don't get errors during checking -sleep 10 - -success=0 -for (( ; ; )) -do - echo "Checking progress..." - progress=$(xcrun altool --notarization-info "${notarize_uuid}" -u "${MACOS_APPSTORE_USERNAME}" -p "${MACOS_APPSTORE_APP_PASSWORD}" 2>&1) - # echo "${progress}" - - if [ $? -ne 0 ] || [[ "${progress}" =~ "Invalid" ]] ; then - echo "Error with notarization. Exiting" - break - fi - - if [[ "${progress}" =~ "success" ]]; then - success=1 - break - else - echo "Not completed yet. Sleeping for 10 seconds" - fi - sleep 10 -done +if ! command -v xcrun >/dev/null || ! xcrun --find notarytool >/dev/null; then + echo "Notarytool is not present in the system. Notarization has failed." + exit 1 +fi + +# Submit the package for notarization +notarization_output=$( + xcrun notarytool submit "${dmg}" \ + --apple-id "hello@koenschmeets.nl" \ + --password "${MACOS_APPSTORE_APP_PASSWORD}" \ + --team-id "8X77K9NDG3" \ + --wait 2>&1) + +if [ $? -eq 0 ]; then + # Extract the operation ID from the output + operation_id=$(echo "$notarization_output" | awk '/RequestUUID/ {print $NF}') + echo "Notarization submitted. Operation ID: $operation_id" + exit 0 + else + echo "Notarization failed. Error: $notarization_output" + exit 1 + fi +fi # staple -xcrun stapler staple "${dmg}" \ No newline at end of file +xcrun stapler staple "${dmg}" diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index dde06b802..48a5c3880 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -12,17 +12,19 @@ jobs: cancel_previous_runs: runs-on: ubuntu-latest name: Cancel Previous Runs + permissions: + actions: write steps: - - uses: styfle/cancel-workflow-action@0.8.0 + - uses: styfle/cancel-workflow-action@0.12.1 with: access_token: ${{ github.token }} test_ubuntu: needs: [cancel_previous_runs] - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest name: Test Ubuntu steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Dependencies run: .github/scripts/install-ubuntu.sh - name: Build & Test @@ -30,10 +32,10 @@ jobs: test_windows: needs: [cancel_previous_runs] - runs-on: windows-2019 + runs-on: windows-2022 name: Test Windows steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Dependencies run: .github/scripts/install-windows.sh shell: bash @@ -43,10 +45,10 @@ jobs: test_macos: needs: [cancel_previous_runs] - runs-on: macos-10.15 + runs-on: macos-latest name: Test macOS steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Dependencies run: .github/scripts/install-macos.sh ci - name: Build & Test @@ -55,9 +57,9 @@ jobs: build_release_windows: needs: [test_ubuntu, test_windows, test_macos] name: Build Release Windows - runs-on: windows-2019 + runs-on: windows-2022 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Dependencies run: .github/scripts/install-windows.sh shell: bash @@ -65,17 +67,17 @@ jobs: run: .github/scripts/build-windows.sh release shell: bash - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: - name: windows - path: build/bin/RelWithDebInfo/solvespace.exe - + name: windows_single_core + path: build/bin/RelWithDebInfo/solvespace_single_core.exe + build_release_windows_openmp: needs: [test_ubuntu, test_windows, test_macos] name: Build Release Windows (OpenMP) - runs-on: windows-2019 + runs-on: windows-2022 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Dependencies run: .github/scripts/install-windows.sh shell: bash @@ -83,17 +85,53 @@ jobs: run: .github/scripts/build-windows.sh release openmp shell: bash - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 + with: + name: windows + path: build/bin/RelWithDebInfo/solvespace.exe + + build_release_windows_x64: + needs: [test_ubuntu, test_windows, test_macos] + name: Build Release Windows x64 + runs-on: windows-2022 + steps: + - uses: actions/checkout@v4 + - name: Install Dependencies + run: .github/scripts/install-windows.sh + shell: bash + - name: Build & Test + run: .github/scripts/build-windows.sh release not_openmp x64 + shell: bash + - name: Upload artifact + uses: actions/upload-artifact@v4 with: - name: windows-openmp - path: build/bin/RelWithDebInfo/solvespace-openmp.exe + name: windows_single_core_x64 + path: build/bin/RelWithDebInfo/solvespace_single_core_x64.exe + + build_release_windows_openmp_x64: + needs: [test_ubuntu, test_windows, test_macos] + name: Build Release Windows (OpenMP) x64 + runs-on: windows-2022 + steps: + - uses: actions/checkout@v4 + - name: Install Dependencies + run: .github/scripts/install-windows.sh + shell: bash + - name: Build & Test + run: .github/scripts/build-windows.sh release openmp x64 + shell: bash + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: windows_x64 + path: build/bin/RelWithDebInfo/solvespace_x64.exe build_release_macos: needs: [test_ubuntu, test_windows, test_macos] name: Build Release macOS - runs-on: macos-10.15 + runs-on: macos-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Dependencies run: .github/scripts/install-macos.sh ci - name: Build & Test @@ -107,121 +145,75 @@ jobs: MACOS_APPSTORE_USERNAME: ${{ secrets.MACOS_APPSTORE_USERNAME }} MACOS_DEVELOPER_ID: ${{ secrets.MACOS_DEVELOPER_ID }} - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: macos path: build/bin/SolveSpace.dmg - deploy_snap_amd64: - needs: [test_ubuntu, test_windows, test_macos] - name: Deploy AMD64 Snap - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set Up Source - run: rsync --filter=":- .gitignore" -r ./ pkg/snap/solvespace-snap-src - - name: Build Snap - id: build - uses: diddlesnaps/snapcraft-multiarch-action@v1 - with: - path: pkg/snap - - name: Upload & Release to Edge - if: github.event_name == 'push' - uses: snapcore/action-publish@v1 - with: - store_login: ${{ secrets.SNAPSTORE_LOGIN }} - snap: ${{ steps.build.outputs.snap }} - release: edge - - name: Upload & Release to Beta + Edge - if: github.event_name == 'release' - uses: snapcore/action-publish@v1 - with: - store_login: ${{ secrets.SNAPSTORE_LOGIN }} - snap: ${{ steps.build.outputs.snap }} - release: edge,beta - - deploy_snap_arm64: + build_release_web: needs: [test_ubuntu, test_windows, test_macos] - name: Deploy ARM64 Snap + name: Build Release Web runs-on: ubuntu-latest steps: - - uses: docker/setup-qemu-action@v1 - with: - image: tonistiigi/binfmt@sha256:df15403e06a03c2f461c1f7938b171fda34a5849eb63a70e2a2109ed5a778bde - - uses: actions/checkout@v2 - - name: Set Up Source - run: rsync --filter=":- .gitignore" -r ./ pkg/snap/solvespace-snap-src - - name: Build Snap - id: build - uses: diddlesnaps/snapcraft-multiarch-action@v1 - with: - path: pkg/snap - architecture: arm64 - - name: Upload & Release to Edge - if: github.event_name == 'push' - uses: snapcore/action-publish@v1 - with: - store_login: ${{ secrets.SNAPSTORE_LOGIN }} - snap: ${{ steps.build.outputs.snap }} - release: edge - - name: Upload & Release to Beta + Edge - if: github.event_name == 'release' - uses: snapcore/action-publish@v1 + - uses: actions/checkout@v4 + - name: Install Dependencies + run: .github/scripts/install-web.sh + - name: Build & Pack + run: | + .github/scripts/build-web.sh release + cd build/bin + rm *.a || true + - name: Upload artifact + uses: actions/upload-artifact@v4 with: - store_login: ${{ secrets.SNAPSTORE_LOGIN }} - snap: ${{ steps.build.outputs.snap }} - release: edge,beta + name: solvespace_web + path: build/bin/ - update_edge_release: - name: Update Edge Release - needs: [build_release_windows, build_release_windows_openmp, build_release_macos] - if: github.event_name == 'push' && !cancelled() - runs-on: ubuntu-latest - outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} - steps: - - name: Delete Old Edge Release - uses: dev-drprasad/delete-tag-and-release@v0.2.0 - with: - delete_release: true - tag_name: edge - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Wait - shell: bash - run: sleep 60 - - name: Create New Edge Release - id: create_release - uses: softprops/action-gh-release@35d938cf01f60fbe522917c81be1e892074f6ad6 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: edge - name: Edge - prerelease: true - draft: false - body: ${{ github.event.head_commit.message }} + # deploy_snap_amd64: + # needs: [test_ubuntu, test_windows, test_macos] + # name: Deploy AMD64 Snap + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # - 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 + # uses: snapcore/action-build@v1 + # id: build + # with: + # path: pkg/snap + # - name: Upload & Release to Edge + # if: github.event_name == 'push' + # uses: snapcore/action-publish@v1 + # env: + # SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPSTORE_LOGIN }} + # with: + # 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: name: Upload Release Assets - needs: [build_release_windows, build_release_windows_openmp, build_release_macos, update_edge_release] - if: "!cancelled()" + needs: [build_release_windows, build_release_windows_openmp, build_release_windows_x64, build_release_windows_openmp_x64, build_release_macos] + if: "!cancelled() && github.event_name == 'release'" runs-on: ubuntu-latest steps: - name: Download All Workflow Artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 - name: Get Release Upload URL id: get_upload_url env: - event_name: ${{ github.event_name }} event: ${{ toJson(github.event) }} - edge_upload_url: ${{ needs.update_edge_release.outputs.upload_url }} run: | - if [ "$event_name" = "release" ]; then - upload_url=$(echo "$event" | jq -r ".release.upload_url") - else - upload_url="$edge_upload_url" - fi + upload_url=$(echo "$event" | jq -r ".release.upload_url") echo "::set-output name=upload_url::$upload_url" echo "Upload URL: $upload_url" - name: Upload solvespace.exe @@ -234,15 +226,35 @@ jobs: asset_path: windows/solvespace.exe asset_name: solvespace.exe asset_content_type: binary/octet-stream - - name: Upload solvespace-openmp.exe + - name: Upload solvespace_single_core.exe + uses: actions/upload-release-asset@v1 + continue-on-error: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_upload_url.outputs.upload_url }} + asset_path: windows-openmp/solvespace_single_core.exe + asset_name: solvespace_single_core.exe + asset_content_type: binary/octet-stream + - name: Upload solvespace_x64.exe + uses: actions/upload-release-asset@v1 + continue-on-error: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_upload_url.outputs.upload_url }} + asset_path: windows/solvespace_x64.exe + asset_name: solvespace_x64.exe + asset_content_type: binary/octet-stream + - name: Upload solvespace_single_core_x64.exe uses: actions/upload-release-asset@v1 continue-on-error: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.get_upload_url.outputs.upload_url }} - asset_path: windows-openmp/solvespace-openmp.exe - asset_name: solvespace-openmp.exe + asset_path: windows-openmp/solvespace_single_core_x64.exe + asset_name: solvespace_single_core_x64.exe asset_content_type: binary/octet-stream - name: Upload SolveSpace.dmg uses: actions/upload-release-asset@v1 diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 000000000..5d8f1802d --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,179 @@ +name: Python + +on: + push: + branches: [ master ] + tags: [ v* ] + workflow_dispatch: + +jobs: + python-sdist: + name: python sdist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: git submodule update --init extlib/mimalloc extlib/eigen + - uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Pack + shell: bash + run: | + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + version="${GITHUB_REF##*/}" + else + mkdir empty-build + cd empty-build + cmake .. -DENABLE_GUI=OFF -DENABLE_CLI=OFF -DENABLE_TESTS=OFF -DENABLE_COVERAGE=OFF -DENABLE_SANITIZERS=OFF -DENABLE_OPENMP=OFF + source version.env + cd .. + version="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.dev${GITHUB_RUN_NUMBER}" + fi + sed -i.bak "s/^version = .*/version = \"${version}\"/g" pyproject.toml && rm pyproject.toml.bak + python -m pip install -U setuptools build + python -m build --sdist + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist/*.tar.gz + python-wheel: + name: ${{ matrix.os_short }} python ${{ matrix.architecture }} cp${{ matrix.python_version }} + runs-on: ${{ matrix.os }} + timeout-minutes: 240 + strategy: + fail-fast: false + matrix: + os_arch: [ + "windows-ia32", + "windows-x64", + # "windows-arm64", + "macos-x86_64", + "macos-arm64", + "linux-x86_64", + "linux-aarch64", + ] + python_version: [ + "37", + "38", + "39", + "310", + "311", + "312", + "313", + ] + exclude: + - os_arch: "macos-arm64" + python_version: "37" + include: + - os_arch: "windows-ia32" + os: "windows-2022" + os_short: "windows" + architecture: "ia32" + cibuildwheel_architecture: "x86" + cmake_generator_platform: "Win32" + - os_arch: "windows-x64" + os: "windows-2022" + os_short: "windows" + architecture: "x64" + cibuildwheel_architecture: "AMD64" + cmake_generator_platform: "x64" + # - os_arch: "windows-arm64" + # os: "windows-2022" + # os_short: "windows" + # architecture: "arm64" + # cibuildwheel_architecture: "ARM64" + # cmake_generator_platform: "ARM64" + - os_arch: "macos-x86_64" + os: "macos-latest" + os_short: "macos" + cibuildwheel_architecture: "x86_64" + architecture: "x86_64" + - os_arch: "macos-arm64" + os: "macos-latest" + os_short: "macos" + cibuildwheel_architecture: "arm64" + architecture: "arm64" + - os_arch: linux-x86_64 + os: "ubuntu-22.04" + os_short: "linux" + cibuildwheel_architecture: "x86_64" + architecture: "x86_64" + - os_arch: linux-aarch64 + os: "ubuntu-22.04" + os_short: "linux" + cibuildwheel_architecture: "aarch64" + architecture: "aarch64" + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - run: git submodule update --init extlib/mimalloc extlib/eigen + - name: Set version + shell: bash + run: | + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + version="${GITHUB_REF##*/}" + else + mkdir empty-build + cd empty-build + cmake .. -DENABLE_GUI=OFF -DENABLE_CLI=OFF -DENABLE_TESTS=OFF -DENABLE_COVERAGE=OFF -DENABLE_SANITIZERS=OFF -DENABLE_OPENMP=OFF + source version.env + cd .. + version="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.dev${GITHUB_RUN_NUMBER}" + fi + sed -i.bak "s/^version = .*/version = \"${version}\"/g" pyproject.toml && rm pyproject.toml.bak + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + if: matrix.architecture == 'aarch64' + with: + platforms: arm64 + - name: Build wheels + uses: pypa/cibuildwheel@v2.22.0 + env: + CIBW_BUILD: "cp${{ matrix.python_version }}-*" + CIBW_PLATFORM: "${{ matrix.os_short }}" + CIBW_BUILD_VERBOSITY: "1" + CIBW_ARCHS: "${{ matrix.cibuildwheel_architecture }}" + CIBW_ENVIRONMENT_WINDOWS: > + CMAKE_GENERATOR="Visual Studio 17 2022" + CMAKE_GENERATOR_PLATFORM="${{ matrix.cmake_generator_platform }}" + CIBW_ENVIRONMENT_PASS_WINDOWS: "CMAKE_GENERATOR CMAKE_GENERATOR_PLATFORM" + CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET=10.12 + - uses: actions/upload-artifact@v4 + with: + name: wheel-cp${{ matrix.python_version }}-${{ matrix.os_short }}-${{ matrix.architecture }} + path: | + ./wheelhouse/*.whl + publish-pypi: + name: publish to PyPi + needs: [ + python-sdist, + python-wheel, + ] + runs-on: ubuntu-22.04 + steps: + - uses: actions/download-artifact@v4 + with: + path: prebuilds + - name: prepare + shell: bash + run: | + mkdir dist + ls prebuilds + mv prebuilds/*/* dist + ls dist + - name: Publish wheels to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository-url: https://test.pypi.org/legacy/ + attestations: false + verbose: true + - name: Publish wheels to PyPI + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + attestations: false + verbose: true diff --git a/.github/workflows/source-tarball.yml b/.github/workflows/source-tarball.yml new file mode 100644 index 000000000..588524e28 --- /dev/null +++ b/.github/workflows/source-tarball.yml @@ -0,0 +1,56 @@ +name: Source Tarball + +on: + release: + types: + - created + +jobs: + create_tarball: + name: Create & Upload Tarball + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + - name: Pack Tarball + id: pack_tarball + run: | + version="${GITHUB_REF#refs/tags/v}" + dir_name="solvespace-${version}" + archive_name="${dir_name}.tar.xz" + 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_path::${archive_path}" + + cd .. + tar \ + --exclude-vcs \ + --transform "s:^solvespace:${dir_name}:" \ + -cvaf \ + ${archive_path} \ + solvespace + - name: Get Release Upload URL + id: get_upload_url + env: + event: ${{ toJson(github.event) }} + run: | + upload_url=$(echo "$event" | jq -r ".release.upload_url") + echo "::set-output name=upload_url::$upload_url" + echo "Upload URL: $upload_url" + - name: Upload Tarball + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_upload_url.outputs.upload_url }} + asset_path: ${{ steps.pack_tarball.outputs.archive_path }} + asset_name: ${{ steps.pack_tarball.outputs.archive_name }} + asset_content_type: binary/octet-stream diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1ca64b3a8..2f20e6941 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,20 +12,20 @@ on: jobs: test_ubuntu: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest name: Test Ubuntu steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Dependencies run: .github/scripts/install-ubuntu.sh - name: Build & Test run: .github/scripts/build-ubuntu.sh test_windows: - runs-on: windows-2019 + runs-on: windows-2022 name: Test Windows steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Dependencies run: .github/scripts/install-windows.sh shell: bash @@ -34,11 +34,28 @@ jobs: shell: bash test_macos: - runs-on: macos-10.15 + runs-on: macos-latest name: Test macOS steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install Dependencies run: .github/scripts/install-macos.sh ci - name: Build & Test 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@v4 + with: + submodules: true + fetch-depth: 0 + - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: "solvespace.flatpak" + manifest-path: "pkg/flatpak/com.solvespace.SolveSpace.json" + cache-key: flatpak-builder-${{ github.sha }} diff --git a/.github/workflows/wasmlib.yml b/.github/workflows/wasmlib.yml new file mode 100644 index 000000000..8bf09a964 --- /dev/null +++ b/.github/workflows/wasmlib.yml @@ -0,0 +1,75 @@ +name: WASM Library + +on: + push: + branches: [ master ] + tags: [ v* ] + workflow_dispatch: + +jobs: + build-wasmlib: + name: WASM library + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: git submodule update --init extlib/mimalloc extlib/eigen + - name: Pack + shell: bash + run: | + .github/scripts/build-wasmlib.sh + cd build-wasmlib/bin + rm libmimalloc.a || true + zip -r slvs-wasmlib.zip . + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: slvs-wasmlib + path: build-wasmlib/bin/slvs-wasmlib.zip + publish-wasmlib: + name: publish WASM library + needs: [ + build-wasmlib, + ] + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - run: git submodule update --init extlib/mimalloc extlib/eigen + - uses: actions/download-artifact@v4 + with: + path: artifacts + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: '14.x' + registry-url: https://registry.npmjs.org/ + - name: prepare + shell: bash + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + git fetch --unshallow --tags + + ls artifacts + unzip artifacts/slvs-wasmlib/slvs-wasmlib.zip -d js + ls js + + # bump version + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + version="${GITHUB_REF##*/}" + else + mkdir empty-build + cd empty-build + cmake .. -DENABLE_GUI=OFF -DENABLE_CLI=OFF -DENABLE_TESTS=OFF -DENABLE_COVERAGE=OFF -DENABLE_SANITIZERS=OFF -DENABLE_OPENMP=OFF + source version.env + cd .. + version="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-dev.${GITHUB_RUN_NUMBER}" + is_dev="1" + fi + jq --arg version "${version}" '.version = $version' package.json > package.json.tmp + mv package.json.tmp js/package.json + cd js + if [ "${is_dev}" == "1" ]; then + npm publish --access public --tag dev + else + npm publish --access public --tag stable + fi diff --git a/.gitignore b/.gitignore index 4f76b2668..9bf6e0f21 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,13 @@ /obj-*/ /*.slvs .vscode/ +.cache/ + +# Visual Studio +out/ +.vs/ +CMakeSettings.json +__pycache__/ +solvespace.egg-info/ +dist/ +.DS_Store diff --git a/.gitmodules b/.gitmodules index 71384a1a8..84b25a893 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,7 +7,7 @@ url = https://github.com/glennrp/libpng [submodule "extlib/freetype"] path = extlib/freetype - url = https://git.savannah.nongnu.org/r/freetype/freetype2.git/ + url = https://github.com/freetype/freetype.git [submodule "extlib/libdxfrw"] path = extlib/libdxfrw url = https://github.com/solvespace/libdxfrw.git @@ -23,3 +23,6 @@ [submodule "extlib/mimalloc"] path = extlib/mimalloc url = https://github.com/microsoft/mimalloc +[submodule "extlib/eigen"] + path = extlib/eigen + url = https://gitlab.com/libeigen/eigen.git diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..2264671f1 --- /dev/null +++ b/.mailmap @@ -0,0 +1,25 @@ +Bauke Conijn +Bauke Conijn + +EvilSpirit + +Isaac Garzón +Isaac Garzón +Isaac Garzón +Isaac Garzón + +luz paz + +Maximilian Federle +Maximilian Federle + +Not4Sure + +Paul Kahler <14852918+phkahler@users.noreply.github.com> + +Rylie Pavlik +Rylie Pavlik +Rylie Pavlik +Rylie Pavlik +Rylie Pavlik +Rylie Pavlik diff --git a/CHANGELOG.md b/CHANGELOG.md index f70812eca..b2d1c2b69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,284 +1,422 @@ Changelog ========= +3.2 +--- + +Geometric Modelling Kernel (NURBS) + +* Improve the difference boolean operations. +* Extrude groups have a checkbox to allow skewing - 3 DoF for the extrude direction. + +Constraints (new and improved): + +* Add Parallel and Perpendicular constraints for 2 faces. +* The equal angle constraint is moved to the `N` shortcut and menu item to allow equal length (`Q`) to be applied to three or four lines. +* Allow tangent on arcs/lines/splines without coincident endpoints. + +Allow these constraints to be applied to more entities at once: + +* More than two line Segments - equal length. +* More than two Arcs and/or circles - equal diameter/radius. +* Any number of Lines - horizontal or vertical. +* More than two points - horizontal or vertical. +* Point on face can be applied to a point and 1-3 faces at once. +* More than two points coincident. + +Sketching + +* `Image` sketch elements are not copied in 3d groups (extrude, lathe, revolve, helix) by default. `Toggle Construction` for an image to get the old behavior. +* New option to use radius instead of diameter for circle and arc dimensions. +* Dimensions on arcs default to radius instead of diameter. Uncheck `use radius` in the Property Browser to get diameters. +* `Text in True Type Font` elements now support basic kerning. Enable it with `apply kerning` in the Property Browser. + +Translations (now in 10 languages!) + +* Added Czech cs_CZ. +* Added Japanese ja_JP. +* Update translation for French fr_FR, Russian ru_RU, Chinese zh_CN and Ukrainian uk_UA. + +Other User interface changes: + +* `CTRL+Tab` hides/shows the toolbar. +* Marquee selection of line segments is now precise. +* Speed up the animation when moving the view, for example when pressing `F2` or `F3`. +* Pressing ESC while drawing a sketch entity now deletes the entity rather than completing it. +* `CTRL+Shift+S` shortcut for "Save As..." +* New option "use camera mouse navigation" for camera (instead of the default model) rotation navigation. +* Sketches can be displayed with only dimensions visible (the button controlling visibility of constraints in the Property Browser has a new state). +* More entity types described in the text screens. +* A new option to `display the full path in the title bar`; or not. +* Handle smooth scrolling (mouse wheel zoom) on GTK/Linux. +* The embedded vector font (used for the UI and labels): + * now has many new and improved Chinese characters + * includes the Greek alphabet + * improved "є" + * added superscript 5 "⁵" + * fixed crash when typing "£" +* The animation speed when changing the 3D view is configurable. +* Add an 'only unconstrained' option on the Property Browser home screen that shows only unconstrained groups. + + +Other + +* STEP files export as solid model(s). +* Merged and improved the experimental Web version (Emscripten port). +* Merged a QT front end as an option for Linux builds. +* Better Flatpack support. +* Several bug fixes and usability improvements. +* Allow 32 bit SolveSpace to access up to 4GB of RAM to allow working on larger projects. + +Bug fixes: + +* `Paste Transformed` on elements that contain a point-line distance does not flip any more. +* Fix saving assemblies when opened with a relative path on the command line. +* Windows: The Property Browser remains visible when going full screen (CTRL-F11). +* Fix crash when linking .STL and .EMN files with extensions in capital letters. +* Mac: Don't crash on file ops without changing dir. +* Fix incorrect arc handling when linking IDF files. +* Windows: avoid (sometimes) showing multiple messages when deleting an entity causes other entities to be removed. + +3.1 +--- + +Constraints: + +* Arcs length ratio and difference. +* Arc & Line length ratio and difference. +* Allow comments to be associated with point entities. + +Sketching: + +* Support for pan, zoom and rotate trackpad gestures on macOS +* Add "exploded view" to sketches via "\\" key. Shows sketch elements separated + by a configurable distance perpendicular to the sketch plane. +* Added Feet-Inches as a unit of measure. Inputs are still in inches. + But the display shows feet, inches, and fraction of an inch. +* Added an optional "pitch" parameter to helix extrusions (in the text window) +* Allow use of Point & Normal to define "sketch-in-new-workplane". +* Update "Property Browser" live while dragging the sketch. + +MISC: + +* Add a link to the GitHub commit from which SolveSpace was built in the Help + menu. +* Make all points, vectors and normals shown in the Property Browser into + active links. This makes them explorable and selectable. +* Load 16bit PNG images correctly by re-scaling to 8bit. +* Fixed hang when trying to display characters missing from the embedded font. +* The main window vertical size can be as small as the toolbar. +* Configurable "SafeHeight" parameter instead of the fixed 5mm for G-code export. +* Add Spanish / Argentina translation. +* Move "perspective factor", "lighting direction" and "explode distance" from + the "configuration" screen to the "view" screen. +* Add a "∆" suffix to groups which have "force to triangle mesh" ticked +* Gray the group name in the text window for groups with suppressed solid model. +* Added the ability to Link STL files. +* When linking circuit boards (IDF .emn files) show keepout regions as construction entities. + +Performance: + +* Speed up sketches with many constraints by roughly 8x by using the Eigen + library in the solver. The maximum unknowns increased from 1024 to 2048. +* Add a "suppress dof calculation" setting to groups - increases performance for + complex sketches. +* More changes to the ID list implementation. + 3.0 --- New sketch features: - * New intersection boolean operation for solid models. - * New groups, revolution and helical extrusion. - * Extrude, lathe, translate and rotate groups can use the "assembly" - boolean operation, to increase performance. - * The solid model of extrude and lathe groups can be suppressed, - for splitting a single model in multiple parts to export, - or if only the generated entities are desired, without the mesh. - * Translate and rotate groups can create n-dimensional arrays using - the "difference" and "assembly" boolean operations. - * A new sketch in workplane group can be created based on existing workplane. - * TTF text request has two additional points on the right side, which allow - constraining the width of text. - * Image requests can now be created, similar to TTF text requests. - This replaces the "style → background image" feature. - * Irrelevant points (e.g. arc center point) are not counted when estimating - the bounding box used to compute chord tolerance. - * When adding a constraint which has a label and is redundant with another - constraint, the constraint is added as a reference, avoiding an error. - * Datum points can be copied and pasted. - * "Split Curves at Intersection" can now split curves at point lying on curve, - not just at intersection of two curves. - * Property browser now shows amount of degrees of freedom in group list. - It also shows a yellow "err" if the sketch has problems (e.g. self - intersecting) that would propagate in subsequent groups. - * It is now possible to press "g" to toggle construction on new objects while - they are still being drawn. + +* New intersection boolean operation for solid models. +* New groups, revolution and helical extrusion. +* Extrude, lathe, translate and rotate groups can use the "assembly" + boolean operation, to increase performance. +* The solid model of extrude and lathe groups can be suppressed, + for splitting a single model in multiple parts to export, + or if only the generated entities are desired, without the mesh. +* Translate and rotate groups can create n-dimensional arrays using + the "difference" and "assembly" boolean operations. +* A new sketch in workplane group can be created based on existing workplane. +* TTF text request has two additional points on the right side, which allow + constraining the width of text. +* Image requests can now be created, similar to TTF text requests. + This replaces the "style → background image" feature. +* Irrelevant points (e.g. arc center point) are not counted when estimating + the bounding box used to compute chord tolerance. +* When adding a constraint which has a label and is redundant with another + constraint, the constraint is added as a reference, avoiding an error. +* Datum points can be copied and pasted. +* "Split Curves at Intersection" can now split curves at point lying on curve, + not just at intersection of two curves. +* Property browser now shows amount of degrees of freedom in group list. + It also shows a yellow "err" if the sketch has problems (e.g. self + intersecting) that would propagate in subsequent groups. +* It is now possible to press "g" to toggle construction on new objects while + they are still being drawn. +* Allow right click to end sketching of all entities. New constraint features: - * When dragging an arc or rectangle point, it will be automatically - constrained to other points with a click. - * When selecting a constraint, the requests it constraints can be selected - in the text window. - * When selecting an entity, the constraints applied to it can be selected - in the text window. - * Distance constraint labels can now be formatted to use SI prefixes. - Values are edited in the configured unit regardless of label format. - * When creating a constraint, if an exactly identical constraint already - exists, it is now selected instead of adding a redundant constraint. - * It is now possible to turn off automatic creation of horizontal/vertical - constraints on line segments. - * Automatic creation of constraints no longer happens if the constraint - would have been redundant with other ones. - * New option to open the constraint editor for newly created constraints - with a value. - * New "redundant constraint timeout (in ms)" option to prevent UI freeze - when looking for redundant constraints. - * Swap vertical and horizontal constraints when pasting rotated by 90/270 - degrees. + +* When dragging an arc or rectangle point, it will be automatically + constrained to other points with a click. +* When selecting a constraint, the requests it constraints can be selected + in the text window. +* When selecting an entity, the constraints applied to it can be selected + in the text window. +* Distance constraint labels can now be formatted to use SI prefixes. + Values are edited in the configured unit regardless of label format. +* When creating a constraint, if an exactly identical constraint already + exists, it is now selected instead of adding a redundant constraint. +* It is now possible to turn off automatic creation of horizontal/vertical + constraints on line segments. +* Automatic creation of constraints no longer happens if the constraint + would have been redundant with other ones. +* New option to open the constraint editor for newly created constraints + with a value. +* New "redundant constraint timeout (in ms)" option to prevent UI freeze + when looking for redundant constraints. +* Swap vertical and horizontal constraints when pasting rotated by 90/270 + degrees. New export/import features: - * Link IDF circuit boards in an assembly (.emn files) - * Three.js: allow configuring projection for exported model, and initially - use the current viewport projection. - * Wavefront OBJ: a material file is exported alongside the model, containing - mesh color information. - * DXF/DWG: 3D DXF files are imported as construction entities, in 3d. - * [ADDED 2019-02-25](https://github.com/solvespace/solvespace/pull/384) and [REMOVED 2020-11-13](https://github.com/solvespace/solvespace/issues/795): - Q3D: [Q3D](https://github.com/q3k/q3d/) triangle meshes can now be - exported. This format allows to easily hack on triangle mesh data created - in SolveSpace, supports colour information and is more space efficient than - most other formats. - * VRML (WRL) triangle meshes can now be exported, useful for e.g. [KiCAD](http://kicad.org). - * Export 2d section: custom styled entities that lie in the same - plane as the exported section are included. - * Added ExportBackgroundColor in configuration for EPS, PDF, and SVG files. - * STEP export includes object colors and transparency. + +* Link IDF circuit boards in an assembly (.emn files) +* Three.js: allow configuring projection for exported model, and initially + use the current viewport projection. +* Wavefront OBJ: a material file is exported alongside the model, containing + mesh color information. +* DXF/DWG: 3D DXF files are imported as construction entities, in 3d. +* VRML (WRL) triangle meshes can now be exported, useful for e.g. [KiCAD](http://kicad.org). +* Export 2d section: custom styled entities that lie in the same + plane as the exported section are included. +* Added ExportBackgroundColor in configuration for EPS, PDF, and SVG files. +* STEP export includes object colors and transparency. +* Default "line styles" have a new "export these objects" option. New rendering features: - * The "Show/hide hidden lines" button is now a tri-state button that allows - showing all lines (on top of shaded mesh), stippling occluded lines - or not drawing them at all. - * The "Show/hide outlines" button is now independent from "Show/hide edges". - * "View | Darken Inactive Solids" added. When turned off and a "sketch in plane" - group is active solids form previous groups will not be "darkened" (have the - s000d-#def-dim-solid style applied to them). + +* The "Show/hide hidden lines" button is now a tri-state button that allows + showing all lines (on top of shaded mesh), stippling occluded lines + or not drawing them at all. +* The "Show/hide outlines" button is now independent from "Show/hide edges". +* "View | Darken Inactive Solids" added. When turned off and a "sketch in plane" + group is active solids form previous groups will not be "darkened" (have the + s000d-#def-dim-solid style applied to them). New measurement/analysis features: - * New choice for base unit, meters. - * New command for measuring total length of selected entities, - "Analyze → Measure Perimeter". - * New command for measuring center of mass, with live updates as the sketch - changes, "Analyze → Center of Mass". - * New option for displaying areas of closed contours. - * When calculating volume of the mesh, volume of the solid from the current - group is now shown alongside total volume of all solids. - * When calculating area, and faces are selected, calculate area of those faces - instead of the closed contour in the sketch. - * When selecting a point and a line, projected distance to current - workplane is displayed. + +* New choice for base unit, meters. +* New command for measuring total length of selected entities, + "Analyze → Measure Perimeter". +* New command for measuring center of mass, with live updates as the sketch + changes, "Analyze → Center of Mass". +* New option for displaying areas of closed contours. +* When calculating volume of the mesh, volume of the solid from the current + group is now shown alongside total volume of all solids. +* When calculating area, and faces are selected, calculate area of those faces + instead of the closed contour in the sketch. +* When selecting a point and a line, projected distance to current + workplane is displayed. Other new features: - * Improvements to the text window for selected entities and constraints. - * Ambient light source added in text window to allow flat shaded renderings. - * New command-line interface, for batch exporting and more. - * The graphical interface now supports HiDPI screens on every OS. - * New option to lock Z axis to be always vertical, like in SketchUp. - * New button to hide all construction entities. - * New link to match the on-screen size of the sketch with its actual size, - "view → set to full scale". - * When zooming to fit, constraints are also considered. - * Ctrl-clicking entities now deselects them, as the inverse of clicking. - * When clicking on an entity that shares a place with other entities, - the entity from the current group is selected. - * When dragging an entity that shares a place with other entities, - the entity from a request is selected. For example, dragging a point on - a face of an extrusion coincident with the source sketch plane will - drag the point from the source sketch. - * The default font for TTF text is now Bitstream Vera Sans, which is - included in the resources such that it is available on any OS. - * In expressions, numbers can contain the digit group separator, "_". - * The "=" key is bound to "Zoom In", like "+" key. - * The numpad decimal separator key is bound to "." regardless of locale. - * On Windows, full-screen mode is implemented. - * On Linux, native file chooser dialog can be used. - * New edit menu items "Line Styles", "View Projection" and "Configuration" - that are shortcuts to the respective configuration screens. - * New cmake build options using -DENABLE_OPENMP=yes and -DENABLE_LTO=yes - to enable support for multi-threading and link-time optimization. - * "Shift+Scroll" for ten times finer zoom. - * Chinese translation + +* Improvements to the text window for selected entities and constraints. +* Ambient light source added in text window to allow flat shaded renderings. +* New command-line interface, for batch exporting and more. +* The graphical interface now supports HiDPI screens on every OS. +* New option to lock Z axis to be always vertical when rotating the view, + a.k.a. "turntable navigation". +* New button to hide all construction entities. +* New link to match the on-screen size of the sketch with its actual size, + "view → set to full scale". +* When zooming to fit, constraints are also considered. +* Ctrl-clicking entities now deselects them, as the inverse of clicking. +* When clicking on an entity that shares a place with other entities, + the entity from the current group is selected. +* When dragging an entity that shares a place with other entities, + the entity from a request is selected. For example, dragging a point on + a face of an extrusion coincident with the source sketch plane will + drag the point from the source sketch. +* The default font for TTF text is now Bitstream Vera Sans, which is + included in the resources such that it is available on any OS. +* In expressions, numbers can contain the digit group separator, "_". +* The "=" key is bound to "Zoom In", like "+" key. +* The numpad decimal separator key is bound to "." regardless of locale. +* On Windows, full-screen mode is implemented. +* On Linux, native file chooser dialog can be used. +* New edit menu items "Line Styles", "View Projection" and "Configuration" + that are shortcuts to the respective configuration screens. +* New cmake build options using -DENABLE_OPENMP=yes and -DENABLE_LTO=yes + to enable support for multi-threading and link-time optimization. +* "Shift+Scroll" for ten times finer zoom. +* Translations: Chinese, French, German, Russian, Turkish, Ukrainian. Bugs fixed: - * Fixed broken --view options for command line thumbnail image creation. - * Some errors in Triangulation of surfaces. - * Some NURNS boolean operations that failed particularly on surfaces - created with Lathe, Revolve, or Helix. - * Segfault in Remove Spline Point context menu. - * A point in 3d constrained to any line whose length is free no longer - causes the line length to collapse. - * Curve-line constraints (in 3d), parallel constraints (in 3d), and - same orientation constraints are more robust. - * Adding some constraints (vertical, midpoint, etc) twice errors out - immediately, instead of later and in a confusing way. - * Constraining a newly placed point to a hovered entity does not cause - spurious changes in the sketch. - * Points highlighted with "Analyze → Show Degrees of Freedom" are drawn - on top of all other geometry. - * A step rotate/translate group using a group forced to triangle mesh - as a source group also gets forced to triangle mesh. - * Paste Transformed with a negative scale does not invert arcs. - * The tangent arc now modifies the original entities instead of deleting - them, such that their constraints are retained. - * When linking a sketch file, missing custom styles are now imported from - the linked file. - * 3Dconnexion SpaceMouse should now work (on Windows and macOS X). - * Improved NURBS boolean operations on curved surfaces in some cases. - * Show only usable fonts in the font selector. + +* Fixed broken --view options for command line thumbnail image creation. +* Some errors in Triangulation of surfaces. +* Some NURNS boolean operations that failed particularly on surfaces + created with Lathe, Revolve, or Helix. +* Segfault in Remove Spline Point context menu. +* A point in 3d constrained to any line whose length is free no longer + causes the line length to collapse. +* Curve-line constraints (in 3d), parallel constraints (in 3d), and + same orientation constraints are more robust. +* Adding some constraints (vertical, midpoint, etc) twice errors out + immediately, instead of later and in a confusing way. +* Constraining a newly placed point to a hovered entity does not cause + spurious changes in the sketch. +* Points highlighted with "Analyze → Show Degrees of Freedom" are drawn + on top of all other geometry. +* A step rotate/translate group using a group forced to triangle mesh + as a source group also gets forced to triangle mesh. +* Paste Transformed with a negative scale does not invert arcs. +* The tangent arc now modifies the original entities instead of deleting + them, such that their constraints are retained. +* When linking a sketch file, missing custom styles are now imported from + the linked file. +* 3Dconnexion SpaceMouse should now work (on Windows and macOS X). +* Improved NURBS boolean operations on curved surfaces in some cases. +* Show only usable fonts in the font selector. 2.x --- Bug fixes: - * Do not crash when changing an unconstrained lathe group between - union and difference modes. + +* Do not crash when changing an unconstrained lathe group between + union and difference modes. 2.3 --- Bug fixes: - * Do not crash when applying a symmetry constraint to two points. - * Fix TTF font metrics again (properly this time). - * Fix the "draw back faces in red" option. - * Fix export of wireframe as 3D DXF. - * Various minor crashes. + +* Do not crash when applying a symmetry constraint to two points. +* Fix TTF font metrics again (properly this time). +* Fix the "draw back faces in red" option. +* Fix export of wireframe as 3D DXF. +* Various minor crashes. 2.2 --- Other new features: - * OS X: support 3Dconnexion devices (SpaceMouse, SpaceNavigator, etc). - * GTK: files with uppercase extensions can be opened. + +* OS X: support 3Dconnexion devices (SpaceMouse, SpaceNavigator, etc). +* GTK: files with uppercase extensions can be opened. Bug fixes: - * Do not remove autosaves after successfully opening a file, preventing - data loss in case of two abnormal terminations in a row. - * Do not crash when changing autosave interval. - * Unbreak the "Show degrees of freedom" command. - * Three.js: correctly respond to controls when browser zoom is used. - * OS X: do not completely hide main window when defocused. - * GTK: unbreak 3Dconnexion support. - * When pasting transformed entities, multiply constraint values by scale. - * Fix TTF font metrics (restore the behavior from version 2.0). - * Forcibly show the current group once we start a drawing operation. - * DXF export: always declare layers before using them. - * Do not truncate operations on selections to first 32 selected entities. - * Translate and rotate groups inherit the "suppress solid model" setting. - * DXF: files with paths containing non-ASCII or spaces can be exported - or imported. - * Significantly improved performance when dragging an entity. - * Various crashes and minor glitches. + +* Do not remove autosaves after successfully opening a file, preventing + data loss in case of two abnormal terminations in a row. +* Do not crash when changing autosave interval. +* Unbreak the "Show degrees of freedom" command. +* Three.js: correctly respond to controls when browser zoom is used. +* OS X: do not completely hide main window when defocused. +* GTK: unbreak 3Dconnexion support. +* When pasting transformed entities, multiply constraint values by scale. +* Fix TTF font metrics (restore the behavior from version 2.0). +* Forcibly show the current group once we start a drawing operation. +* DXF export: always declare layers before using them. +* Do not truncate operations on selections to first 32 selected entities. +* Translate and rotate groups inherit the "suppress solid model" setting. +* DXF: files with paths containing non-ASCII or spaces can be exported + or imported. +* Significantly improved performance when dragging an entity. +* Various crashes and minor glitches. 2.1 --- New sketch features: - * Lathe groups create circle and face entities. - * New toolbar button for creating lathe groups. - * Chord tolerance is separated into two: display chord tolerance (specified - in percents, relative to model bounding box), and export chord tolerance - (specified in millimeters as absolute value). - * Bezier spline points can be added and removed after the spline is created. - * When an unconstrained extrusion is switched between "union" and - "difference", its normal is flipped. - * Groups can be added in the middle of the stack. Note that this results - in files incompatible with version 2.0. - * Active group can be removed. - * Removing an imported group does not cause all subsequent groups to also - be removed. - * When a new group with a solid is created, the color is taken from - a previous group with a solid, if any. - * Entities in a newly active group do not become visible. - * When entities are selected, "Zoom to fit" zooms to fit only these - entities and not the entire sketch. - * Zero-length edges are reported with a "zero-length error", not - "points not all coplanar". + +* Lathe groups create circle and face entities. +* New toolbar button for creating lathe groups. +* Chord tolerance is separated into two: display chord tolerance (specified + in percents, relative to model bounding box), and export chord tolerance + (specified in millimeters as absolute value). +* Bezier spline points can be added and removed after the spline is created. +* When an unconstrained extrusion is switched between "union" and + "difference", its normal is flipped. +* Groups can be added in the middle of the stack. Note that this results + in files incompatible with version 2.0. +* Active group can be removed. +* Removing an imported group does not cause all subsequent groups to also + be removed. +* When a new group with a solid is created, the color is taken from + a previous group with a solid, if any. +* Entities in a newly active group do not become visible. +* When entities are selected, "Zoom to fit" zooms to fit only these + entities and not the entire sketch. +* Zero-length edges are reported with a "zero-length error", not + "points not all coplanar". New constraint features: - * Height of the font used for drawing constraint labels can be changed. - * New constraint, length difference, placed with J. - (Patch by Peter Ruevski) - * Horizontal/vertical constraints are automatically added if a line segment - is close enough to being horizontal/vertical. This can be disabled by - holding Ctrl. - * Reference dimensions and angles can be placed with Shift+D and Shift+N. - * Copying and pasting entities duplicates any constraints that only involve - entities in the clipboard, as well as selected comments. - * Diameter constraints can be shown as radius. - * The "pi" identifier can be used in expressions. - * Constraint labels can be snapped to grid. - * Integer angles are displayed without trailing zeroes. - * Angle constraints have proper reference lines and arrowheads. - * Extension lines are drawn for point-line distance constraints. + +* Height of the font used for drawing constraint labels can be changed. +* New constraint, length difference, placed with J. + (Patch by Peter Ruevski) +* Horizontal/vertical constraints are automatically added if a line segment + is close enough to being horizontal/vertical. This can be disabled by + holding Ctrl. +* Reference dimensions and angles can be placed with Shift+D and Shift+N. +* Copying and pasting entities duplicates any constraints that only involve + entities in the clipboard, as well as selected comments. +* Diameter constraints can be shown as radius. +* The "pi" identifier can be used in expressions. +* Constraint labels can be snapped to grid. +* Integer angles are displayed without trailing zeroes. +* Angle constraints have proper reference lines and arrowheads. +* Extension lines are drawn for point-line distance constraints. New solver features: - * Sketches with redundant and unsolvable constraints are distinguished. - * New group setting, "allow redundant constraints". Note that it makes - the solver less stable. + +* Sketches with redundant and unsolvable constraints are distinguished. +* New group setting, "allow redundant constraints". Note that it makes + the solver less stable. New rendering and styling features: - * New line style parameter: stippling, based on ISO 128. - * Outlines of solids can be drawn in a particular style (by default, thick - lines) controlled by the "Show outline of solid model" button. - * Occluded edges can be drawn in a particular style (by default, stippled - with short dashes) controlled by the "Show hidden lines" button. - * Solids can be made transparent. + +* New line style parameter: stippling, based on ISO 128. +* Outlines of solids can be drawn in a particular style (by default, thick + lines) controlled by the "Show outline of solid model" button. +* Occluded edges can be drawn in a particular style (by default, stippled + with short dashes) controlled by the "Show hidden lines" button. +* Solids can be made transparent. New export/import features: - * The old "import" command (for .slvs files) is renamed to "link". - * If a linked .slvs file is not found, first the relative path recorded - in the .slvs file is checked and then the absolute path; this is - an inversion of the previously used order. If it is still not found, - a dialog appears offering to locate it. - * DXF and DWG files can be imported, with point-coincident, horizontal and - vertical constraints automatically inferred from geometry, and distance - and angle constraints created when a dimension placed against geometry - exists. - * Triangle mesh can be exported for viewing in the browser through WebGL. - * Export dialogs remember the last file format used, and preselect it. - * Exported DXF files have exact circles, arcs and splines instead of - a piecewise linear approximation (unless hidden line removal was needed). - * Exported DXF files preserve color and line thickness. - * In exported DXF files, constraints are represented as DXF dimensions, - instead of piecewise linear geometry. - * When exporting 2d views, overlapping lines are removed. + +* The old "import" command (for .slvs files) is renamed to "link". +* If a linked .slvs file is not found, first the relative path recorded + in the .slvs file is checked and then the absolute path; this is + an inversion of the previously used order. If it is still not found, + a dialog appears offering to locate it. +* DXF and DWG files can be imported, with point-coincident, horizontal and + vertical constraints automatically inferred from geometry, and distance + and angle constraints created when a dimension placed against geometry + exists. +* Triangle mesh can be exported for viewing in the browser through WebGL. +* Export dialogs remember the last file format used, and preselect it. +* Exported DXF files have exact circles, arcs and splines instead of + a piecewise linear approximation (unless hidden line removal was needed). +* Exported DXF files preserve color and line thickness. +* In exported DXF files, constraints are represented as DXF dimensions, + instead of piecewise linear geometry. +* When exporting 2d views, overlapping lines are removed. Other new features: - * Native Linux (GTK 2 and GTK 3) and Mac OS X ports. - * Automatically save and then restore sketches if SolveSpace crashes. - (Patch by Marc Britten) - * Unicode is supported everywhere (filenames, group names, TTF text, - comments), although RTL scripts and scripts making heavy use of ligatures - are not rendered correctly. - * The vector font is grid-fitted when rendered on screen to make it easier - to read regardless of its size. + +* Native Linux (GTK 2 and GTK 3) and Mac OS X ports. +* Automatically save and then restore sketches if SolveSpace crashes. + (Patch by Marc Britten) +* Unicode is supported everywhere (filenames, group names, TTF text, + comments), although RTL scripts and scripts making heavy use of ligatures + are not rendered correctly. +* The vector font is grid-fitted when rendered on screen to make it easier + to read regardless of its size. 2.0 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index bdd36705d..11adeb5f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ # cmake configuration -cmake_minimum_required(VERSION 3.9...3.19) +cmake_minimum_required(VERSION 3.18...3.25) + +cmake_policy(SET CMP0094 NEW) # for Python*_FIND_STRATEGY=LOCATION if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) message(FATAL_ERROR @@ -8,10 +10,9 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) " mkdir build && cd build && cmake ..") endif() -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") -cmake_policy(SET CMP0048 OLD) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED YES) @@ -25,6 +26,11 @@ if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") endif() +if (APPLE) + # Docs say this must be set before the first project() call + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.12" CACHE STRING "macOS minimum supported version") +endif() + # project # NOTE TO PACKAGERS: The embedded git commit hash is critical for rapid bug triage when the builds @@ -34,16 +40,29 @@ include(GetGitCommitHash) # and instead uncomment the following, adding the complete git hash of the checkout you are using: # set(GIT_COMMIT_HASH 0000000000000000000000000000000000000000) -set(solvespace_VERSION_MAJOR 3) -set(solvespace_VERSION_MINOR 0) string(SUBSTRING "${GIT_COMMIT_HASH}" 0 8 solvespace_GIT_HASH) -project(solvespace LANGUAGES C CXX ASM) +project(solvespace + VERSION 3.2 + LANGUAGES C CXX ASM) + +if(EMSCRIPTEN) + # Enable optimization. Workaround for "too many locals" error when runs in the browser. + if(NOT CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_options(-O1) + endif() + + set(INCLUDE_NATIVE_COMPONENTS OFF) +else() + set(INCLUDE_NATIVE_COMPONENTS ON) +endif() set(ENABLE_GUI ON CACHE BOOL "Whether the graphical interface is enabled") -set(ENABLE_CLI ON CACHE BOOL +set(USE_QT_GUI OFF CACHE BOOL + "Whether the Qt library used to build graphical interface") +set(ENABLE_CLI ${INCLUDE_NATIVE_COMPONENTS} CACHE BOOL "Whether the command line interface is enabled") -set(ENABLE_TESTS ON CACHE BOOL +set(ENABLE_TESTS ${INCLUDE_NATIVE_COMPONENTS} CACHE BOOL "Whether the test suite will be built and run") set(ENABLE_COVERAGE OFF CACHE BOOL "Whether code coverage information will be collected") @@ -51,8 +70,11 @@ set(ENABLE_SANITIZERS OFF CACHE BOOL "Whether to enable Clang's AddressSanitizer and UndefinedBehaviorSanitizer") set(ENABLE_OPENMP OFF CACHE BOOL "Whether geometric operations will be parallelized using OpenMP") -set(ENABLE_LTO OFF CACHE BOOL +set(ENABLE_LTO OFF CACHE BOOL "Whether interprocedural (global) optimizations are enabled") +option(FORCE_VENDORED_Eigen3 + "Whether we should use our bundled Eigen even in the presence of a system copy" + OFF) set(OPENGL 3 CACHE STRING "OpenGL version to use (one of: 1 3)") @@ -84,6 +106,12 @@ endif() if(MINGW) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++") + if(CMAKE_SIZEOF_VOID_P EQUAL 4) + # 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; @@ -109,7 +137,12 @@ endif() if(ENABLE_OPENMP) find_package( OpenMP REQUIRED ) if(OPENMP_FOUND) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + add_library(slvs_openmp INTERFACE) + target_compile_options(slvs_openmp INTERFACE "SHELL:${OpenMP_CXX_FLAGS}") + target_link_libraries(slvs_openmp INTERFACE + ${OpenMP_CXX_LIBRARIES}) + target_include_directories(slvs_openmp SYSTEM INTERFACE + ${OpenMP_CXX_INCLUDE_DIRS}) message(STATUS "found OpenMP, compiling with flags: " ${OpenMP_CXX_FLAGS} ) endif() endif() @@ -120,44 +153,52 @@ endif() if(ENABLE_SANITIZERS) if(NOT SANITIZERS) - set(SANITIZERS "address;undefined") + if(MSVC) + if(MSVC_VERSION LESS 1928) + # Technically 1928 is also used for VS 16.8, but CMake doesn't have a way + # to distinguish patch versions of MSVC, so this is good enough + message(FATAL_ERROR "Using sanitizers with MSVC requires Visual Studio 16.9 or newer") + endif() + set(SANITIZERS "address") + else() + set(SANITIZERS "address;undefined") + endif() endif() - if("thread" IN_LIST SANITIZERS) - list(REMOVE_ITEM SANITIZERS "thread") - list(APPEND SANITIZE_OPTIONS thread) - endif() - if("address" IN_LIST SANITIZERS) - list(REMOVE_ITEM SANITIZERS "address") - list(APPEND SANITIZE_OPTIONS address) - endif() - if("undefined" IN_LIST SANITIZERS) - list(REMOVE_ITEM SANITIZERS "undefined") - list(APPEND SANITIZE_OPTIONS alignment bounds) - list(APPEND SANITIZE_OPTIONS shift signed-integer-overflow integer-divide-by-zero) - list(APPEND SANITIZE_OPTIONS null bool enum) - list(APPEND SANITIZE_OPTIONS return) - endif() - if(SANITIZERS) - message(FATAL_ERROR "Unknown sanitizer(s) ${SANITIZERS}") - else() - message(STATUS "Using sanitizer options ${SANITIZE_OPTIONS}") - endif() + foreach(option ${SANITIZERS}) + if(option STREQUAL "thread" AND NOT MSVC) + list(APPEND SANITIZE_OPTIONS thread) + elseif(option STREQUAL "address") + list(APPEND SANITIZE_OPTIONS address) + elseif(option STREQUAL "undefined" AND NOT MSVC) + list(APPEND SANITIZE_OPTIONS alignment bounds) + list(APPEND SANITIZE_OPTIONS shift signed-integer-overflow integer-divide-by-zero) + list(APPEND SANITIZE_OPTIONS null bool enum) + list(APPEND SANITIZE_OPTIONS return) + else() + message(FATAL_ERROR "Unknown sanitizer ${option}") + endif() + endforeach() - string(REPLACE ";" "," SANITIZE_OPTIONS "${SANITIZE_OPTIONS}") + message(STATUS "Using sanitizer options ${SANITIZE_OPTIONS}") - if (NOT APPLE) - set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZE_OPTIONS} -fno-sanitize-recover=address,undefined") + if(MSVC) + string(REPLACE ";" " /fsanitize=" SANITIZE_OPTIONS "${SANITIZE_OPTIONS}") + set(SANITIZE_FLAGS "/Zi /fsanitize=${SANITIZE_OPTIONS}") else() + string(REPLACE ";" "," SANITIZE_OPTIONS "${SANITIZE_OPTIONS}") set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZE_OPTIONS}") + if (NOT APPLE) + set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fno-sanitize-recover=address,undefined") + endif() endif() if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls") elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fuse-ld=gold") - else() - message(FATAL_ERROR "Sanitizers are only available when using GCC or Clang") + elseif(NOT MSVC) + message(FATAL_ERROR "Sanitizers are only available when using MSVC, GCC, or Clang") endif() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZE_FLAGS}") @@ -167,82 +208,142 @@ endif() # common dependencies if(APPLE) - set(CMAKE_FIND_FRAMEWORK LAST) + set(CMAKE_FIND_FRAMEWORK FIRST) endif() -message(STATUS "Using in-tree libdxfrw") -add_subdirectory(extlib/libdxfrw) +if(ENABLE_GUI OR ENABLE_CLI) + message(STATUS "Using in-tree libdxfrw") + add_subdirectory(extlib/libdxfrw) +endif() message(STATUS "Using in-tree mimalloc") set(MI_OVERRIDE OFF CACHE BOOL "") set(MI_BUILD_SHARED OFF CACHE BOOL "") set(MI_BUILD_OBJECT OFF CACHE BOOL "") set(MI_BUILD_TESTS OFF CACHE BOOL "") +if(EMSCRIPTEN) + # HACK: We only use mimalloc for the temporary heap, not as a general purpose + # allocator. However, the Emscripten port of mimalloc assumes that it is used + # as the system allocator, and depends on the slow emmalloc allocator. + # We do not want to enable emmalloc, as that would slow down allocations all + # over the place, so we must either use mimalloc as the system allocator as + # well, or override its definitions to just use the existing system allocator. + # The latter option is the least intrusive change from a stability standpoint, + # so for now that's what we do here. We can try to convert to mimalloc wholsale + # and remove this hack if we find the need to do so in the future. + set(MI_EXTRA_CPPDEFS "emmalloc_free=free;emmalloc_memalign=memalign" CACHE INTERNAL "") +endif() add_subdirectory(extlib/mimalloc EXCLUDE_FROM_ALL) set(MIMALLOC_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/mimalloc/include) -if(WIN32 OR APPLE) - # 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 - # Homebrew or macOS system libraries into the .app file is highly likely to result - # in incompatibilities after upgrades. - - include(FindVendoredPackage) - include(AddVendoredSubdirectory) - - set(FORCE_VENDORED_ZLIB ON) - set(FORCE_VENDORED_PNG ON) - set(FORCE_VENDORED_Freetype ON) - - find_vendored_package(ZLIB zlib - ZLIB_LIBRARY zlibstatic - ZLIB_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/zlib) - list(APPEND ZLIB_INCLUDE_DIR ${CMAKE_BINARY_DIR}/extlib/zlib) - - find_vendored_package(PNG libpng - SKIP_INSTALL_ALL ON - PNG_LIBRARY png_static - PNG_ARM_NEON "off" - PNG_SHARED OFF - PNG_STATIC ON - PNG_EXECUTABLES OFF - PNG_TESTS OFF - PNG_FRAMEWORK OFF - PNG_PNG_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/libpng) - list(APPEND PNG_PNG_INCLUDE_DIR ${CMAKE_BINARY_DIR}/extlib/libpng) - - find_vendored_package(Freetype freetype - WITH_ZLIB OFF - WITH_BZip2 OFF - WITH_PNG OFF - WITH_HarfBuzz OFF - FREETYPE_LIBRARY freetype - FREETYPE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/freetype/include) - - message(STATUS "Using in-tree pixman") - set(PIXMAN_FOUND YES) - set(PIXMAN_LIBRARY pixman) - set(PIXMAN_BUILD_TESTS OFF CACHE BOOL "") - set(PIXMAN_BUILD_DEMOS OFF CACHE BOOL "") - - set(PIXMAN_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/pixman/pixman) - list(APPEND PIXMAN_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/extlib/pixman/pixman) - add_vendored_subdirectory(extlib/pixman) - - message(STATUS "Using in-tree cairo") - add_vendored_subdirectory(extlib/cairo) - set(CAIRO_FOUND YES) - set(CAIRO_LIBRARIES cairo) - set(CAIRO_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/cairo/src) - list(APPEND CAIRO_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/extlib/cairo/src) +if(NOT FORCE_VENDORED_Eigen3) + find_package(Eigen3 CONFIG) +endif() +if(FORCE_VENDORED_Eigen3 OR NOT EIGEN3_INCLUDE_DIRS) + message(STATUS "Using in-tree Eigen") + set(EIGEN3_FOUND YES) + set(EIGEN3_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/eigen) else() - # On Linux and BSDs we're a good citizen and link to system libraries. - find_package(Backtrace) - find_package(PkgConfig REQUIRED) - find_package(ZLIB REQUIRED) - find_package(PNG REQUIRED) - find_package(Freetype REQUIRED) - pkg_check_modules(CAIRO REQUIRED cairo) + message(STATUS "Using system Eigen: ${EIGEN3_INCLUDE_DIRS}") +endif() +if(NOT EXISTS "${EIGEN3_INCLUDE_DIRS}") + message(FATAL_ERROR "Eigen 3 not found on system or in-tree") +endif() + + +if(ENABLE_GUI OR ENABLE_CLI) + if(WIN32 OR APPLE OR EMSCRIPTEN) + # 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 + # Homebrew or macOS system libraries into the .app file is highly likely to result + # in incompatibilities after upgrades. + + include(FindVendoredPackage) + include(AddVendoredSubdirectory) + + set(FORCE_VENDORED_ZLIB ON) + set(FORCE_VENDORED_PNG ON) + set(FORCE_VENDORED_Freetype ON) + + # HACK: This is essentially what `find_vendored_package()` does, except we also + # define the ZLIB::ZLIB target to be an alias for zlibstatic before calling + # `find_package()` in order to avoid having it defined as an imported target + # by the built-in FindZLIB.cmake module, which breaks libpng's png_static + # target that relies on ZLIB::ZLIB instead of on zlibstatic. + # Once zlib releases its develop branch post 1.3.1, the built-in module will not + # generate an imported target since we'll have proper targets, but libpng would + # still need to be updated to use ZLIB::ZLIBSTATIC instead of ZLIB::ZLIB, so this + # ugly hack will have to stay until both libraries release fixed versions. + add_vendored_subdirectory(extlib/zlib) + set(ZLIB_LIBRARY zlibstatic CACHE INTERNAL "") + set(ZLIB_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/zlib CACHE INTERNAL "") + add_library(ZLIB::ZLIB ALIAS ${ZLIB_LIBRARY}) + find_package(ZLIB REQUIRED) + set(ZLIB_FOUND ${ZLIB_FOUND} CACHE INTERNAL "") + + list(APPEND ZLIB_INCLUDE_DIR ${CMAKE_BINARY_DIR}/extlib/zlib) + + # There's an issue with the zlib configuration, which doesn't provide a way to + # disable the shared or static library builds, and on UNIX sets the base name of + # the output for both the static and shared targets to `libz`. As Emscripten doesn't + # support shared libraries, CMake converts the shared library target to a static + # library and both targets end up with the name `libz.a`, causing Ninja to complain + # about different rules generating the same target. As a workaround, force the output + # name of the static library target to be different on Emscripten. + # NOTE: The unreleased develop branch of zlib after version 1.3.1 allows disabling + # the shared library build, which is the proper fix for this issue, so when the + # next version of zlib is released and updated as a dependency, `ZLIB_BUILD_SHARED` + # should be set to `OFF` instead of this hack. + if(EMSCRIPTEN AND UNIX) + set_target_properties(${ZLIB_LIBRARY} PROPERTIES OUTPUT_NAME zlibstatic) + endif() + + find_vendored_package(PNG libpng + SKIP_INSTALL_ALL ON + PNG_LIBRARY png_static + PNG_SHARED OFF + PNG_STATIC ON + PNG_TOOLS OFF + PNG_TESTS OFF + PNG_FRAMEWORK OFF + PNG_PNG_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/libpng) + list(APPEND PNG_PNG_INCLUDE_DIR ${CMAKE_BINARY_DIR}/extlib/libpng) + + find_vendored_package(Freetype freetype + FT_DISABLE_BZIP2 ON + FT_DISABLE_HARFBUZZ ON + FT_DISABLE_BROTLI ON + FT_DISABLE_PNG ON + FREETYPE_LIBRARY freetype + FREETYPE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/freetype/include) + + message(STATUS "Using in-tree pixman") + set(PIXMAN_FOUND YES) + set(PIXMAN_LIBRARY pixman) + set(PIXMAN_BUILD_TESTS OFF CACHE BOOL "") + set(PIXMAN_BUILD_DEMOS OFF CACHE BOOL "") + + set(PIXMAN_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/pixman/pixman) + list(APPEND PIXMAN_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/extlib/pixman/pixman) + add_vendored_subdirectory(extlib/pixman) + + message(STATUS "Using in-tree cairo") + add_vendored_subdirectory(extlib/cairo) + set(CAIRO_FOUND YES) + set(CAIRO_LIBRARIES cairo) + set(CAIRO_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/cairo/src) + list(APPEND CAIRO_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/extlib/cairo/src) + else() + # On Linux and BSDs we're a good citizen and link to system libraries. + find_package(Backtrace) + find_package(PkgConfig REQUIRED) + find_package(ZLIB REQUIRED) + find_package(PNG REQUIRED) + find_package(Freetype REQUIRED) + if (NOT USE_QT_GUI OR ENABLE_CLI OR ENABLE_TESTS) + find_package(Cairo REQUIRED) + endif() + endif() endif() # GUI dependencies @@ -276,13 +377,16 @@ if(ENABLE_GUI) elseif(APPLE) find_package(OpenGL REQUIRED) find_library(APPKIT_LIBRARY AppKit REQUIRED) - set(util_LIBRARIES ${APPKIT_LIBRARY}) + elseif(EMSCRIPTEN) + # Everything is built in else() find_package(OpenGL REQUIRED) find_package(SpaceWare) pkg_check_modules(FONTCONFIG REQUIRED fontconfig) - pkg_check_modules(JSONC REQUIRED json-c) - pkg_check_modules(GTKMM REQUIRED gtkmm-3.0>=3.18 pangomm-1.4 x11) + if (NOT USE_QT_GUI) + pkg_check_modules(JSONC REQUIRED json-c) + pkg_check_modules(GTKMM REQUIRED gtkmm-3.0>=3.18 pangomm-1.4 x11) + endif() endif() endif() @@ -347,9 +451,19 @@ if(MSVC) # Same for the (C99) __func__ special variable; we use it only in C++ code. 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 # follows: /w4062=-Wswitch 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() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") @@ -385,15 +499,16 @@ if(ENABLE_COVERAGE) endif() # application components - -add_subdirectory(res) +if(ENABLE_GUI OR ENABLE_CLI) + add_subdirectory(res) + add_subdirectory(exposed) +endif() add_subdirectory(src) -add_subdirectory(exposed) if(ENABLE_TESTS) add_subdirectory(test) endif() -if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") +if((ENABLE_CLI OR ENABLE_GUI) AND (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) add_subdirectory(bench) else() - message(STATUS "Benchmarking disabled in debug builds.") + message(STATUS "Benchmarking disabled in debug and library builds.") endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5636b39be..27f90fd61 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,8 +55,8 @@ This includes GCC 5 and later, Clang 3.3 and later, and Visual Studio 12 (2013) SolveSpace aims to consist of two general parts: a fully portable core, and platform-specific UI and support code. Anything outside of `src/platform/` should only use standard C++11, -and rely on `src/platform/unixutil.cpp` and `src/platform/w32util.cpp` to interact with -the OS where this cannot be done through the C++11 standard library. +and rely on `src/platform/platform.h` to interact with the OS where this cannot be done +through the C++11 standard library. #### Libraries @@ -65,7 +65,7 @@ widely supported, used, and understood. SolveSpace also includes a fair amount o bespoke containers List and IdList; these provide STL iterators, and can be used when convenient, such as when reusing other code. -One notable departure here is the STL I/O threads. SolveSpace does not use STL I/O threads +One notable departure here is the STL I/O streams. SolveSpace does not use STL I/O streams for two reasons: (i) the interface is borderline unusable, and (ii) on Windows it is not possible to open files with Unicode paths through STL. diff --git a/README.md b/README.md index 9b6cb2253..9091ac741 100644 --- a/README.md +++ b/README.md @@ -1,172 +1,252 @@ +# SolveSpace + SolveSpace Logo -SolveSpace -========== [![Build Status](https://github.com/solvespace/solvespace/workflows/CD/badge.svg)](https://github.com/solvespace/solvespace/actions) [![solvespace](https://snapcraft.io/solvespace/badge.svg)](https://snapcraft.io/solvespace) [![solvespace](https://snapcraft.io/solvespace/trending.svg?name=0)](https://snapcraft.io/solvespace) This repository contains the source code of [SolveSpace][], a parametric -2d/3d CAD. +2d/3d CAD tool. -[solvespace]: http://solvespace.com +[solvespace]: https://solvespace.com -Community ---------- +## Community The official SolveSpace [website][sswebsite] has [tutorials][sstutorial], [reference manual][ssref] and a [forum][ssforum]; there is also an official -IRC channel [#solvespace at irc.freenode.net][ssirc]. +IRC channel [#solvespace at web.libera.chat][ssirc]. [sswebsite]: http://solvespace.com/ [ssref]: http://solvespace.com/ref.pl [sstutorial]: http://solvespace.com/tutorial.pl [ssforum]: http://solvespace.com/forum.pl -[ssirc]: https://webchat.freenode.net/?channels=solvespace +[ssirc]: https://web.libera.chat/#solvespace -Installation ------------- +# Installation -### Via official binary packages +### Via Official Packages -_Official_ release binary packages for macOS (>=10.6 64-bit) and Windows (>=Vista 32-bit) are -available via [GitHub releases][rel]. These packages are automatically built by -the SolveSpace maintainers for each stable release. +_Official_ release packages for macOS (>=10.6 64-bit) and Windows +(>=Vista 32-bit) are available via [GitHub releases][rel]. These packages are +automatically built by the SolveSpace maintainers for each stable release. [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 -Builds from master are automatically released to the `edge` channel in the Snap Store. Those packages contain the latest improvements, but receive less testing than release builds. +Official releases can be installed from the `stable` channel. -Future official releases will appear in the `stable` channel. +Builds from master are automatically released to the `edge` channel in the Snap +Store. Those packages contain the latest improvements, but receive less testing +than release builds. [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/solvespace) Or install from a terminal: -``` -snap install --edge solvespace +```sh +# for the latest stable release: +snap install solvespace + +# for the bleeding edge builds from master: +snap install solvespace --edge ``` -### Via third-party binary packages +### Via automated edge builds -_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. +> :warning: **Edge builds might be unstable or contain severe bugs!** +> They are intended for experienced users to test new features or verify bugfixes. -[notesalexp]: https://notesalexp.org/packages/en/source/solvespace/ +Cutting edge builds from the latest master commit are available as zip archives +from the following links: + +- [macOS](https://nightly.link/solvespace/solvespace/workflows/cd/master/macos.zip) +- [Windows with OpenMP enabled 32bit](https://nightly.link/solvespace/solvespace/workflows/cd/master/windows.zip) +- [Windows 32bit](https://nightly.link/solvespace/solvespace/workflows/cd/master/windows_single_core.zip) +- [Windows with OpenMP enabled 64bit](https://nightly.link/solvespace/solvespace/workflows/cd/master/windows_x64.zip) +- [Windows 64bit](https://nightly.link/solvespace/solvespace/workflows/cd/master/windows_single_core_x64.zip) + +**Please note that the 64bit Windows versions do *not* support 6DOF (SpeceMouse, SpaceNavigator) controllers.** + +Extract the downloaded archive and install or execute the contained file as is +appropriate for your platform. ### 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 ### Building for Linux -You will need the usual build tools, CMake, zlib, libpng, cairo, freetype. -To build the GUI, you will need fontconfig, gtkmm 3.0 (version 3.16 or later), pangomm 1.4, -OpenGL and OpenGL GLU, and optionally, the Space Navigator client library. -On a Debian derivative (e.g. Ubuntu) these can be installed with: +You will need the usual build tools, CMake, zlib, libpng, cairo, freetype. To +build the GUI, you will need fontconfig, gtkmm 3.0 (version 3.16 or later) for +GTK, or QT6 for the newer QT interface, pangomm 1.4, OpenGL and OpenGL GLU, +and optionally, the Space Navigator client library. - sudo apt install git build-essential cmake zlib1g-dev libpng-dev \ - libcairo2-dev libfreetype6-dev libjson-c-dev \ - libfontconfig1-dev libgtkmm-3.0-dev libpangomm-1.4-dev \ - libgl-dev libglu-dev libspnav-dev +On a Debian derivative (e.g. Ubuntu) these can be installed with: -On a Redhat derivative (e.g. Fedora) the dependencies can be installed with: +```sh +sudo apt install git build-essential cmake zlib1g-dev libpng-dev \ + libcairo2-dev libfreetype6-dev libjson-c-dev \ + libfontconfig1-dev libpangomm-1.4-dev libgl-dev \ + libglu-dev libspnav-dev libgtkmm-3.0-dev qt6-base-dev +``` - sudo dnf install git gcc-c++ cmake zlib-devel libpng-devel \ - cairo-devel freetype-devel json-c-devel \ - fontconfig-devel gtkmm30-devel pangomm-devel \ - mesa-libGL-devel mesa-libGLU-devel libspnav-devel +On a RedHat derivative (e.g. Fedora) the dependencies can be installed with: -Before building, check out the project and the necessary submodules: +```sh +sudo dnf install git gcc-c++ cmake zlib-devel libpng-devel \ + cairo-devel freetype-devel json-c-devel \ + fontconfig-devel pangomm-devel mesa-libGL-devel \ + mesa-libGLU-devel libspnav-devel gtkmm30-devel \ + qt6-qtbase-devel +``` +`gtkmm30-devel` is required to build the GTK version and `qt6-qtbase-devel` is required to build the QT version. One or +the other may be omitted if both versions are not needed. Likewise with `libgtkmm-3.0-dev` and `qt6-base-dev` for Debuntu +respectively. - git clone https://github.com/solvespace/solvespace - cd solvespace - git submodule update --init extlib/libdxfrw extlib/mimalloc +Before building, [check out the project and the necessary submodules](#via-source-code). After that, build SolveSpace as following: - mkdir build - cd build - cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON - make - sudo make install +```sh +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON [-DENABLE_LTO=ON] [-DUSE_QT_GUI=ON] [-DENABLE_GUI=OFF] +make -Link Time Optimization is supported by adding -DENABLE_LTO=ON to cmake at the -expense of longer build time. +# Optionally +sudo make install +``` +Optional: + - -DENABLE_LTO=ON: Enable Link Time Optimization at the expense of longer build time. + - -DUSE_QT_GUI=ON: Build the newer QT GUI interface. + - -DENABLE_GUI=OFF: Build only the command-line interface -The graphical interface is built as `build/bin/solvespace`, and the command-line interface -is built as `build/bin/solvespace-cli`. It is possible to build only the command-line interface by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation. +The GTK graphical interface is built as `build/bin/solvespace`, and the command-line +interface is built as `build/bin/solvespace-cli`. The QT graphical interface is built +as `build/bin/solvespace-qt`. ### Building for Windows -Ubuntu will require 20.04 or above. Cross-compiling with WSL is also confirmed to work. - -You will need the usual build tools, CMake, a Windows cross-compiler, and flatc. On a Debian derivative (e.g. Ubuntu) these can be installed with: +Ubuntu will require 20.04 or above. Cross-compiling with WSL is also confirmed +to work. - apt-get install git build-essential cmake mingw-w64 +You will need the usual build tools, CMake, and a Windows cross-compiler. On a +Debian derivative (e.g. Ubuntu) these can be installed with: -Before building, check out the project and the necessary submodules: +```sh +apt-get install git build-essential cmake mingw-w64 +``` - git clone https://github.com/solvespace/solvespace - cd solvespace - git submodule update --init +Before building, [check out the project and the necessary submodules](#via-source-code). Build 64-bit SolveSpace with the following: - mkdir build - cd build - cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw64.cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DFLATC=$(which flatc) - make +```sh +mkdir build +cd build +cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw64.cmake \ + -DCMAKE_BUILD_TYPE=Release +make +``` -The graphical interface is built as `build/bin/solvespace.exe`, and the command-line interface -is built as `build/bin/solvespace-cli.exe`. +The graphical interface is built as `build/bin/solvespace.exe`, and the +command-line interface is built as `build/bin/solvespace-cli.exe`. Space Navigator support will not be available. -If using Ubuntu to cross-compile, Ubuntu 17.10 or newer (or, alternatively, MinGW from the Ubuntu -17.10 repositories) is required. +### 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). -Building on macOS ------------------ +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 +``` -You will need git, XCode tools and CMake. Git and CMake can be installed +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 + +You will need git, XCode tools, CMake and libomp. Git, CMake and libomp can be installed via [Homebrew][]: - brew install git cmake +```sh +brew install git cmake libomp +``` XCode has to be installed via AppStore or [the Apple website][appledeveloper]; it requires a free Apple ID. -Before building, check out the project and the necessary submodules: - - git clone https://github.com/solvespace/solvespace - cd solvespace - git submodule update --init +Before building, [check out the project and the necessary submodules](#via-source-code). After that, build SolveSpace as following: - mkdir build - cd build - cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON - make +```sh +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON +make +``` -Link Time Optimization is supported by adding -DENABLE_LTO=ON to cmake at the +Link Time Optimization is supported by adding `-DENABLE_LTO=ON` to cmake at the expense of longer build time. Alternatively, generate an XCode project, open it, and build the "Release" scheme: - mkdir build - cd build - cmake .. -G Xcode +```sh +mkdir build +cd build +cmake .. -G Xcode +``` The application is built in `build/bin/SolveSpace.app`, the graphical interface executable is `build/bin/SolveSpace.app/Contents/MacOS/SolveSpace`, and the command-line interface executable @@ -175,26 +255,26 @@ is `build/bin/SolveSpace.app/Contents/MacOS/solvespace-cli`. [homebrew]: https://brew.sh/ [appledeveloper]: https://developer.apple.com/download/ -Building on OpenBSD -------------------- +## Building on OpenBSD You will need git, cmake, libexecinfo, libpng, gtk3mm and pangomm. These can be installed from the ports tree: - pkg_add -U git cmake libexecinfo png json-c gtk3mm pangomm - -Before building, check out the project and the necessary submodules: +```sh +pkg_add -U git cmake libexecinfo png json-c gtk3mm pangomm +``` - git clone https://github.com/solvespace/solvespace - cd solvespace - git submodule update --init extlib/libdxfrw extlib/mimalloc +Before building, [check out the project and the necessary submodules](#via-source-code). After that, build SolveSpace as following: - mkdir build - cd build - cmake .. -DCMAKE_BUILD_TYPE=Release - make +```sh +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make +sudo make install +``` Unfortunately, on OpenBSD, the produced executables are not filesystem location independent and must be installed before use. By default, the graphical interface is installed to @@ -202,61 +282,75 @@ and must be installed before use. By default, the graphical interface is install `/usr/local/bin/solvespace-cli`. It is possible to build only the command-line interface by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation. -Building on Windows -------------------- +## Building on Windows You will need [git][gitwin], [cmake][cmakewin] and a C++ compiler (either Visual C++ or MinGW). If using Visual C++, Visual Studio 2015 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 -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. Press "Configure" and "Generate", then open `build\solvespace.sln` with Visual C++ and build it. ### Building with Visual Studio in a command prompt -First, ensure that git and cl (the Visual C++ compiler driver) are in your +First, ensure that `git` and `cl` (the Visual C++ compiler driver) are in your `%PATH%`; the latter is usually done by invoking `vcvarsall.bat` from your Visual Studio install. Then, run the following in cmd or PowerShell: - git clone https://github.com/solvespace/solvespace - cd solvespace - git submodule update --init - mkdir build - cd build - cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release - nmake +```bat +mkdir build +cd build +cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release +nmake +``` ### Building with MinGW It is also possible to build SolveSpace using [MinGW][mingw], though Space Navigator support will be disabled. -First, ensure that git and gcc are in your `$PATH`. Then, run the following -in bash: +The easiest way to build using MinGW is with [MSYS2][msys2]. If you're not using MSYS2, skip +the installation instructions and ensure that git, cmake, ninja, and gcc are in your `$PATH`. - git clone https://github.com/solvespace/solvespace - cd solvespace - git submodule update --init - mkdir build - cd build - cmake .. -DCMAKE_BUILD_TYPE=Release - make +With MSYS2, you can build either a 32-bit binary or a 64-bit one, depending on the compiler +used. The following instructions assume you're running the commands inside an `MSYS2 MINGW64` +terminal window and building a 64-bit version. If you want to build a 32-bit version, you'll +need to run the commands in an `MSYS2 MINGW32` terminal window and replace `x86_64` +with `i686` in the installation commands. + +First, install Git, GCC, CMake, and Ninja: + +```sh +pacman -Sy mingw-w64-x86_64-git mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja +``` + +Then, run the following in bash: + +```sh +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -GNinja +ninja +``` [gitwin]: https://git-scm.com/download/win -[cmakewin]: http://www.cmake.org/download/#latest +[cmakewin]: https://www.cmake.org/download/#latest [mingw]: http://www.mingw.org/ +[msys2]: https://www.msys2.org/ -Contributing ------------- +## Contributing See the [guide for contributors](CONTRIBUTING.md) for the best way to file issues, contribute code, and debug SolveSpace. -License -------- +## License SolveSpace is distributed under the terms of the [GPL v3](COPYING.txt) or later. diff --git a/bench/harness.cpp b/bench/harness.cpp index 4180558ad..66d53088b 100644 --- a/bench/harness.cpp +++ b/bench/harness.cpp @@ -3,8 +3,15 @@ // // Copyright 2016 whitequark //----------------------------------------------------------------------------- +#include +#include +#include +#include + #include "solvespace.h" +using namespace SolveSpace; + static bool RunBenchmark(std::function setupFn, std::function benchFn, std::function teardownFn, diff --git a/cmake/DisableWarnings.cmake b/cmake/DisableWarnings.cmake index b79ef9cd5..34925c80d 100644 --- a/cmake/DisableWarnings.cmake +++ b/cmake/DisableWarnings.cmake @@ -4,12 +4,12 @@ function(disable_warnings) if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w" PARENT_SCOPE) 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() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w" PARENT_SCOPE) 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() endfunction() diff --git a/cmake/FindCairo.cmake b/cmake/FindCairo.cmake new file mode 100644 index 000000000..d0130ad52 --- /dev/null +++ b/cmake/FindCairo.cmake @@ -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 +# +# 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 +) diff --git a/cmake/FindSpaceWare.cmake b/cmake/FindSpaceWare.cmake index fb6073c32..451063c67 100644 --- a/cmake/FindSpaceWare.cmake +++ b/cmake/FindSpaceWare.cmake @@ -16,7 +16,7 @@ if(UNIX) # Support the REQUIRED and QUIET arguments, and set SPACEWARE_FOUND if found. include(FindPackageHandleStandardArgs) - FIND_PACKAGE_HANDLE_STANDARD_ARGS(SPACEWARE DEFAULT_MSG + find_package_handle_standard_args(SpaceWare DEFAULT_MSG SPACEWARE_LIBRARY SPACEWARE_INCLUDE_DIR) if(SPACEWARE_FOUND) diff --git a/cmake/GetGitCommitHash.cmake b/cmake/GetGitCommitHash.cmake index f67a844a4..c3d226734 100644 --- a/cmake/GetGitCommitHash.cmake +++ b/cmake/GetGitCommitHash.cmake @@ -3,6 +3,14 @@ function(get_git_commit_hash) get_filename_component(GIT_ROOT ${GIT_DESCRIBE_CMAKE_DIR} PATH) set(GIT_DIR "${GIT_ROOT}/.git") + if(EXISTS ${GIT_DIR} AND NOT IS_DIRECTORY ${GIT_DIR}) + # In case we are included as a git submodule. + file(READ ${GIT_DIR} GIT_SUB_DIR) + string(REPLACE "gitdir: " "" GIT_SUB_DIR ${GIT_SUB_DIR}) + string(STRIP ${GIT_SUB_DIR} GIT_SUB_DIR) + set(GIT_DIR "${GIT_ROOT}/${GIT_SUB_DIR}") + endif() + # Add a CMake configure dependency to the currently checked out revision. set(GIT_DEPENDS ${GIT_DIR}/HEAD) file(READ ${GIT_DIR}/HEAD HEAD_REF) diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in index 668d1f7ed..19ef1e072 100644 --- a/cmake/MacOSXBundleInfo.plist.in +++ b/cmake/MacOSXBundleInfo.plist.in @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleVersion - ${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH} + ${PROJECT_VERSION}~${solvespace_GIT_HASH} CFBundleShortVersionString - ${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR} + ${PROJECT_VERSION} NSHumanReadableCopyright - © 2008-2016 Jonathan Westhues and other authors + © 2008-2025 Jonathan Westhues and other authors NSPrincipalClass NSApplication NSMainNibFile diff --git a/cmake/Platform/Emscripten.cmake b/cmake/Platform/Emscripten.cmake new file mode 100644 index 000000000..9ad0a778d --- /dev/null +++ b/cmake/Platform/Emscripten.cmake @@ -0,0 +1,364 @@ +# This file is a 'toolchain description file' for CMake. +# It teaches CMake about the Emscripten compiler, so that CMake can generate +# makefiles from CMakeLists.txt that invoke emcc. + +# The following variable describes the target OS we are building to. +set(CMAKE_SYSTEM_NAME Emscripten) +set(CMAKE_SYSTEM_VERSION 1) + +set(CMAKE_CROSSCOMPILING TRUE) +set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE) + +# Advertise Emscripten as a 32-bit platform (as opposed to +# CMAKE_SYSTEM_PROCESSOR=x86_64 for 64-bit platform), since some projects (e.g. +# OpenCV) use this to detect bitness. +# Allow users to ovewrite this on the command line with -DEMSCRIPTEN_SYSTEM_PROCESSOR=arm. +if (NOT DEFINED EMSCRIPTEN_SYSTEM_PROCESSOR) + set(EMSCRIPTEN_SYSTEM_PROCESSOR x86) +endif() +set(CMAKE_SYSTEM_PROCESSOR ${EMSCRIPTEN_SYSTEM_PROCESSOR}) + +# Tell CMake how it should instruct the compiler to generate multiple versions +# of an outputted .so library: e.g. "libfoo.so, libfoo.so.1, libfoo.so.1.4" etc. +# This feature is activated if a shared library project has the property +# SOVERSION defined. +set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "-Wl,-soname,") + +# In CMake, CMAKE_HOST_WIN32 is set when we are cross-compiling from Win32 to +# Emscripten: +# http://www.cmake.org/cmake/help/v2.8.12/cmake.html#variable:CMAKE_HOST_WIN32 +# The variable WIN32 is set only when the target arch that will run the code +# will be WIN32, so unset WIN32 when cross-compiling. +set(WIN32) + +# The same logic as above applies for APPLE and CMAKE_HOST_APPLE, so unset +# APPLE. +set(APPLE) + +# And for UNIX and CMAKE_HOST_UNIX. However, Emscripten is often able to mimic +# being a Linux/Unix system, in which case a lot of existing CMakeLists.txt +# files can be configured for Emscripten while assuming UNIX build, so this is +# left enabled. +set(UNIX 1) + +# Do a no-op access on the CMAKE_TOOLCHAIN_FILE variable so that CMake will not +# issue a warning on it being unused. +if (CMAKE_TOOLCHAIN_FILE) +endif() + +if (CMAKE_HOST_WIN32) + set(EMCC_SUFFIX ".bat") +else() + set(EMCC_SUFFIX "") +endif() + +# Locate where the Emscripten compiler resides in +if (NOT DEFINED EMSCRIPTEN_ROOT_PATH) + # Check relative to this toolchain file + get_filename_component(GUESS_EMSCRIPTEN_ROOT_PATH "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE) + # If not found by above search, locate using the EMSCRIPTEN environment variable + find_program(EMSCRIPTEN_EMRANLIB_PATH emranlib${EMCC_SUFFIX} HINTS "${GUESS_EMSCRIPTEN_ROOT_PATH}" ENV "EMSCRIPTEN") + if(NOT EMSCRIPTEN_EMRANLIB_PATH) + # Abort if not found + message(FATAL_ERROR "Could not locate the Emscripten compiler toolchain directory! Either set the EMSCRIPTEN environment variable, or pass -DEMSCRIPTEN_ROOT_PATH=xxx to CMake to explicitly specify the location of the compiler!") + endif() + get_filename_component(EMSCRIPTEN_ROOT_PATH "${EMSCRIPTEN_EMRANLIB_PATH}" DIRECTORY) +endif() + +# Normalize, convert Windows backslashes to forward slashes or CMake will crash. +get_filename_component(EMSCRIPTEN_ROOT_PATH "${EMSCRIPTEN_ROOT_PATH}" ABSOLUTE) + +list(APPEND CMAKE_MODULE_PATH "${EMSCRIPTEN_ROOT_PATH}/cmake/Modules") + +# Specify the compilers to use for C and C++ +set(CMAKE_C_COMPILER "${EMSCRIPTEN_ROOT_PATH}/emcc${EMCC_SUFFIX}") +set(CMAKE_CXX_COMPILER "${EMSCRIPTEN_ROOT_PATH}/em++${EMCC_SUFFIX}") +set(CMAKE_NM "${EMSCRIPTEN_ROOT_PATH}/emnm${EMCC_SUFFIX}") +set(CMAKE_AR "${EMSCRIPTEN_ROOT_PATH}/emar${EMCC_SUFFIX}") +set(CMAKE_RANLIB "${EMSCRIPTEN_ROOT_PATH}/emranlib${EMCC_SUFFIX}") +set(CMAKE_C_COMPILER_AR "${CMAKE_AR}") +set(CMAKE_CXX_COMPILER_AR "${CMAKE_AR}") +set(CMAKE_C_COMPILER_RANLIB "${CMAKE_RANLIB}") +set(CMAKE_CXX_COMPILER_RANLIB "${CMAKE_RANLIB}") +set(CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS "${EMSCRIPTEN_ROOT_PATH}/emscan-deps") + +# Capture the Emscripten version to EMSCRIPTEN_VERSION variable. +if (NOT EMSCRIPTEN_VERSION) + execute_process(COMMAND "${CMAKE_C_COMPILER}" "-v" RESULT_VARIABLE _cmake_compiler_result ERROR_VARIABLE _cmake_compiler_output OUTPUT_QUIET) + if (NOT _cmake_compiler_result EQUAL 0) + message(FATAL_ERROR "Failed to fetch Emscripten version information with command \"'${CMAKE_C_COMPILER}' -v\"!\n" + "Process returned with error code ${_cmake_compiler_result}.\n" + "Output:\n${_cmake_compiler_output}") + endif() + string(REGEX MATCH "emcc \\(.*\\) ([0-9\\.]+)" _dummy_unused "${_cmake_compiler_output}") + if (NOT CMAKE_MATCH_1) + message(FATAL_ERROR "Failed to regex parse Emscripten compiler version from version string: ${_cmake_compiler_output}") + endif() + + set(EMSCRIPTEN_VERSION "${CMAKE_MATCH_1}") +endif() + +execute_process(COMMAND "${EMSCRIPTEN_ROOT_PATH}/em-config${EMCC_SUFFIX}" "CACHE" + RESULT_VARIABLE _emcache_result + OUTPUT_VARIABLE _emcache_output + OUTPUT_STRIP_TRAILING_WHITESPACE) +if (NOT _emcache_result EQUAL 0) + message(FATAL_ERROR "Failed to find emscripten cache directory with command \"'${EMSCRIPTEN_ROOT_PATH}/em-config${EMCC_SUFFIX}' CACHE\"! Process returned with error code ${_emcache_result}.") +endif() +file(TO_CMAKE_PATH "${_emcache_output}" _emcache_output) +set(EMSCRIPTEN_SYSROOT "${_emcache_output}/sysroot") + +# Allow skipping of CMake compiler autodetection, since this is quite slow with +# Emscripten. Pass -DEMSCRIPTEN_FORCE_COMPILERS=ON to enable +option(EMSCRIPTEN_FORCE_COMPILERS "Force C/C++ compiler" OFF) +if (EMSCRIPTEN_FORCE_COMPILERS) + # Detect version of the 'emcc' executable. Note that for CMake, we tell it the + # version of the Clang compiler and not the version of Emscripten, because + # CMake understands Clang better. + # Toolchain script is interpreted multiple times, so don't rerun the check if + # already done before. + if (NOT CMAKE_C_COMPILER_VERSION) + execute_process(COMMAND "${CMAKE_C_COMPILER}" "-v" RESULT_VARIABLE _cmake_compiler_result ERROR_VARIABLE _cmake_compiler_output OUTPUT_QUIET) + if (NOT _cmake_compiler_result EQUAL 0) + message(FATAL_ERROR "Failed to fetch compiler version information with command \"'${CMAKE_C_COMPILER}' -v\"! Process returned with error code ${_cmake_compiler_result}.") + endif() + if (NOT "${_cmake_compiler_output}" MATCHES "[Ee]mscripten") + message(FATAL_ERROR "System LLVM compiler cannot be used to build with Emscripten! Check Emscripten's LLVM toolchain location in .emscripten configuration file, and make sure to point CMAKE_C_COMPILER to where emcc is located. (was pointing to \"${CMAKE_C_COMPILER}\")") + endif() + string(REGEX MATCH "clang version ([0-9\\.]+)" _dummy_unused "${_cmake_compiler_output}") + if (NOT CMAKE_MATCH_1) + message(FATAL_ERROR "Failed to regex parse Clang compiler version from version string: ${_cmake_compiler_output}") + endif() + + set(CMAKE_C_COMPILER_VERSION "${CMAKE_MATCH_1}") + set(CMAKE_CXX_COMPILER_VERSION "${CMAKE_MATCH_1}") + if (${CMAKE_C_COMPILER_VERSION} VERSION_LESS 3.9.0) + message(WARNING "CMAKE_C_COMPILER version looks too old. Was ${CMAKE_C_COMPILER_VERSION}, should be at least 3.9.0.") + endif() + endif() + + set(CMAKE_C_COMPILER_ID_RUN TRUE) + set(CMAKE_C_COMPILER_FORCED TRUE) + set(CMAKE_C_COMPILER_WORKS TRUE) + set(CMAKE_C_COMPILER_ID Clang) + set(CMAKE_C_COMPILER_FRONTEND_VARIANT GNU) + set(CMAKE_C_STANDARD_COMPUTED_DEFAULT 11) + + set(CMAKE_CXX_COMPILER_ID_RUN TRUE) + set(CMAKE_CXX_COMPILER_FORCED TRUE) + set(CMAKE_CXX_COMPILER_WORKS TRUE) + set(CMAKE_CXX_COMPILER_ID Clang) + set(CMAKE_CXX_COMPILER_FRONTEND_VARIANT GNU) + set(CMAKE_CXX_STANDARD_COMPUTED_DEFAULT 98) + + set(CMAKE_C_PLATFORM_ID "emscripten") + set(CMAKE_CXX_PLATFORM_ID "emscripten") + + if (NOT DEFINED CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES) + set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES "${EMSCRIPTEN_SYSROOT}/include") + endif() + if (NOT DEFINED CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES) + set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "${EMSCRIPTEN_SYSROOT}/include;${EMSCRIPTEN_SYSROOT}/include/c++/v1") + endif() + + if ("${CMAKE_VERSION}" VERSION_LESS "3.8") + set(CMAKE_C_COMPILE_FEATURES "c_function_prototypes;c_restrict;c_variadic_macros;c_static_assert") + set(CMAKE_C90_COMPILE_FEATURES "c_function_prototypes") + set(CMAKE_C99_COMPILE_FEATURES "c_restrict;c_variadic_macros") + set(CMAKE_C11_COMPILE_FEATURES "c_static_assert") + + set(CMAKE_CXX_COMPILE_FEATURES "cxx_template_template_parameters;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") + set(CMAKE_CXX98_COMPILE_FEATURES "cxx_template_template_parameters") + set(CMAKE_CXX11_COMPILE_FEATURES "cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates") + set(CMAKE_CXX14_COMPILE_FEATURES "cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") + else() # 3.8+ + set(CMAKE_C90_COMPILE_FEATURES "c_std_90;c_function_prototypes") + set(CMAKE_C99_COMPILE_FEATURES "c_std_99;c_restrict;c_variadic_macros") + set(CMAKE_C11_COMPILE_FEATURES "c_std_11;c_static_assert") + + set(CMAKE_CXX98_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters") + set(CMAKE_CXX11_COMPILE_FEATURES "cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates") + set(CMAKE_CXX14_COMPILE_FEATURES "cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") + set(CMAKE_CXX17_COMPILE_FEATURES "cxx_std_17") + if ("${CMAKE_VERSION}" VERSION_LESS "3.12") # [3.8, 3.12) + set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert") + set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17") + else() # 3.12+ + set(CMAKE_CXX20_COMPILE_FEATURES "cxx_std_20") + if ("${CMAKE_VERSION}" VERSION_LESS "3.20") # [3.12, 3.20) + set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert") + set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17;cxx_std_20") + else() # 3.20+ + set(CMAKE_CXX23_COMPILE_FEATURES "cxx_std_23") + if ("${CMAKE_VERSION}" VERSION_LESS "3.25") + set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17;cxx_std_20;cxx_std_23") + else() # 3.25+ + set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17;cxx_std_20;cxx_std_23;cxx_std_26") + endif() + if ("${CMAKE_VERSION}" VERSION_LESS "3.21") # 3.20 + set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert") + else() # 3.21+ + set(CMAKE_C17_COMPILE_FEATURES "c_std_17") + set(CMAKE_C23_COMPILE_FEATURES "c_std_23") + set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert;c_std_17;c_std_23") + endif() + endif() + endif() + endif() +endif() + +list(APPEND CMAKE_FIND_ROOT_PATH "${EMSCRIPTEN_SYSROOT}") +list(APPEND CMAKE_SYSTEM_PREFIX_PATH /) + +if (${CMAKE_C_FLAGS} MATCHES "MEMORY64") + set(CMAKE_LIBRARY_ARCHITECTURE "wasm64-emscripten") + set(CMAKE_SIZEOF_VOID_P 8) + set(CMAKE_C_SIZEOF_DATA_PTR 8) + set(CMAKE_CXX_SIZEOF_DATA_PTR 8) +else() + set(CMAKE_LIBRARY_ARCHITECTURE "wasm32-emscripten") + set(CMAKE_SIZEOF_VOID_P 4) + set(CMAKE_C_SIZEOF_DATA_PTR 4) + set(CMAKE_CXX_SIZEOF_DATA_PTR 4) +endif() + +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${EMSCRIPTEN_SYSROOT}" CACHE PATH + "Install path prefix, prepended onto install directories." FORCE) +endif() + +# To find programs to execute during CMake run time with find_program(), e.g. +# 'git' or so, we allow looking into system paths. +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +# Since Emscripten is a cross-compiler, we should never look at the +# system-provided directories like /usr/include and so on. Therefore only +# CMAKE_FIND_ROOT_PATH should be used as a find directory. See +# http://www.cmake.org/cmake/help/v3.0/variable/CMAKE_FIND_ROOT_PATH_MODE_INCLUDE.html +if (NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +endif() +if (NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() +if (NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) +endif() + +set(_em_pkgconfig_libdir "${EMSCRIPTEN_SYSROOT}/local/lib/pkgconfig" "${EMSCRIPTEN_SYSROOT}/lib/pkgconfig") +if("${CMAKE_VERSION}" VERSION_LESS "3.20") + file(TO_NATIVE_PATH "${_em_pkgconfig_libdir}" _em_pkgconfig_libdir) + if(CMAKE_HOST_UNIX) + string(REPLACE ";" ":" _em_pkgconfig_libdir "${_em_pkgconfig_libdir}") + string(REPLACE "\\ " " " _em_pkgconfig_libdir "${_em_pkgconfig_libdir}") + endif() +else() + cmake_path(CONVERT "${_em_pkgconfig_libdir}" TO_NATIVE_PATH_LIST _em_pkgconfig_libdir) +endif() +set(ENV{PKG_CONFIG_LIBDIR} "${_em_pkgconfig_libdir}") +unset(_em_pkgconfig_libdir) + +option(EMSCRIPTEN_GENERATE_BITCODE_STATIC_LIBRARIES "If set, static library targets generate LLVM bitcode files (.bc). If disabled (default), UNIX ar archives (.a) are generated." OFF) +if (EMSCRIPTEN_GENERATE_BITCODE_STATIC_LIBRARIES) + message(FATAL_ERROR "EMSCRIPTEN_GENERATE_BITCODE_STATIC_LIBRARIES is not compatible with the llvm backend") +endif() + +set(CMAKE_EXECUTABLE_SUFFIX ".js") + +set(CMAKE_C_USE_RESPONSE_FILE_FOR_LIBRARIES 1) +set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_LIBRARIES 1) +set(CMAKE_C_USE_RESPONSE_FILE_FOR_OBJECTS 1) +set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS 1) +set(CMAKE_C_USE_RESPONSE_FILE_FOR_INCLUDES 1) +set(CMAKE_CXX_USE_RESPONSE_FILE_FOR_INCLUDES 1) + +set(CMAKE_C_RESPONSE_FILE_LINK_FLAG "@") +set(CMAKE_CXX_RESPONSE_FILE_LINK_FLAG "@") + +# Enable $ for CMake 3.24+ +set(CMAKE_LINK_LIBRARY_USING_WHOLE_ARCHIVE "-Wl,--whole-archive" "" "-Wl,--no-whole-archive") +set(CMAKE_LINK_LIBRARY_USING_WHOLE_ARCHIVE_SUPPORTED True) + +# Set a global EMSCRIPTEN variable that can be used in client CMakeLists.txt to +# detect when building using Emscripten. +set(EMSCRIPTEN 1 CACHE INTERNAL "If true, we are targeting Emscripten output.") + +# Hardwire support for cmake-2.8/Modules/CMakeBackwardsCompatibilityC.cmake +# without having CMake to try complex things to autodetect these: +set(CMAKE_SKIP_COMPATIBILITY_TESTS 1) +set(CMAKE_SIZEOF_CHAR 1) +set(CMAKE_SIZEOF_UNSIGNED_SHORT 2) +set(CMAKE_SIZEOF_SHORT 2) +set(CMAKE_SIZEOF_INT 4) +set(CMAKE_SIZEOF_UNSIGNED_LONG 4) +set(CMAKE_SIZEOF_UNSIGNED_INT 4) +set(CMAKE_SIZEOF_LONG 4) +set(CMAKE_SIZEOF_FLOAT 4) +set(CMAKE_SIZEOF_DOUBLE 8) +set(CMAKE_HAVE_LIMITS_H 1) +set(CMAKE_HAVE_UNISTD_H 1) +set(CMAKE_HAVE_PTHREAD_H 1) +set(CMAKE_HAVE_SYS_PRCTL_H 1) +set(CMAKE_WORDS_BIGENDIAN 0) +set(CMAKE_C_BYTE_ORDER "LITTLE_ENDIAN") +set(CMAKE_CXX_BYTE_ORDER "LITTLE_ENDIAN") +set(CMAKE_DL_LIBS) + +function(em_validate_asmjs_after_build target) + message(WARNING "em_validate_asmjs_after_build no longer exists") +endfunction() + +# Internal function: Do not call from user CMakeLists.txt files. Use one of +# em_link_js_library()/em_link_pre_js()/em_link_post_js() instead. +function(em_add_link_deps target flagname) + # User can input list of JS files either as a single list, or as variable + # arguments to this function, so iterate over varargs, and treat each item in + # varargs as a list itself, to support both syntax forms. + foreach(jsFileList ${ARGN}) + foreach(jsfile ${jsFileList}) + get_target_property(linkdeps ${target} LINK_DEPENDS) + if(linkdeps STREQUAL "linkdeps-NOTFOUND") + set(linkdeps "") + endif() + get_filename_component(jsfile_abs "${jsfile}" ABSOLUTE ) + set_target_properties(${target} PROPERTIES LINK_DEPENDS "${linkdeps};${jsfile_abs}") + target_link_libraries(${target} "${flagname} \"${jsfile_abs}\"") + endforeach() + endforeach() +endfunction() + +# This function links a (list of ) .js library file(s) to the given CMake project. +# Example: em_link_js_library(my_executable "lib1.js" "lib2.js") +# will result in emcc passing --js-library lib1.js --js-library lib2.js to +# the emscripten linker, as well as tracking the modification timestamp +# between the linked .js files and the main project, so that editing the .js +# file will cause the target project to be relinked. +function(em_link_js_library target) + em_add_link_deps(${target} "--js-library" ${ARGN}) +endfunction() + +# This function is identical to em_link_js_library(), except the .js files will +# be added with '--pre-js file.js' command line flag, which is generally used to +# add some preamble .js code to a generated output file. +function(em_link_pre_js target) + em_add_link_deps(${target} "--pre-js" ${ARGN}) +endfunction() + +# This function is identical to em_link_js_library(), except the .js files will +# be added with '--post-js file.js' command line flag, which is generally used +# to add some postamble .js code to a generated output file. +function(em_link_post_js target) + em_add_link_deps(${target} "--post-js" ${ARGN}) +endfunction() + +if (NOT DEFINED CMAKE_CROSSCOMPILING_EMULATOR) + find_program(NODE_JS_EXECUTABLE NAMES nodejs node) + if(NODE_JS_EXECUTABLE) + set(CMAKE_CROSSCOMPILING_EMULATOR "${NODE_JS_EXECUTABLE}" CACHE FILEPATH "Path to the emulator for the target system.") + endif() +endif() + +# No-op on CMAKE_CROSSCOMPILING_EMULATOR so older versions of cmake do not +# complain about unused CMake variable. +if (CMAKE_CROSSCOMPILING_EMULATOR) +endif() diff --git a/cmake/c_flag_overrides.cmake b/cmake/c_flag_overrides.cmake index b21f00e3e..7d33e9bff 100644 --- a/cmake/c_flag_overrides.cmake +++ b/cmake/c_flag_overrides.cmake @@ -3,4 +3,9 @@ if(MSVC) set(CMAKE_C_FLAGS_MINSIZEREL_INIT "/MT /O1 /Ob1 /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") -endif() \ No newline at end of file + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() + +if(EMSCRIPTEN) + set(CMAKE_C_FLAGS_DEBUG_INIT "-g4") +endif() diff --git a/cmake/cxx_flag_overrides.cmake b/cmake/cxx_flag_overrides.cmake index 67e004334..9c8d15fe6 100644 --- a/cmake/cxx_flag_overrides.cmake +++ b/cmake/cxx_flag_overrides.cmake @@ -4,3 +4,7 @@ if(MSVC) set(CMAKE_CXX_FLAGS_RELEASE_INIT "/MT /O2 /Ob2 /D NDEBUG") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "/MT /Zi /O2 /Ob1 /D NDEBUG") endif() + +if(EMSCRIPTEN) + set(CMAKE_CXX_FLAGS_DEBUG_INIT "-g4") +endif() diff --git a/cmake/libpng-macos-arm64.patch b/cmake/libpng-macos-arm64.patch deleted file mode 100644 index 2d0e15cfa..000000000 --- a/cmake/libpng-macos-arm64.patch +++ /dev/null @@ -1,117 +0,0 @@ -diff --git a/extlib/libpng/CMakeLists.txt b/extlib/libpng/CMakeLists.txt -index 42ff0f9025..6834ea332e 100644 ---- a/extlib/libpng/CMakeLists.txt -+++ b/extlib/libpng/CMakeLists.txt -@@ -65,11 +65,22 @@ option(PNG_HARDWARE_OPTIMIZATIONS "Enable hardware optimizations" ON) - set(PNG_PREFIX "" CACHE STRING "Prefix to add to the API function names") - set(DFA_XTRA "" CACHE FILEPATH "File containing extra configuration settings") - -+# CMake currently sets CMAKE_SYSTEM_PROCESSOR to one of x86_64 or arm64 on macOS, -+# based upon the OS architecture, not the target architecture. As such, we need -+# to check CMAKE_OSX_ARCHITECTURES to identify which hardware-specific flags to -+# enable. Note that this will fail if you attempt to build a universal binary in -+# a single cmake invocation. -+if (APPLE AND CMAKE_OSX_ARCHITECTURES) -+ set(TARGET_ARCH ${CMAKE_OSX_ARCHITECTURES}) -+else() -+ set(TARGET_ARCH ${CMAKE_SYSTEM_PROCESSOR}) -+endif() -+ - if(PNG_HARDWARE_OPTIMIZATIONS) - - # Set definitions and sources for ARM. --if(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" OR -- CMAKE_SYSTEM_PROCESSOR MATCHES "^aarch64") -+if(TARGET_ARCH MATCHES "^arm" OR -+ TARGET_ARCH MATCHES "^aarch64") - set(PNG_ARM_NEON_POSSIBLE_VALUES check on off) - set(PNG_ARM_NEON "check" - CACHE STRING "Enable ARM NEON optimizations: check|on|off; check is default") -@@ -95,8 +106,8 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" OR - endif() - - # Set definitions and sources for PowerPC. --if(CMAKE_SYSTEM_PROCESSOR MATCHES "^powerpc*" OR -- CMAKE_SYSTEM_PROCESSOR MATCHES "^ppc64*") -+if(TARGET_ARCH MATCHES "^powerpc*" OR -+ TARGET_ARCH MATCHES "^ppc64*") - set(PNG_POWERPC_VSX_POSSIBLE_VALUES on off) - set(PNG_POWERPC_VSX "on" - CACHE STRING "Enable POWERPC VSX optimizations: on|off; on is default") -@@ -118,8 +129,8 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^powerpc*" OR - endif() - - # Set definitions and sources for Intel. --if(CMAKE_SYSTEM_PROCESSOR MATCHES "^i?86" OR -- CMAKE_SYSTEM_PROCESSOR MATCHES "^x86_64*") -+if(TARGET_ARCH MATCHES "^i?86" OR -+ TARGET_ARCH MATCHES "^x86_64*") - set(PNG_INTEL_SSE_POSSIBLE_VALUES on off) - set(PNG_INTEL_SSE "on" - CACHE STRING "Enable INTEL_SSE optimizations: on|off; on is default") -@@ -141,8 +152,8 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^i?86" OR - endif() - - # Set definitions and sources for MIPS. --if(CMAKE_SYSTEM_PROCESSOR MATCHES "mipsel*" OR -- CMAKE_SYSTEM_PROCESSOR MATCHES "mips64el*") -+if(TARGET_ARCH MATCHES "mipsel*" OR -+ TARGET_ARCH MATCHES "mips64el*") - set(PNG_MIPS_MSA_POSSIBLE_VALUES on off) - set(PNG_MIPS_MSA "on" - CACHE STRING "Enable MIPS_MSA optimizations: on|off; on is default") -@@ -166,26 +177,26 @@ endif() - else(PNG_HARDWARE_OPTIMIZATIONS) - - # Set definitions and sources for ARM. --if(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" OR -- CMAKE_SYSTEM_PROCESSOR MATCHES "^aarch64") -+if(TARGET_ARCH MATCHES "^arm" OR -+ TARGET_ARCH MATCHES "^aarch64") - add_definitions(-DPNG_ARM_NEON_OPT=0) - endif() - - # Set definitions and sources for PowerPC. --if(CMAKE_SYSTEM_PROCESSOR MATCHES "^powerpc*" OR -- CMAKE_SYSTEM_PROCESSOR MATCHES "^ppc64*") -+if(TARGET_ARCH MATCHES "^powerpc*" OR -+ TARGET_ARCH MATCHES "^ppc64*") - add_definitions(-DPNG_POWERPC_VSX_OPT=0) - endif() - - # Set definitions and sources for Intel. --if(CMAKE_SYSTEM_PROCESSOR MATCHES "^i?86" OR -- CMAKE_SYSTEM_PROCESSOR MATCHES "^x86_64*") -+if(TARGET_ARCH MATCHES "^i?86" OR -+ TARGET_ARCH MATCHES "^x86_64*") - add_definitions(-DPNG_INTEL_SSE_OPT=0) - endif() - - # Set definitions and sources for MIPS. --if(CMAKE_SYSTEM_PROCESSOR MATCHES "mipsel*" OR -- CMAKE_SYSTEM_PROCESSOR MATCHES "mips64el*") -+if(TARGET_ARCH MATCHES "mipsel*" OR -+ TARGET_ARCH MATCHES "mips64el*") - add_definitions(-DPNG_MIPS_MSA_OPT=0) - endif() - -@@ -412,19 +412,11 @@ else() - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/scripts/checksym.awk" - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/symbols.def") - -- add_custom_target(symbol-check -- DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/scripts/symbols.chk") -- - generate_copy("${CMAKE_CURRENT_BINARY_DIR}/scripts/sym.out" - "${CMAKE_CURRENT_BINARY_DIR}/libpng.sym") - generate_copy("${CMAKE_CURRENT_BINARY_DIR}/scripts/vers.out" - "${CMAKE_CURRENT_BINARY_DIR}/libpng.vers") - -- add_custom_target(genvers -- DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/libpng.vers") -- add_custom_target(gensym -- DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/libpng.sym") -- - add_custom_target("genprebuilt" - COMMAND "${CMAKE_COMMAND}" - "-DOUTPUT=scripts/pnglibconf.h.prebuilt" diff --git a/developer_docs/IdLists_Entities_and_Remap.txt b/developer_docs/IdLists_Entities_and_Remap.txt index 03ea9d7f3..5fe17916e 100644 --- a/developer_docs/IdLists_Entities_and_Remap.txt +++ b/developer_docs/IdLists_Entities_and_Remap.txt @@ -9,25 +9,25 @@ by pointers from the entity (font, extra points, etc...) Entities in a sketch are kept in a global array (IdList) referenced by a unique Id (handle) and can be looked up by Id in log(n) time via binary search. In -order to use binary seach the array must be kept in order sorted by Id. One +order to use binary search the array must be kept in order sorted by Id. One problem is that insertion takes O(n) time because half the list (on average) must be shifted to make room for a new item. -The IdList class is a template and is used for more than entites. +The IdList class is a template and is used for more than entities. EntityMap: ========== Another important structure is the EntityMap and EntityKey defined in sketch.h This is what allows SovleSpace to update groups when earlier groups in the sketch are changed. If a rectangle is extruded to a box and items are -constrained to entites on that box, the user can go back to the sketch and -modify it. Entites can be added, modified an even deleted. So long as the -entites that are later used to build upon are kept the later extrude group will +constrained to entities on that box, the user can go back to the sketch and +modify it. Entities can be added, modified an even deleted. So long as the +entities that are later used to build upon are kept the later extrude group will pick up the changes from the 2D sketch and anything build on it will remain. The way this works is that each group has a member called remap, which is one of these maps. This is where my understanding is fuzzy. At the end of Group.cpp is -a function called Group::CopyEntity() which is used to make new sketch entites +a function called Group::CopyEntity() which is used to make new sketch entities when a group is created. These are generally copies of entities in the previous group, but there are exceptions. A point will be used to generate a line when extruding a 2D sketch. A point will also be "copied" to a circle for a Lathe @@ -35,7 +35,7 @@ group. For this reason, the entity key is derived by combining its previous key with something often called the CopyNumber or just remap (unfortunate). When a group is regenerated (the first time, or after a previous one is -modified) entites are copied from the old group to the new one. For Step +modified) entities are copied from the old group to the new one. For Step Translating and Rotating there may be many copies, and the copy number is literally N for the Nth copy except for the last one which gets an enum - it is common to constrain the last item, so it gets a large unique number so that @@ -45,5 +45,5 @@ Remap that was created the same way. This is how constructions are preserved across underlying changes. There are some hard limits used in the hash table for the remap mechanism which -limit the number of entites in a group (but not the global sketch). +limit the number of entities in a group (but not the global sketch). diff --git a/developer_docs/Solver_Transforms.txt b/developer_docs/Solver_Transforms.txt index 1ea3ae5f2..98300f8b1 100644 --- a/developer_docs/Solver_Transforms.txt +++ b/developer_docs/Solver_Transforms.txt @@ -46,7 +46,7 @@ POINT_N_ROT_TRANS: Rotates a point via quaternion param[3],param[4],param[5],par POINT_N_COPY: A non-transformed copy of a point - numeric copy? -POINT_N_ROT_AA: A point rotated arount point param[0],param[1],param[2] Where the +POINT_N_ROT_AA: A point rotated around point param[0],param[1],param[2] Where the angle is given by param[3]*timesApplied (times 2?) and the axis of rotation defined by param[4],param[5],param[6] @@ -130,7 +130,7 @@ the entity itself. The ForceTo() functions are shortcuts for using the solver. They are passed the desired location of a point (or orientation of a normal...) and have the opportunity to back-calculate what the group parameters should be to place it there. This is -used for mouse dragging of copied entites. It is notable that the constraints will +used for mouse dragging of copied entities. It is notable that the constraints will still be applied afterward, but this is a good shortcut. When creating a new entity transformation, the first thing to do is define the diff --git a/exposed/CDemo.c b/exposed/CDemo.c index cf6107e79..a5564b3d4 100644 --- a/exposed/CDemo.c +++ b/exposed/CDemo.c @@ -27,6 +27,35 @@ static void *CheckMalloc(size_t n) return r; } +void ExampleStateful() +{ + Slvs_hGroup g = 1; + Slvs_Entity wp = Slvs_AddBase2D(g); + Slvs_Entity p1 = Slvs_AddPoint2D(g, 0.0, 10.0, wp); + Slvs_Entity p2 = Slvs_AddPoint2D(g, 5.0, 20.0, wp); + Slvs_AddLine2D(g, p1, p2, wp); + + // double w, vx, vy, vz; + // Slvs_MakeQuaternion(1, 0, 0, 0, 1, 0, &w, &vx, &vy, &vz); + // Slvs_Entity n = Slvs_AddNormal3D(g, w, vx, vy, vz); + // Slvs_Entity center = Slvs_AddPoint2D(g, 5.0, 5.0, wp); + // Slvs_Entity radius = Slvs_AddDistance(g, 50.0, wp); + // Slvs_Entity c1 = Slvs_AddCircle(g, n, center, radius, wp); + + Slvs_Vertical(g, p1, wp, p2); + Slvs_SolveResult res = Slvs_SolveSketch(g, NULL); + printf("res: %i\n", res.result); + printf("dof: %i\n", res.dof); + double p1x = Slvs_GetParamValue(p1.param[0]); + double p1y = Slvs_GetParamValue(p1.param[1]); + double p2x = Slvs_GetParamValue(p2.param[0]); + double p2y = Slvs_GetParamValue(p2.param[1]); + printf("p1x:%.3f\n", p1x); + printf("p1y:%.3f\n", p1y); + printf("p2x:%.3f\n", p2x); + printf("p2y:%.3f\n", p2y); +} + /*----------------------------------------------------------------------------- * An example of a constraint in 3d. We create a single group, with some * entities and constraints. @@ -60,9 +89,9 @@ void Example3d() /* Let's tell the solver to keep the second point as close to constant * as possible, instead moving the first point. */ - sys.dragged[0] = 4; - sys.dragged[1] = 5; - sys.dragged[2] = 6; + sys.dragged[sys.ndragged++] = 4; + sys.dragged[sys.ndragged++] = 5; + sys.dragged[sys.ndragged++] = 6; /* Now that we have written our system, we solve. */ Slvs_Solve(&sys, g); @@ -251,17 +280,21 @@ void Example2d() int main() { + ExampleStateful(); + sys.param = CheckMalloc(50*sizeof(sys.param[0])); sys.entity = CheckMalloc(50*sizeof(sys.entity[0])); sys.constraint = CheckMalloc(50*sizeof(sys.constraint[0])); + sys.dragged = CheckMalloc(50*sizeof(sys.dragged[0])); sys.failed = CheckMalloc(50*sizeof(sys.failed[0])); sys.faileds = 50; - /*Example3d();*/ + Example3d(); + sys.params = sys.constraints = sys.entities = sys.ndragged = 0; for(;;) { Example2d(); - sys.params = sys.constraints = sys.entities = 0; + sys.params = sys.constraints = sys.entities = sys.ndragged = 0; break; } return 0; diff --git a/exposed/CMakeLists.txt b/exposed/CMakeLists.txt index bdc3fc39c..f0bce9142 100644 --- a/exposed/CMakeLists.txt +++ b/exposed/CMakeLists.txt @@ -1,8 +1,12 @@ -include_directories( - ${CMAKE_SOURCE_DIR}/include) - add_executable(CDemo CDemo.c) -target_link_libraries(CDemo +target_link_libraries(CDemo PRIVATE slvs) + +if(EMSCRIPTEN) + target_link_options(CDemo PRIVATE + "SHELL:-s TOTAL_MEMORY=134217728") + set_target_properties(CDemo PROPERTIES + SUFFIX ".html") +endif() diff --git a/exposed/VbDemo.vb b/exposed/VbDemo.vb index e702957c5..708de93c1 100644 --- a/exposed/VbDemo.vb +++ b/exposed/VbDemo.vb @@ -546,10 +546,8 @@ Module VbDemo Public constraint As IntPtr Public constraints As Integer - Public dragged0 As UInteger - Public dragged1 As UInteger - Public dragged2 As UInteger - Public dragged3 As UInteger + Public dragged As IntPtr + Public ndragged As Integer Public calculatedFaileds As Integer @@ -799,15 +797,13 @@ Module VbDemo ' solution process may be obtained by calling GetResult(), ' GetFaileds(), GetDof(), and GetParamByXXX(). ' - ' The parameters draggedx (indicated by their handles) will be held + ' The parameters dragged (indicated by their handles) will be held ' as close as possible to their original positions, even if this ' results in large moves for other parameters. This feature may be ' useful if, for example, the user is dragging the point whose - ' location is defined by those parameters. Unused draggedx - ' parameters may be specified as zero. + ' location is defined by those parameters. Public Sub Solve(ByVal group As UInteger, - ByVal dragged0 As UInteger, ByVal dragged1 As UInteger, - ByVal dragged2 As UInteger, ByVal dragged3 As UInteger, + ByVal dragged As List(Of UInteger), ByVal calculateFaileds As Boolean) Dim i As Integer @@ -833,9 +829,16 @@ Module VbDemo Next Dim f(Constraints.Count()) As UInteger + Dim dd, d(dragged.Count()) As UInteger + i = 0 + For Each cc In dragged + d(i) = dd + i += 1 + Next + Dim sys As Slvs_System - Dim pgc, egc, cgc As GCHandle + Dim pgc, egc, cgc, dgc As GCHandle pgc = GCHandle.Alloc(p, GCHandleType.Pinned) sys.param = pgc.AddrOfPinnedObject() sys.params = Params.Count() @@ -845,11 +848,9 @@ Module VbDemo cgc = GCHandle.Alloc(c, GCHandleType.Pinned) sys.constraint = cgc.AddrOfPinnedObject() sys.constraints = Constraints.Count() - - sys.dragged0 = dragged0 - sys.dragged1 = dragged1 - sys.dragged2 = dragged2 - sys.dragged3 = dragged3 + dgc = GCHandle.Alloc(d, GCHandleType.Pinned) + sys.dragged = dgc.AddrOfPinnedObject() + sys.ndragged = dragged.Count() Dim fgc As GCHandle fgc = GCHandle.Alloc(f, GCHandleType.Pinned) @@ -882,6 +883,7 @@ Module VbDemo pgc.Free() egc.Free() cgc.Free() + dgc.Free() Result = sys.result Dof = sys.dof @@ -893,11 +895,13 @@ Module VbDemo If TypeOf dragged Is Point2d Then Dim p As Point2d p = dragged - Solve(group, p.up.H, p.vp.H, 0, 0, calculatedFaileds) + Dim d As New List(Of UInteger)({ p.up.H, p.vp.H }) + Solve(group, d, calculatedFaileds) ElseIf TypeOf dragged Is Point3d Then Dim p As Point3d p = dragged - Solve(group, p.xp.H, p.yp.H, p.zp.H, 0, calculatedFaileds) + Dim d As New List(Of UInteger)({ p.xp.H, p.yp.H, p.zp.H }) + Solve(group, d, calculatedFaileds) Else Throw New Exception("Can't get dragged params for point.") End If @@ -905,7 +909,8 @@ Module VbDemo ' or if it's a single distance (e.g., the radius of a circle) Public Sub Solve(ByVal group As UInteger, ByVal dragged As Distance, ByVal calculatedFaileds As Boolean) - Solve(group, dragged.dp.H, 0, 0, 0, calculatedFaileds) + Dim d As New List(Of UInteger)({ dragged.dp.H }) + Solve(group, d, calculatedFaileds) End Sub ' or if it's nothing. Public Sub Solve(ByVal group As UInteger, diff --git a/extlib/angle b/extlib/angle index 8776e911b..3500f85f2 160000 --- a/extlib/angle +++ b/extlib/angle @@ -1 +1 @@ -Subproject commit 8776e911bf71f68518ddd1995ce09d80db8c1216 +Subproject commit 3500f85f2a0da4c1baefc224d14f5f9a9545ddda diff --git a/extlib/cairo b/extlib/cairo index d4724ee92..e4cd47fcd 160000 --- a/extlib/cairo +++ b/extlib/cairo @@ -1 +1 @@ -Subproject commit d4724ee921c4fa399ccbd0019c3d6917452e0ffd +Subproject commit e4cd47fcde357b50a99064d3a65dcf41a0b4dd02 diff --git a/extlib/eigen b/extlib/eigen new file mode 160000 index 000000000..3147391d9 --- /dev/null +++ b/extlib/eigen @@ -0,0 +1 @@ +Subproject commit 3147391d946bb4b6c68edd901f2add6ac1f31f8c diff --git a/extlib/freetype b/extlib/freetype index 069083ccc..42608f77f 160000 --- a/extlib/freetype +++ b/extlib/freetype @@ -1 +1 @@ -Subproject commit 069083cccd73d1d68da68116c8d050bb62cdfe0e +Subproject commit 42608f77f20749dd6ddc9e0536788eaad70ea4b5 diff --git a/extlib/libdxfrw b/extlib/libdxfrw index 0b7b7b709..8359399ff 160000 --- a/extlib/libdxfrw +++ b/extlib/libdxfrw @@ -1 +1 @@ -Subproject commit 0b7b7b709d9299565db603f878214656ef5e9ddf +Subproject commit 8359399ff3eb96aec14fb160c9a5bc4796b60717 diff --git a/extlib/libpng b/extlib/libpng index dbe3e0c43..2b978915d 160000 --- a/extlib/libpng +++ b/extlib/libpng @@ -1 +1 @@ -Subproject commit dbe3e0c43e549a1602286144d94b0666549b18e6 +Subproject commit 2b978915d82377df13fcbb1fb56660195ded868a diff --git a/extlib/mimalloc b/extlib/mimalloc index 4e643b6d3..f81bf1b31 160000 --- a/extlib/mimalloc +++ b/extlib/mimalloc @@ -1 +1 @@ -Subproject commit 4e643b6d3178e0ea2a093b7e14fe621631a91e4b +Subproject commit f81bf1b31af819a31195e08f9546dc80f8931587 diff --git a/extlib/pixman b/extlib/pixman index 127f1a7ad..6564d88d8 160000 --- a/extlib/pixman +++ b/extlib/pixman @@ -1 +1 @@ -Subproject commit 127f1a7ad3eb88f7e74c83f89590fa8ee76853ae +Subproject commit 6564d88d8b872514f8bb7692337b5b1f96d18e98 diff --git a/extlib/zlib b/extlib/zlib index 2fa463bac..51b7f2abd 160000 --- a/extlib/zlib +++ b/extlib/zlib @@ -1 +1 @@ -Subproject commit 2fa463bacfff79181df1a5270fb67cc679a53e71 +Subproject commit 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf diff --git a/include/slvs.h b/include/slvs.h index 1c97fbf82..0e58d461d 100644 --- a/include/slvs.h +++ b/include/slvs.h @@ -10,7 +10,7 @@ #ifndef __SLVS_H #define __SLVS_H -#ifdef WIN32 +#if defined(WIN32) && !defined(STATIC_LIB) # ifdef EXPORT_DLL # define DLL __declspec( dllexport ) # else @@ -29,6 +29,8 @@ typedef unsigned __int32 uint32_t; #else #include #endif +#include +#include typedef uint32_t Slvs_hParam; typedef uint32_t Slvs_hEntity; @@ -39,7 +41,6 @@ typedef uint32_t Slvs_hGroup; * an entity, specify this instead of the workplane. */ #define SLVS_FREE_IN_3D 0 - typedef struct { Slvs_hParam h; Slvs_hGroup group; @@ -113,6 +114,10 @@ typedef struct { #define SLVS_C_WHERE_DRAGGED 100031 #define SLVS_C_CURVE_CURVE_TANGENT 100032 #define SLVS_C_LENGTH_DIFFERENCE 100033 +#define SLVS_C_ARC_ARC_LEN_RATIO 100034 +#define SLVS_C_ARC_LINE_LEN_RATIO 100035 +#define SLVS_C_ARC_ARC_DIFFERENCE 100036 +#define SLVS_C_ARC_LINE_DIFFERENCE 100037 typedef struct { Slvs_hConstraint h; @@ -157,8 +162,14 @@ typedef struct { * that parameter, and attempt to change it as little as possible even * if that requires it to change other parameters more. * - * Unused members of this array should be set to zero. */ - Slvs_hParam dragged[4]; + * Note that the solver is still allowed to change the values of parameters + * specified this way. If you want to lock a dragged entity in place, use + * a SLVS_C_WHERE_DRAGGED constraint (though note that it can overconstrain + * e.g in case the dragged entity has less than two degrees of freedom in + * a workplane or less than 3 degrees of freedom in free space). */ + Slvs_hParam *dragged; + int ndragged; + /* If the solver fails, then it can determine which constraints are * causing the problem. But this is a relatively slow process (for @@ -189,11 +200,15 @@ typedef struct { #define SLVS_RESULT_INCONSISTENT 1 #define SLVS_RESULT_DIDNT_CONVERGE 2 #define SLVS_RESULT_TOO_MANY_UNKNOWNS 3 +#define SLVS_RESULT_REDUNDANT_OKAY 4 int result; } Slvs_System; -DLL void Slvs_Solve(Slvs_System *sys, Slvs_hGroup hg); - +typedef struct { + int result; + int dof; + int nbad; +} Slvs_SolveResult; /* Our base coordinate system has basis vectors * (1, 0, 0) (0, 1, 0) (0, 0, 1) @@ -397,6 +412,103 @@ static inline Slvs_Constraint Slvs_MakeConstraint(Slvs_hConstraint h, return r; } +DLL bool Slvs_IsFreeIn3D(Slvs_Entity e); +DLL bool Slvs_Is3D(Slvs_Entity e); +DLL bool Slvs_IsNone(Slvs_Entity e); +DLL bool Slvs_IsPoint2D(Slvs_Entity e); +DLL bool Slvs_IsPoint3D(Slvs_Entity e); +DLL bool Slvs_IsNormal2D(Slvs_Entity e); +DLL bool Slvs_IsNormal3D(Slvs_Entity e); +DLL bool Slvs_IsLine(Slvs_Entity e); +DLL bool Slvs_IsLine2D(Slvs_Entity e); +DLL bool Slvs_IsLine3D(Slvs_Entity e); +DLL bool Slvs_IsCubic(Slvs_Entity e); +DLL bool Slvs_IsArc(Slvs_Entity e); +DLL bool Slvs_IsWorkplane(Slvs_Entity e); +DLL bool Slvs_IsDistance(Slvs_Entity e); +DLL bool Slvs_IsPoint(Slvs_Entity e); +DLL bool Slvs_IsCircle(Slvs_Entity e); + +static const Slvs_Entity SLVS_E_NONE = { 0 }; +static const Slvs_Entity SLVS_E_FREE_IN_3D = { 0 }; + +DLL Slvs_Entity Slvs_AddPoint2D(uint32_t grouph, double u, double v, Slvs_Entity workplane); +DLL Slvs_Entity Slvs_AddPoint3D(uint32_t grouph, double x, double y, double z); +DLL Slvs_Entity Slvs_AddNormal2D(uint32_t grouph, Slvs_Entity workplane); +DLL Slvs_Entity Slvs_AddNormal3D(uint32_t grouph, double qw, double qx, double qy, double qz); +DLL Slvs_Entity Slvs_AddDistance(uint32_t grouph, double value, Slvs_Entity workplane); +DLL Slvs_Entity Slvs_AddLine2D(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity workplane); +DLL Slvs_Entity Slvs_AddLine3D(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB); +DLL Slvs_Entity Slvs_AddCubic(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity ptC, Slvs_Entity ptD, Slvs_Entity workplane); +DLL Slvs_Entity Slvs_AddArc(uint32_t grouph, Slvs_Entity normal, Slvs_Entity center, Slvs_Entity start, Slvs_Entity end, Slvs_Entity workplane); +DLL Slvs_Entity Slvs_AddCircle(uint32_t grouph, Slvs_Entity normal, Slvs_Entity center, Slvs_Entity radius, Slvs_Entity workplane); +DLL Slvs_Entity Slvs_AddWorkplane(uint32_t grouph, Slvs_Entity origin, Slvs_Entity nm); +DLL Slvs_Entity Slvs_AddBase2D(uint32_t grouph); + + +DLL Slvs_Constraint Slvs_AddConstraint(uint32_t grouph, int type, Slvs_Entity workplane, double val, Slvs_Entity ptA, + Slvs_Entity ptB, Slvs_Entity entityA, + Slvs_Entity entityB, Slvs_Entity entityC, + Slvs_Entity entityD, int other, int other2); +DLL Slvs_Constraint Slvs_Coincident(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_Distance(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, double value, + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_Equal(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_EqualAngle(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity entityC, + Slvs_Entity entityD, + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_EqualPointToLine(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, + Slvs_Entity entityC, Slvs_Entity entityD, + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_Ratio(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, double value, + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_Symmetric(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, + Slvs_Entity entityC , + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_SymmetricH(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB, + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_SymmetricV(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB, + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_Midpoint(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB, + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_Horizontal(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity workplane, + Slvs_Entity entityB); +DLL Slvs_Constraint Slvs_Vertical(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity workplane, + Slvs_Entity entityB); +DLL Slvs_Constraint Slvs_Diameter(uint32_t grouph, Slvs_Entity entityA, double value); +DLL Slvs_Constraint Slvs_SameOrientation(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB); +DLL Slvs_Constraint Slvs_Angle(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, double value, + Slvs_Entity workplane, + int inverse); +DLL Slvs_Constraint Slvs_Perpendicular(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, + Slvs_Entity workplane, + int inverse); +DLL Slvs_Constraint Slvs_Parallel(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_Tangent(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_DistanceProj(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB, double value); +DLL Slvs_Constraint Slvs_LengthDiff(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, double value, + Slvs_Entity workplane); +DLL Slvs_Constraint Slvs_Dragged(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity workplane); + +DLL double Slvs_GetParamValue(uint32_t ph); +DLL void Slvs_SetParamValue(uint32_t ph, double value); + +DLL void Slvs_Solve(Slvs_System *sys, uint32_t hg); +DLL void Slvs_MarkDragged(Slvs_Entity ptA); +/** + * Setting `bad` to a non NULL pointer enables finding of bad constraints. + * If such constraints are found, `bad` is set to a heap allocated array containing + * the handles of those constraints, while the `nbad` member of the returned + * `Slvs_SolveResult` struct is set to the number of the bad constraints. + * NOTE: the user is responsible for freeing the heap allocated memory using `free()`. + */ +DLL Slvs_SolveResult Slvs_SolveSketch(uint32_t hg, Slvs_hConstraint **bad); +DLL void Slvs_ClearSketch(); + #ifdef __cplusplus } #endif diff --git a/js/README.md b/js/README.md new file mode 100644 index 000000000..e1e900c7d --- /dev/null +++ b/js/README.md @@ -0,0 +1,84 @@ +# slvs + +SolveSpace's geometric constraint solver for the browser and node.js + +## example + +```html + + +``` \ No newline at end of file diff --git a/js/slvs.d.ts b/js/slvs.d.ts new file mode 100644 index 000000000..6c1037687 --- /dev/null +++ b/js/slvs.d.ts @@ -0,0 +1,194 @@ +export interface Entity { + h: number; + group: number; + type: number; + wrkpl: number; + normal: number; + distance: number; + param: [number, number, number, number]; +} + +export interface Constraint { + h: number; + group: number; + type: number; + wrkpl: Entity; + valA: number; + ptA: Entity; + ptB: Entity; + entityA: Entity; + entityB: Entity; + entityC: Entity; + entityD: Entity; + other: boolean; + other2: boolean; +} + +export interface SolveResult { + result: number; + dof: number; + nbad: number; + bad: Uint32Array; +} + +export interface Vector { + x: number + y: number + z: number +} + +export interface Quaternion { + w: number + vx: number + vy: number + vz: number + plus(b: Quaternion): Quaternion; + minus(b: Quaternion): Quaternion; + scaledBy(s: number): Quaternion; + magnitude(): number; + withMagnitude(s: number): Quaternion; + rotationU(): Vector; + rotationV(): Vector; + rotationN(): Vector; + rotate(p: Vector): Vector; + toThe(p: number): Quaternion; + inverse(): Quaternion; + times(b: Quaternion): Quaternion; + mirror(): Quaternion; +} + +export interface QuaternionConstructor { + new(): Quaternion; + prototype: Quaternion; + from(w: number, vx: number, vy: number, vz: number): Quaternion; +} + +export interface SlvsModule { + C_POINTS_COINCIDENT: 100000; + C_PT_PT_DISTANCE: 100001; + C_PT_PLANE_DISTANCE: 100002; + C_PT_LINE_DISTANCE: 100003; + C_PT_FACE_DISTANCE: 100004; + C_PT_IN_PLANE: 100005; + C_PT_ON_LINE: 100006; + C_PT_ON_FACE: 100007; + C_EQUAL_LENGTH_LINES: 100008; + C_LENGTH_RATIO: 100009; + C_EQ_LEN_PT_LINE_D: 100010; + C_EQ_PT_LN_DISTANCES: 100011; + C_EQUAL_ANGLE: 100012; + C_EQUAL_LINE_ARC_LEN: 100013; + C_SYMMETRIC: 100014; + C_SYMMETRIC_HORIZ: 100015; + C_SYMMETRIC_VERT: 100016; + C_SYMMETRIC_LINE: 100017; + C_AT_MIDPOINT: 100018; + C_HORIZONTAL: 100019; + C_VERTICAL: 100020; + C_DIAMETER: 100021; + C_PT_ON_CIRCLE: 100022; + C_SAME_ORIENTATION: 100023; + C_ANGLE: 100024; + C_PARALLEL: 100025; + C_PERPENDICULAR: 100026; + C_ARC_LINE_TANGENT: 100027; + C_CUBIC_LINE_TANGENT: 100028; + C_EQUAL_RADIUS: 100029; + C_PROJ_PT_DISTANCE: 100030; + C_WHERE_DRAGGED: 100031; + C_CURVE_CURVE_TANGENT: 100032; + C_LENGTH_DIFFERENCE: 100033; + C_ARC_ARC_LEN_RATIO: 100034; + C_ARC_LINE_LEN_RATIO: 100035; + C_ARC_ARC_DIFFERENCE: 100036; + C_ARC_LINE_DIFFERENCE: 100037; + + E_POINT_IN_3D: 50000; + E_POINT_IN_2D: 50001; + E_NORMAL_IN_3D: 60000; + E_NORMAL_IN_2D: 60001; + E_DISTANCE: 70000; + E_WORKPLANE: 80000; + E_LINE_SEGMENT: 80001; + E_CUBIC: 80002; + E_CIRCLE: 80003; + E_ARC_OF_CIRCLE: 80004; + + E_NONE: Entity; + E_FREE_IN_3D: Entity; + + isFreeIn3D(entity: Entity): boolean; + is3D(entity: Entity): boolean; + isNone(entity: Entity): boolean; + isPoint2D(entity: Entity): boolean; + isPoint3D(entity: Entity): boolean; + isNormal2D(entity: Entity): boolean; + isNormal3D(entity: Entity): boolean; + isLine(entity: Entity): boolean; + isLine2D(entity: Entity): boolean; + isLine3D(entity: Entity): boolean; + isCubic(entity: Entity): boolean; + isArc(entity: Entity): boolean; + isWorkplane(entity: Entity): boolean; + isDistance(entity: Entity): boolean; + isPoint(entity: Entity): boolean; + + addPoint2D(grouph: number, u: number, v: number, workplane: Entity): Entity; + addPoint3D(grouph: number, x: number, y: number, z: number): Entity; + addNormal2D(grouph: number, workplane: Entity): Entity; + addNormal3D(grouph: number, qw: number, qx: number, qy: number, qz: number): Entity; + addDistance(grouph: number, value: number, workplane: Entity): Entity; + addLine2D(grouph: number, ptA: Entity, ptB: Entity, workplane: Entity): Entity; + addLine3D(grouph: number, ptA: Entity, ptB: Entity): Entity; + addCubic(grouph: number, ptA: Entity, ptB: Entity, ptC: Entity, ptD: Entity, workplane: Entity): Entity; + addArc(grouph: number, normal: Entity, center: Entity, start: Entity, end: Entity, workplane: Entity): Entity; + addCircle(grouph: number, normal: Entity, center: Entity, radius: Entity, workplane: Entity): Entity; + addWorkplane(grouph: number, origin: Entity, nm: Entity): Entity; + addBase2D(grouph: number): Entity; + + addConstraint( + grouph: number, + type: number, + workplane: Entity, + val: number, + ptA: Entity, + ptB: Entity, + entityA: Entity, + entityB: Entity, + entityC: Entity, + entityD: Entity, + other: boolean, + other2: boolean, + ): Constraint; + + coincident(grouph: number, entityA: Entity, entityB: Entity, workplane: Entity): Constraint; + distance(grouph: number, entityA: Entity, entityB: Entity, value: number, workplane: Entity): Constraint; + equal(grouph: number, entityA: Entity, entityB: Entity, workplane: Entity): Constraint; + equalAngle(grouph: number, entityA: Entity, entityB: Entity, entityC: Entity, entityD: Entity, workplane: Entity): Constraint; + equalPointToLine(grouph: number, entityA: Entity, entityB: Entity, entityC: Entity, entityD: Entity, workplane: Entity): Constraint; + ratio(grouph: number, entityA: Entity, entityB: Entity, value: number, workplane: Entity): Constraint; + symmetric(grouph: number, entityA: Entity, entityB: Entity, entityC: Entity): Constraint; + symmetricH(grouph: number, ptA: Entity, ptB: Entity, workplane: Entity): Constraint; + symmetricV(grouph: number, ptA: Entity, ptB: Entity, workplane: Entity): Constraint; + midpoint(grouph: number, ptA: Entity, ptB: Entity, workplane: Entity): Constraint; + horizontal(grouph: number, entityA: Entity, workplane: Entity, entityB: Entity): Constraint; + vertical(grouph: number, entityA: Entity, workplane: Entity, entityB: Entity): Constraint; + diameter(grouph: number, entityA: Entity, value: number): Constraint; + sameOrientation(grouph: number, entityA: Entity, entityB: Entity): Constraint; + angle(grouph: number, entityA: Entity, entityB: Entity, value: number, workplane: Entity, inverse: boolean): Constraint; + perpendicular(grouph: number, entityA: Entity, entityB: Entity, workplane: Entity, inverse: boolean): Constraint; + parallel(grouph: number, entityA: Entity, entityB: Entity, workplane: Entity): Constraint; + tangent(grouph: number, entityA: Entity, entityB: Entity, workplane: Entity): Constraint; + distanceProj(grouph: number, ptA: Entity, ptB: Entity, value: number): Constraint; + lengthDiff(grouph: number, entityA: Entity, entityB: Entity, value: number, workplane: Entity): Constraint; + dragged(grouph: number, ptA: Entity, workplane: Entity): Constraint; + + getParamValue(ph: number): number; + setParamValue(ph: number, value: number): number; + solveSketch(hgroup: number, calculateFaileds: boolean): SolveResult; + clearSketch(): void; +} + +declare function ModuleLoader(): Promise; + +export default ModuleLoader; diff --git a/package.json b/package.json new file mode 100644 index 000000000..c9188b7d7 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "slvs", + "version": "0.0.0", + "description": "\"SolveSpace", + "main": "slvs.js", + "types": "slvs.d.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/solvespace/solvespace.git" + }, + "keywords": [], + "author": "", + "license": "GNU GPL V3", + "bugs": { + "url": "https://github.com/solvespace/solvespace/issues" + }, + "homepage": "http://solvespace.com" +} diff --git a/pkg/flatpak/com.solvespace.SolveSpace.json b/pkg/flatpak/com.solvespace.SolveSpace.json index 8c4ce5137..1d6a8fa91 100644 --- a/pkg/flatpak/com.solvespace.SolveSpace.json +++ b/pkg/flatpak/com.solvespace.SolveSpace.json @@ -1,60 +1,97 @@ { + "$schema": "https://raw.githubusercontent.com/TingPing/flatpak-manifest-schema/master/flatpak-manifest.schema", "app-id": "com.solvespace.SolveSpace", - "runtime": "org.gnome.Platform", - "runtime-version": "3.30", - "sdk": "org.gnome.Sdk", + "runtime": "org.freedesktop.Platform", + "runtime-version": "21.08", + "sdk": "org.freedesktop.Sdk", "finish-args": [ - /* Access to display server and OpenGL */ - "--share=ipc", "--socket=fallback-x11", "--socket=wayland", "--device=dri", - /* Access to save files */ - "--filesystem=home" + "--device=dri", + "--share=ipc", + "--socket=fallback-x11", + "--socket=wayland" ], "cleanup": [ - "/include", "/lib/*/include", - "*.a", "*.la", "*.m4", "/lib/libslvs*.so*", "/lib/libglibmm_generate_extra_defs*.so*", - "/share/pkgconfig", "*.pc", - "/share/man", "/share/doc", + "/include", + "/lib/cmake", + "/lib/pkgconfig", "/share/aclocal", - /* mm-common junk */ - "/bin/mm-common-prepare", - "/share/mm-common" + "/share/pkgconfig", + "*.la" ], "command": "solvespace", "modules": [ { "name": "mm-common", + "buildsystem": "meson", "sources": [ { "type": "archive", - "url": "http://ftp.gnome.org/pub/GNOME/sources/mm-common/0.9/mm-common-0.9.12.tar.xz", - "sha256": "ceffdcce1e5b52742884c233ec604bf6fded12eea9da077ce7a62c02c87e7c0b" + "url": "https://download.gnome.org/sources/mm-common/1.0/mm-common-1.0.4.tar.xz", + "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": [ - "--disable-documentation" + "-Dbuild-examples=false" ], "sources": [ { "type": "archive", - "url": "http://ftp.gnome.org/pub/GNOME/sources/libsigc++/2.10/libsigc++-2.10.1.tar.xz", - "sha256": "c9a25f26178c6cbb147f9904d8c533b5a5c5111a41ac2eb781eb734eea446003" + "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": [ - "--disable-documentation" + "-Dbuild-examples=false" ], "sources": [ { "type": "archive", - "url": "http://ftp.gnome.org/pub/GNOME/sources/glibmm/2.58/glibmm-2.58.1.tar.xz", - "sha256": "6e5fe03bdf1e220eeffd543e017fd2fb15bcec9235f0ffd50674aff9362a85f0" + "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*" ] }, { @@ -65,76 +102,152 @@ "sources": [ { "type": "archive", - "url": "http://ftp.gnome.org/pub/GNOME/sources/cairomm/1.12/cairomm-1.12.0.tar.xz", - "sha256": "a54ada8394a86182525c0762e6f50db6b9212a2109280d13ec6a0b29bfd1afe6" + "url": "https://download.gnome.org/sources/cairomm/1.12/cairomm-1.12.0.tar.xz", + "sha256": "a54ada8394a86182525c0762e6f50db6b9212a2109280d13ec6a0b29bfd1afe6", + "x-checker-data": { + "type": "gnome", + "name": "cairomm", + "stable-only": true, + "versions": { + "<": "1.16.0" + } + } } + ], + "cleanup": [ + "/lib/cairomm-*" ] }, { "name": "pangomm", - "config-opts": [ - "--disable-documentation" - ], + "buildsystem": "meson", "sources": [ { "type": "archive", - "url": "http://ftp.gnome.org/pub/GNOME/sources/pangomm/2.40/pangomm-2.40.2.tar.xz", - "sha256": "0a97aa72513db9088ca3034af923484108746dba146e98ed76842cf858322d05" + "url": "https://download.gnome.org/sources/pangomm/2.46/pangomm-2.46.2.tar.xz", + "sha256": "57442ab4dc043877bfe3839915731ab2d693fc6634a71614422fb530c9eaa6f4", + "x-checker-data": { + "type": "gnome", + "name": "pangomm", + "stable-only": true, + "versions": { + "<": "2.48.0" + } + } } + ], + "cleanup": [ + "/lib/pangomm-*" ] }, { "name": "atkmm", - "config-opts": [ - "--disable-documentation" - ], + "buildsystem": "meson", "sources": [ { "type": "archive", - "url": "http://ftp.gnome.org/pub/GNOME/sources/atkmm/2.28/atkmm-2.28.0.tar.xz", - "sha256": "4c4cfc917fd42d3879ce997b463428d6982affa0fb660cafcc0bc2d9afcedd3a" + "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": [ - "--disable-documentation" + "-Dbuild-demos=false", + "-Dbuild-tests=false" ], "sources": [ { "type": "archive", - "url": "http://ftp.gnome.org/pub/GNOME/sources/gtkmm/3.24/gtkmm-3.24.1.tar.xz", - "sha256": "ddfe42ed2458a20a34de252854bcf4b52d3f0c671c045f56b42aa27c7542d2fd" + "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": "libjson-c", + "name": "eigen", + "buildsystem": "cmake-ninja", + "builddir": true, "sources": [ { "type": "archive", - "url": "https://s3.amazonaws.com/json-c_releases/releases/json-c-0.13.1-nodoc.tar.gz", - "sha256": "94a26340c0785fcff4f46ff38609cf84ebcd670df0c8efd75d039cc951d80132" + "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" + } } ], - "buildsystem": "cmake", - "builddir": true + "cleanup": [ + "*" + ] }, { - "name": "SolveSpace", + "name": "libjson-c", + "buildsystem": "cmake-ninja", + "builddir": true, + "config-opts": [ + "-DBUILD_STATIC_LIBS=OFF", + "-DENABLE_THREADING=ON" + ], "sources": [ { - "type": "git", - "path": "/home/whitequark/Projects/solvespace" + "type": "archive", + "url": "https://s3.amazonaws.com/json-c_releases/releases/json-c-0.16.tar.gz", + "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", + ] + }, + { + "name": "solvespace", + "buildsystem": "cmake-ninja", "builddir": true, "config-opts": [ "-DFLATPAK=ON", - "-DENABLE_CLI=OFF", "-DENABLE_TESTS=OFF" + ], + "sources": [ + { + "type": "dir", + "path": "../.." + } + ], + "cleanup": [ + "/lib/libslvs*.so*" ] } ] diff --git a/pkg/snap/snap/snapcraft.yaml b/pkg/snap/snap/snapcraft.yaml index 5fe5f7be6..7d3cf77be 100644 --- a/pkg/snap/snap/snapcraft.yaml +++ b/pkg/snap/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: solvespace -base: core18 +base: core22 summary: Parametric 2d/3d CAD adopt-info: solvespace description: | @@ -14,6 +14,8 @@ description: | confinement: strict license: GPL-3.0 +compression: lzo +grade: stable layout: /usr/share/solvespace: @@ -23,13 +25,13 @@ apps: solvespace: command: usr/bin/solvespace desktop: solvespace.desktop - extensions: [gnome-3-34] + extensions: [gnome] plugs: [opengl, unity7, home, removable-media, gsettings, network] environment: - __EGL_VENDOR_LIBRARY_DIRS: $SNAP/gnome-platform/usr/share/glvnd/egl_vendor.d:$SNAP/usr/share/glvnd/egl_vendor.d + GTK_USE_PORTAL: "0" cli: command: usr/bin/solvespace-cli - extensions: [gnome-3-34] + extensions: [gnome] plugs: [home, removable-media, network] parts: @@ -38,15 +40,15 @@ parts: source: ./solvespace-snap-src source-type: local override-pull: | - snapcraftctl pull - version_major=$(grep "solvespace_VERSION_MAJOR" CMakeLists.txt | tr -d "()" | cut -d" " -f2) - version_minor=$(grep "solvespace_VERSION_MINOR" CMakeLists.txt | tr -d "()" | cut -d" " -f2) - version="$version_major.$version_minor~$(git rev-parse --short=8 HEAD)" - snapcraftctl set-version "$version" - git describe --exact-match HEAD && grade="stable" || grade="devel" - snapcraftctl set-grade "$grade" - git submodule update --init extlib/libdxfrw extlib/mimalloc - configflags: + craftctl default + git submodule update --init extlib/libdxfrw extlib/mimalloc extlib/eigen + override-build: | + craftctl default + project_version=$(grep CMAKE_PROJECT_VERSION:STATIC CMakeCache.txt | cut -d "=" -f2) + cd $CRAFT_PART_SRC + version="$project_version~$(git rev-parse --short=8 HEAD)" + craftctl set version="$version" + cmake-parameters: - -DCMAKE_INSTALL_PREFIX=/usr - -DCMAKE_BUILD_TYPE=Release - -DENABLE_TESTS=OFF @@ -56,6 +58,7 @@ parts: build-packages: - zlib1g-dev - libpng-dev + - libcairo2-dev - libfreetype6-dev - libjson-c-dev - libgl-dev @@ -63,6 +66,7 @@ parts: - libspnav-dev - git - g++ + - libc6-dev stage-packages: - libspnav0 - libsigc++-2.0-0v5 @@ -70,11 +74,14 @@ parts: cleanup: after: [solvespace] plugin: nil - build-snaps: [core18, gnome-3-34-1804] + build-snaps: [gnome-42-2204] override-prime: | - # Remove all files from snap that are already included in the base snap or in - # any connected content snaps set -eux - for snap in "core18" "gnome-3-34-1804"; do # List all content-snaps and base snaps you're using here - cd "/snap/$snap/current" && find . -type f,l -exec rm -f "$SNAPCRAFT_PRIME/{}" \; + 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 "$CRAFT_PRIME/{}" "$CRAFT_PRIME/usr/{}" \; done + for cruft in bug lintian man; do + rm -rf $CRAFT_PRIME/usr/share/$cruft + done + find $CRAFT_PRIME/usr/share/doc/ -type f -not -name 'copyright' -delete + find $CRAFT_PRIME/usr/share -type d -empty -delete diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..2ac8b1072 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[build-system] +requires = ["scikit-build-core", "Cython", "cmake==3.31.6", "ninja"] +build-backend = "scikit_build_core.build" + +[project] +name = "slvs" +version = "0.0.0" +description="SolveSpace solver wrapped with Cython" +readme = "README.md" +authors = [ + { name = "Koen Schmeets", email = "koen@schmeets.de" }, +] +requires-python = ">=3.7" +classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] + +[tool.scikit-build] +wheel.expand-macos-universal-tags = true +cmake.verbose = true +cmake.build-type = "RelWithDebInfo" +build.targets = ["slvs-py"] +install.components = ["python"] +sdist.include = [ + ".git/HEAD", + ".git/refs" +] +wheel.packages = ["python/slvs"] + +[tool.scikit-build.cmake.define] +ENABLE_GUI = "OFF" +ENABLE_CLI = "OFF" +ENABLE_TESTS = "OFF" +ENABLE_COVERAGE = "OFF" +ENABLE_OPENMP = "OFF" +FORCE_VENDORED_Eigen3 = "ON" +ENABLE_LTO = "ON" +ENABLE_PYTHON_LIB = "ON" + +[tool.cibuildwheel] +build-verbosity = 1 \ No newline at end of file diff --git a/python/slvs/__init__.py b/python/slvs/__init__.py new file mode 100644 index 000000000..9595104ff --- /dev/null +++ b/python/slvs/__init__.py @@ -0,0 +1,99 @@ +from .solvespace import ( + add_base_2d, + add_arc, + add_circle, + add_constraint, + add_cubic, + add_distance, + add_line_2d, + add_line_3d, + add_normal_2d, + add_normal_3d, + add_point_2d, + add_point_3d, + add_workplane, + angle, + clear_sketch, + coincident, + diameter, + distance, + distance_proj, + dragged, + equal, + equal_angle, + equal_point_to_line, + get_param_value, + set_param_value, + horizontal, + length_diff, + make_quaternion, + midpoint, + parallel, + perpendicular, + quaternion_n, + quaternion_u, + quaternion_v, + ratio, + same_orientation, + solve_sketch, + symmetric, + symmetric_h, + symmetric_v, + tangent, + vertical, + ResultFlag, + ConstraintType, + E_NONE, + E_FREE_IN_3D +) + +__all__ = [ + "add_base_2d", + "add_arc", + "add_circle", + "add_constraint", + "add_cubic", + "add_distance", + "add_line_2d", + "add_line_3d", + "add_normal_2d", + "add_normal_3d", + "add_point_2d", + "add_point_3d", + "add_workplane", + "angle", + "clear_sketch", + "coincident", + "diameter", + "distance", + "distance_proj", + "dragged", + "equal", + "equal_angle", + "equal_point_to_line", + "get_param_value", + "set_param_value", + "horizontal", + "length_diff", + "make_quaternion", + "midpoint", + "parallel", + "perpendicular", + "quaternion_n", + "quaternion_u", + "quaternion_v", + "ratio", + "same_orientation", + "solve_sketch", + "symmetric", + "symmetric_h", + "symmetric_v", + "tangent", + "vertical", + "ResultFlag", + "ConstraintType", + "E_NONE", + "E_FREE_IN_3D", +] + +__test__ = {} diff --git a/python/slvs/solvespace.pyi b/python/slvs/solvespace.pyi new file mode 100644 index 000000000..54497fcf5 --- /dev/null +++ b/python/slvs/solvespace.pyi @@ -0,0 +1,280 @@ +# -*- coding: utf-8 -*- + +from typing import Tuple, TypedDict +from enum import IntEnum, auto + +class ConstraintType(IntEnum): + """Symbol of the constraint types.""" + POINTS_COINCIDENT = 100000 + PT_PT_DISTANCE = 100001 + PT_PLANE_DISTANCE = 100002 + PT_LINE_DISTANCE = 100003 + PT_FACE_DISTANCE = 100004 + PT_IN_PLANE = 100005 + PT_ON_LINE = 100006 + PT_ON_FACE = 100007 + EQUAL_LENGTH_LINES = 100008 + LENGTH_RATIO = 100009 + EQ_LEN_PT_LINE_D = 100010 + EQ_PT_LN_DISTANCES = 100011 + EQUAL_ANGLE = 100012 + EQUAL_LINE_ARC_LEN = 100013 + SYMMETRIC = 100014 + SYMMETRIC_HORIZ = 100015 + SYMMETRIC_VERT = 100016 + SYMMETRIC_LINE = 100017 + AT_MIDPOINT = 100018 + HORIZONTAL = 100019 + VERTICAL = 100020 + DIAMETER = 100021 + PT_ON_CIRCLE = 100022 + SAME_ORIENTATION = 100023 + ANGLE = 100024 + PARALLEL = 100025 + PERPENDICULAR = 100026 + ARC_LINE_TANGENT = 100027 + CUBIC_LINE_TANGENT = 100028 + EQUAL_RADIUS = 100029 + PROJ_PT_DISTANCE = 100030 + WHERE_DRAGGED = 100031 + CURVE_CURVE_TANGENT = 100032 + LENGTH_DIFFERENCE = 100033 + ARC_ARC_LEN_RATIO = 100034 + ARC_LINE_LEN_RATIO = 100035 + ARC_ARC_DIFFERENCE = 100036 + ARC_LINE_DIFFERENCE = 100037 + +class EntityType(IntEnum): + POINT_IN_3D = 50000 + POINT_IN_2D = 50001 + NORMAL_IN_3D = 60000 + NORMAL_IN_2D = 60001 + DISTANCE = 70000 + WORKPLANE = 80000 + LINE_SEGMENT = 80001 + CUBIC = 80002 + CIRCLE = 80003 + ARC_OF_CIRCLE = 80004 + +class ResultFlag(IntEnum): + """Symbol of the result flags.""" + OKAY = 0 + INCONSISTENT = auto() + DIDNT_CONVERGE = auto() + TOO_MANY_UNKNOWNS = auto() + REDUNDANT_OKAY = auto() + +class Slvs_Entity(TypedDict): + h: int + group: int + type: int + wrkpl: int + point: list[int] + normal: int + distance: int + param: list[int] + +class Slvs_Constraint(TypedDict): + h: int + group: int + type: int + wrkpl: int + valA: float + ptA: int + ptB: int + entityA: int + entityB: int + entityC: int + entityD: int + other: int + other2: int + +class Slvs_SolveResult(TypedDict): + result: int + dof: int + rank: int + bad: int + +E_FREE_IN_3D : Slvs_Entity +E_NONE : Slvs_Entity + +def quaternion_u( + qw: float, + qx: float, + qy: float, + qz: float +) -> Tuple[float, float, float]: + ... + +def quaternion_v( + qw: float, + qx: float, + qy: float, + qz: float +) -> Tuple[float, float, float]: + ... + +def quaternion_n( + qw: float, + qx: float, + qy: float, + qz: float +) -> Tuple[float, float, float]: + ... + +def make_quaternion( + ux: float, + uy: float, + uz: float, + vx: float, + vy: float, + vz: float +) -> Tuple[float, float, float, float]: + ... + +def add_base_2d(grouph: int) -> Slvs_Entity: + ... + +def add_point_2d(grouph: int, u: float, v: float, wp: Slvs_Entity) -> Slvs_Entity: + ... + +def add_point_3d(grouph: int, x: float, y: float, z: float) -> Slvs_Entity: + ... + +def add_normal_2d(grouph: int, wp: Slvs_Entity) -> Slvs_Entity: + ... + +def add_normal_3d(grouph: int, qw: float, qx: float, qy: float, qz: float) -> Slvs_Entity: + ... + +def add_distance(grouph: int, d: float, wp: Slvs_Entity) -> Slvs_Entity: + ... + +def add_line_2d(grouph: int, p1: Slvs_Entity, p2: Slvs_Entity, wp: Slvs_Entity) -> Slvs_Entity: + ... + +def add_line_3d(grouph: int, p1: Slvs_Entity, p2: Slvs_Entity) -> Slvs_Entity: + ... + +def add_cubic(grouph: int, p1: Slvs_Entity, p2: Slvs_Entity, p3: Slvs_Entity, p4: Slvs_Entity, wp: Slvs_Entity) -> Slvs_Entity: + ... + +def add_arc(grouph: int, nm: Slvs_Entity, ct: Slvs_Entity, start: Slvs_Entity, end: Slvs_Entity, wp: Slvs_Entity) -> Slvs_Entity: + ... + +def add_circle(grouph: int, nm: Slvs_Entity, ct: Slvs_Entity, radius: Slvs_Entity, wp: Slvs_Entity) -> Slvs_Entity: + ... + +def add_workplane(grouph: int, origin: Slvs_Entity, nm: Slvs_Entity) -> Slvs_Entity: + ... + +def add_constraint( + grouph: int, + c_type: ConstraintType, + wp: Slvs_Entity, + v: float, + p1: Slvs_Entity = E_NONE, + p2: Slvs_Entity = E_NONE, + e1: Slvs_Entity = E_NONE, + e2: Slvs_Entity = E_NONE, + e3: Slvs_Entity = E_NONE, + e4: Slvs_Entity = E_NONE, + other: int = 0, + other2: int = 0 +) -> Slvs_Constraint: + ... + +def coincident(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity, wp: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + ... + +def distance( + grouph: int, + e1: Slvs_Entity, + e2: Slvs_Entity, + value: float, + wp: Slvs_Entity = E_FREE_IN_3D +) -> Slvs_Constraint: + ... + +def equal(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity, wp: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + ... + +def equal_angle( + grouph: int, + e1: Slvs_Entity, + e2: Slvs_Entity, + e3: Slvs_Entity, + e4: Slvs_Entity, + wp: Slvs_Entity = E_FREE_IN_3D +) -> Slvs_Constraint: + ... + +def equal_point_to_line( + grouph: int, + e1: Slvs_Entity, + e2: Slvs_Entity, + e3: Slvs_Entity, + e4: Slvs_Entity, + wp: Slvs_Entity = E_FREE_IN_3D +) -> Slvs_Constraint: + ... + +def ratio(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity, value: float, wp: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + ... + +def symmetric(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity, e3: Slvs_Entity = E_NONE, wp: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + ... + +def symmetric_h(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity, wp: Slvs_Entity) -> Slvs_Constraint: + ... + +def symmetric_v(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity, wp: Slvs_Entity) -> Slvs_Constraint: + ... + +def midpoint(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity, wp: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + ... + +def horizontal(grouph: int, e1: Slvs_Entity, wp: Slvs_Entity) -> Slvs_Constraint: + ... + +def vertical(grouph: int, e1: Slvs_Entity, wp: Slvs_Entity) -> Slvs_Constraint: + ... + +def diameter(grouph: int, e1: Slvs_Entity, value: float) -> Slvs_Constraint: + ... + +def same_orientation(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity) -> Slvs_Constraint: + ... + +def angle(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity, value: float, wp: Slvs_Entity = E_FREE_IN_3D, inverse: bool = False) -> Slvs_Constraint: + ... + +def perpendicular(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity, wp: Slvs_Entity = E_FREE_IN_3D, inverse: bool = False) -> Slvs_Constraint: + ... + +def parallel(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity, wp: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + ... + +def tangent(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity, wp: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + ... + +def distance_proj(grouph: int, e1: Slvs_Entity, e2: Slvs_Entity, value: float) -> Slvs_Constraint: + ... + +def length_diff(grouph: int, Slvs_entityA: Slvs_Entity, Slvs_entityB: Slvs_Entity, value: float, workplane: Slvs_Entity) -> Slvs_Constraint: + ... + +def dragged(grouph: int, e1: Slvs_Entity, wp: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + ... + +def clear_sketch() -> None: + ... + +def solve_sketch(grouph: int, calculateFaileds: bool) -> Slvs_SolveResult: + ... + +def get_param_value(ph: int) -> float: + ... + +def set_param_value(ph: int, value: float) -> None: + ... \ No newline at end of file diff --git a/python/tests/__main__.py b/python/tests/__main__.py new file mode 100644 index 000000000..7cbb027dd --- /dev/null +++ b/python/tests/__main__.py @@ -0,0 +1,4 @@ +from unittest import defaultTestLoader, TextTestRunner + +if __name__ == '__main__': + TextTestRunner().run(defaultTestLoader.discover('tests', '*.py')) diff --git a/python/tests/test.py b/python/tests/test.py new file mode 100644 index 000000000..9a37d735a --- /dev/null +++ b/python/tests/test.py @@ -0,0 +1,253 @@ +from unittest import TestCase +import slvs +# from solvespace import ConstraintType, E_NONE +from math import radians + +class CoreTest(TestCase): + def test_crank_rocker(self): + """Crank rocker example.""" + print("Crank rocker") + slvs.clear_sketch() + g = 1 + wp = slvs.add_base_2d(g) + p0 = slvs.add_point_2d(g, 0, 0, wp) + slvs.dragged(g, p0, wp) + p1 = slvs.add_point_2d(g, 90, 0, wp) + slvs.dragged(g, p1, wp) + line0 = slvs.add_line_2d(g, p0, p1, wp) + p2 = slvs.add_point_2d(g, 20, 20, wp) + p3 = slvs.add_point_2d(g, 0, 10, wp) + p4 = slvs.add_point_2d(g, 30, 20, wp) + slvs.distance(g, p2, p3, 40, wp) + slvs.distance(g, p2, p4, 40, wp) + slvs.distance(g, p3, p4, 70, wp) + slvs.distance(g, p0, p3, 35, wp) + slvs.distance(g, p1, p4, 70, wp) + line1 = slvs.add_line_2d(g, p0, p3, wp) + slvs.angle(g, line0, line1, 45, wp, False) + # slvs.add_constraint(g, ConstraintType.ANGLE, wp, 45.0, E_NONE, E_NONE, line0, line1) + + result = slvs.solve_sketch(g, False) + self.assertEqual(result['result'], slvs.ResultFlag.OKAY) + x = slvs.get_param_value(p2['param'][0]) + y = slvs.get_param_value(p2['param'][1]) + self.assertAlmostEqual(39.54852, x, 4) + self.assertAlmostEqual(61.91009, y, 4) + + def test_involute(self): + """Involute example.""" + print("Involute") + r = 10 + angle = 45 + slvs.clear_sketch() + g = 1 + wp = slvs.add_base_2d(g) + p0 = slvs.add_point_2d(g, 0, 0, wp) + slvs.dragged(g, p0, wp) + p1 = slvs.add_point_2d(g, 0, 10, wp) + slvs.distance(g, p0, p1, r, wp) + line0 = slvs.add_line_2d(g, p0, p1, wp) + + p2 = slvs.add_point_2d(g, 10, 10, wp) + line1 = slvs.add_line_2d(g, p1, p2, wp) + slvs.distance(g, p1, p2, r * radians(angle), wp) + slvs.perpendicular(g, line0, line1, wp, False) + + p3 = slvs.add_point_2d(g, 10, 0, wp) + slvs.dragged(g, p3, wp) + line_base = slvs.add_line_2d(g, p0, p3, wp) + slvs.angle(g, line0, line_base, angle, wp, False) + + result = slvs.solve_sketch(g, False) + print("result", result) + self.assertEqual(result['result'], slvs.ResultFlag.OKAY) + x = slvs.get_param_value(p2['param'][0]) + y = slvs.get_param_value(p2['param'][1]) + self.assertAlmostEqual(12.62467, x, 4) + self.assertAlmostEqual(1.51746, y, 4) + + def test_jansen_linkage(self): + """Jansen's linkage example.""" + + print("Jansen's linkage") + slvs.clear_sketch() + g = 1 + wp = slvs.add_base_2d(g) + p0 = slvs.add_point_2d(g, 0, 0, wp) + slvs.dragged(g, p0, wp) + + p1 = slvs.add_point_2d(g, 0, 20, wp) + slvs.distance(g, p0, p1, 15, wp) + line0 = slvs.add_line_2d(g, p0, p1, wp) + + p2 = slvs.add_point_2d(g, -38, -7.8, wp) + slvs.dragged(g, p2, wp) + p3 = slvs.add_point_2d(g, -50, 30, wp) + p4 = slvs.add_point_2d(g, -70, -15, wp) + slvs.distance(g, p2, p3, 41.5, wp) + slvs.distance(g, p3, p4, 55.8, wp) + slvs.distance(g, p2, p4, 40.1, wp) + + p5 = slvs.add_point_2d(g, -50, -50, wp) + p6 = slvs.add_point_2d(g, -10, -90, wp) + p7 = slvs.add_point_2d(g, -20, -40, wp) + slvs.distance(g, p5, p6, 65.7, wp) + slvs.distance(g, p6, p7, 49.0, wp) + slvs.distance(g, p5, p7, 36.7, wp) + + slvs.distance(g, p1, p3, 50, wp) + slvs.distance(g, p1, p7, 61.9, wp) + + p8 = slvs.add_point_2d(g, 20, 0, wp) + line_base = slvs.add_line_2d(g, p0, p8, wp) + slvs.angle(g, line0, line_base, 45, wp, False) + + result = slvs.solve_sketch(g, False) + print("result", result) + self.assertEqual(result['result'], slvs.ResultFlag.OKAY) + x = slvs.get_param_value(p8['param'][0]) + y = slvs.get_param_value(p8['param'][1]) + self.assertAlmostEqual(18.93036, x, 4) + self.assertAlmostEqual(13.63778, y, 4) + + def test_nut_cracker(self): + print("Nut cracker") + + h0 = 0.5 + b0 = 0.75 + r0 = 0.25 + n1 = 1.5 + n2 = 2.3 + l0 = 3.25 + + slvs.clear_sketch() + g = 1 + wp = slvs.add_base_2d(g) + p0 = slvs.add_point_2d(g, 0, 0, wp) + slvs.dragged(g, p0, wp) + + p1 = slvs.add_point_2d(g, 2, 2, wp) + p2 = slvs.add_point_2d(g, 2, 0, wp) + line0 = slvs.add_line_2d(g, p0, p2, wp) + slvs.horizontal(g, line0, wp) + line1 = slvs.add_line_2d(g, p1, p2, wp) + p3 = slvs.add_point_2d(g, b0 / 2, h0, wp) + slvs.dragged(g, p3, wp) + slvs.distance(g, p3, line1, r0, wp) + slvs.distance(g, p0, p1, n1, wp) + slvs.distance(g, p1, p2, n2, wp) + + result = slvs.solve_sketch(g, False) + print("result", result) + self.assertEqual(result['result'], slvs.ResultFlag.OKAY) + x = slvs.get_param_value(p2['param'][0]) + print("x", x) + ans_min = x - b0 / 2 + ans_max = l0 - r0 - b0 / 2 + self.assertAlmostEqual(1.01576, ans_min, 4) + self.assertAlmostEqual(2.625, ans_max, 4) + + def test_pydemo(self): + """Some sample code for slvs.dll. + We draw some geometric entities, provide initial guesses for their positions, + and then constrain them. The solver calculates their new positions, + in order to satisfy the constraints. + + Copyright 2008-2013 Jonathan Westhues. + Copyright 2016-2017 Yuan Chang [pyslvs@gmail.com] Solvespace bundled. + + An example of a constraint in 2d. In our first group, we create a workplane + along the reference frame's xy plane. In a second group, we create some + entities in that group and dimension them. + """ + + slvs.clear_sketch() + g1 = 1 + g2 = 2 + # First, we create our workplane. Its origin corresponds to the origin + # of our base frame (x y z) = (0 0 0) + p101 = slvs.add_point_3d(g1, 0, 0, 0) + # and it is parallel to the xy plane, so it has basis vectors (1 0 0) + # and (0 1 0). + (w, vx, vy, vz) = slvs.make_quaternion(1, 0, 0, 0, 1, 0) + n102 = slvs.add_normal_3d(g1, w, vx, vy, vz) + wp200 = slvs.add_workplane(g1, p101, n102) + + # Now create a second group. We'll solve group 2, while leaving group 1 + # constant; so the workplane that we've created will be locked down, + # and the solver can't move it. + p301 = slvs.add_point_2d(g2, 10, 20, wp200) + p302 = slvs.add_point_2d(g2, 20, 10, wp200) + + # And we create a line segment with those endpoints. + l400 = slvs.add_line_2d(g2, p301, p302, wp200) + + # Now three more points. + p303 = slvs.add_point_2d(g2, 100, 120, wp200) + p304 = slvs.add_point_2d(g2, 120, 110, wp200) + p305 = slvs.add_point_2d(g2, 115, 115, wp200) + + # And arc, centered at point 303, starting at point 304, ending at + # point 305. + a401 = slvs.add_arc(g2, n102, p303, p304, p305, wp200) + + # Now one more point, and a distance + p306 = slvs.add_point_2d(g2, 200, 200, wp200) + d307 = slvs.add_distance(g2, 30, wp200) + + # And a complete circle, centered at point 306 with radius equal to + # distance 307. The normal is 102, the same as our workplane. + c402 = slvs.add_circle(g2, n102, p306, d307, wp200) + + # The length of our line segment is 30.0 units. + slvs.distance(g2, p301, p302, 30, wp200) + + # And the distance from our line segment to the origin is 10.0 units. + slvs.distance(g2, p101, l400, 10, wp200) + + # And the line segment is vertical. + slvs.vertical(g2, l400, wp200) + + # And the distance from one endpoint to the origin is 15.0 units. + slvs.distance(g2, p301, p101, 15, wp200) + + # The arc and the circle have equal radius. + slvs.equal(g2, a401, c402, wp200) + + # The arc has radius 17.0 units. + slvs.diameter(g2, a401, 17 * 2) + + # If the solver fails, then aslvs it to report which constraints caused + # the problem. + + # And solve. + g1result = slvs.solve_sketch(g1, False) + g2result = slvs.solve_sketch(g2, False) + self.assertEqual(g2result['result'], slvs.ResultFlag.OKAY) + x = slvs.get_param_value(p301['param'][0]) + y = slvs.get_param_value(p301['param'][1]) + self.assertAlmostEqual(10, x, 4) + self.assertAlmostEqual(11.18030, y, 4) + x = slvs.get_param_value(p302['param'][0]) + y = slvs.get_param_value(p302['param'][1]) + self.assertAlmostEqual(10, x, 4) + self.assertAlmostEqual(-18.81966, y, 4) + x = slvs.get_param_value(p303['param'][0]) + y = slvs.get_param_value(p303['param'][1]) + self.assertAlmostEqual(101.11418, x, 4) + self.assertAlmostEqual(119.04153, y, 4) + x = slvs.get_param_value(p304['param'][0]) + y = slvs.get_param_value(p304['param'][1]) + self.assertAlmostEqual(116.47661, x, 4) + self.assertAlmostEqual(111.76171, y, 4) + x = slvs.get_param_value(p305['param'][0]) + y = slvs.get_param_value(p305['param'][1]) + self.assertAlmostEqual(117.40922, x, 4) + self.assertAlmostEqual(114.19676, y, 4) + x = slvs.get_param_value(p306['param'][0]) + y = slvs.get_param_value(p306['param'][1]) + self.assertAlmostEqual(200, x, 4) + self.assertAlmostEqual(200, y, 4) + x = slvs.get_param_value(d307['param'][0]) + self.assertAlmostEqual(17, x, 4) + self.assertEqual(6, g2result['dof']) diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt index 0f737a1e3..7e1b2db3a 100644 --- a/res/CMakeLists.txt +++ b/res/CMakeLists.txt @@ -83,6 +83,11 @@ elseif(APPLE) DEPENDS ${source} VERBATIM) endfunction() +elseif(EMSCRIPTEN) + function(add_resource name) + set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) + set(resource_list "${resource_list};${source}" PARENT_SCOPE) + endfunction() else() # Unix include(GNUInstallDirs) @@ -111,7 +116,7 @@ endif() function(add_resources) foreach(name ${ARGN}) add_resource(${name}) - set(resource_list "${resource_list}" PARENT_SCOPE) + set(resource_list "${resource_list}" PARENT_SCOPE) endforeach() endfunction() @@ -134,6 +139,13 @@ else() DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications RENAME com.solvespace.SolveSpace.desktop) + set(DESKTOP_FILE_NAME com.solvespace.SolveSpace.desktop) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/freedesktop/com.solvespace.SolveSpace.metainfo.xml.in + ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/com.solvespace.SolveSpace.metainfo.xml) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/com.solvespace.SolveSpace.metainfo.xml + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo) + install(FILES freedesktop/solvespace-flatpak-mime.xml DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mime/packages RENAME com.solvespace.SolveSpace-slvs.xml) @@ -170,22 +182,38 @@ else() RENAME snap.solvespace.png) endforeach() else() + if(USE_QT_GUI) + set(SS_EXE solvespace-qt) + else() + set(SS_EXE solvespace) + endif() configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/freedesktop/solvespace.desktop.in - ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/solvespace.desktop) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/solvespace.desktop + ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/${SS_EXE}.desktop) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/${SS_EXE}.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications) + set(DESKTOP_FILE_NAME ${SS_EXE}.desktop) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/freedesktop/com.solvespace.SolveSpace.metainfo.xml.in + ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/com.solvespace.SolveSpace.metainfo.xml) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/com.solvespace.SolveSpace.metainfo.xml + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo) + install(FILES freedesktop/solvespace-mime.xml DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mime/packages RENAME solvespace-slvs.xml) - + + install(FILES freedesktop/solvespace.thumbnailer + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/thumbnailers + RENAME solvespace.thumbnailer) + install(FILES freedesktop/solvespace-scalable.svg DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps RENAME solvespace.svg) install(FILES freedesktop/solvespace-scalable.svg DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/mimetypes - RENAME application.x-solvespace.svg) + RENAME application-x-solvespace.svg) foreach(SIZE 16x16 24x24 32x32 48x48) install(FILES freedesktop/solvespace-${SIZE}.png @@ -193,12 +221,7 @@ else() RENAME solvespace.png) install(FILES freedesktop/solvespace-${SIZE}.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}/mimetypes - RENAME application.x-solvespace.png) - endforeach() - - foreach(SIZE 16x16 24x24 32x32 48x48) - install(FILES freedesktop/solvespace-${SIZE}.xpm - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pixmaps) + RENAME application-x-solvespace.png) endforeach() endif() endif() @@ -240,6 +263,8 @@ add_resources( icons/graphics-window/trim.png icons/graphics-window/vert.png icons/text-window/constraint.png + icons/text-window/constraint-dimo.png + icons/text-window/constraint-wo.png icons/text-window/construction.png icons/text-window/edges.png icons/text-window/faces.png @@ -253,13 +278,16 @@ add_resources( icons/text-window/shaded.png icons/text-window/workplane.png locales.txt + locales/cs_CZ.po locales/de_DE.po locales/en_US.po locales/fr_FR.po locales/uk_UA.po + locales/es_AR.po locales/tr_TR.po locales/ru_RU.po locales/zh_CN.po + locales/ja_JP.po fonts/unifont.hex.gz fonts/private/0-check-false.png fonts/private/1-check-true.png @@ -291,7 +319,8 @@ add_resources( # Third, distribute the resources. add_custom_target(resources - DEPENDS ${resource_list}) + DEPENDS ${resource_list} + SOURCES ${resource_list}) if(WIN32) set_property(TARGET resources PROPERTY EXTRA_SOURCES ${rc_file}) endif() diff --git a/res/fonts/unicode.lff.gz b/res/fonts/unicode.lff.gz index 6d35aecf6..2227958ca 100644 Binary files a/res/fonts/unicode.lff.gz and b/res/fonts/unicode.lff.gz differ diff --git a/res/freedesktop/com.solvespace.SolveSpace.metainfo.xml.in b/res/freedesktop/com.solvespace.SolveSpace.metainfo.xml.in new file mode 100644 index 000000000..d56a38880 --- /dev/null +++ b/res/freedesktop/com.solvespace.SolveSpace.metainfo.xml.in @@ -0,0 +1,128 @@ + + + com.solvespace.SolveSpace + + SolveSpace + A free (GPLv3) parametric 3d CAD tool + + CC0-1.0 + GPL-3.0-or-later + rylie_AT_ryliepavlik.com + + + Graphics + 3DGraphics + Engineering + + +

+ SolveSpace is a free (GPLv3) parametric 3d CAD tool. Applications include: +

+
    +
  • Modeling 3d parts — draw with extrudes, revolves/helix, and Boolean operations
  • +
  • Modeling 2d parts — draw the part as a single section, and export; use 3d assembly to verify fit
  • +
  • Modeling 3d-printed parts — export the STL or other triangle mesh expected by most slicers
  • +
  • Preparing 2D CAM data — export 2d vector art for a waterjet machine or laser cutter
  • +
  • Mechanism design — use the constraint solver to simulate planar or spatial linkages
  • +
  • Plane and solid geometry — replace hand-solved trigonometry with a live dimensioned drawing
  • +
+
+ https://solvespace.com + https://github.com/solvespace/solvespace/issues + + @DESKTOP_FILE_NAME@ + + + + Main window with an empty document + https://solvespace.com/pics/window-linux-main.png + + + Property Browser with an empty document + https://solvespace.com/pics/window-linux-property-browser.png + + + Viewing and editing constraints on a model + https://solvespace.com/pics/front-page-pic.png + + + 3D view of a stand made from notched angle iron, from the "ex-stand" project + https://solvespace.com/pics/ex-stand-detail.jpg + + + Dimensioning a 2D sketch for a case for a printed circuit board, from the "ex-case" project + https://solvespace.com/pics/ex-case-outline.png + + + Showing tracing of Chebyshev's linkage, from the "ex-chebyshev" project + https://solvespace.com/pics/ex-chebyshev.png + + + + + application/x-solvespace + + + + + + + +

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.

+
+ https://github.com/solvespace/solvespace/releases/tag/v3.0 +
+ + +

Major new stable release. Includes new intersection boolean operation, + new types of groups, solid model suppression, usability improvements + (especially regarding redundant constraints and automatic constraints), + and more. Also includes performance and scalability improvements.

+
+ https://github.com/solvespace/solvespace/releases/tag/v3.0 +
+ + + +

Second release candidate for the 3.0 stable release.

+
+ https://github.com/solvespace/solvespace/releases/tag/v3.0.rc2 +
+ + + +

First release candidate for the 3.0 stable release.

+
+ https://github.com/solvespace/solvespace/releases/tag/v3.0.rc1 +
+ + + +

Bug-fix release in the 2.x series, fixing some crashes.

+
+ https://github.com/solvespace/solvespace/releases/tag/v2.3 +
+ + + +

Bug-fix release, including performance improvements.

+
+ https://github.com/solvespace/solvespace/releases/tag/v2.2 +
+ + + +

Introduced *nix compatibility, internationalization, technical drawing mode, improved import and export, and other features and fixes.

+
+ https://github.com/solvespace/solvespace/releases/tag/v2.1 +
+
+ +
diff --git a/res/freedesktop/solvespace-16x16.xpm b/res/freedesktop/solvespace-16x16.xpm deleted file mode 100644 index 3cd06622d..000000000 --- a/res/freedesktop/solvespace-16x16.xpm +++ /dev/null @@ -1,27 +0,0 @@ -/* XPM */ -static char *solvespace_16x16[] = { -/* columns rows colors chars-per-pixel */ -"16 16 5 1 ", -" c black", -". c #1ED500", -"X c #DE00D6", -"o c #CBCBCB", -"O c None", -/* pixels */ -"OOO OOOOOOOOOOOO", -"OOO OOOOOOOOOOOO", -"OOO OOOOOOOOOOOO", -"OOO OOOOOXOOOOOO", -"OOO OOOOOXoOOOOO", -"OOO OOOOOXoOOOOO", -"OOO OOOOOXoOOOOO", -"OOO OOOOOXoOOOOO", -"OOO OOOOOXoOOOOO", -"OOO OOXXXXXXXOOO", -"OOO OOOoooooooOO", -"OO...OOOOOOOOOOO", -" ... ", -"OO...OOOOOOOOOOO", -"OOO OOOOOOOOOOOO", -"OOO OOOOOOOOOOOO" -}; diff --git a/res/freedesktop/solvespace-24x24.xpm b/res/freedesktop/solvespace-24x24.xpm deleted file mode 100644 index 9d1c02e69..000000000 --- a/res/freedesktop/solvespace-24x24.xpm +++ /dev/null @@ -1,35 +0,0 @@ -/* XPM */ -static char *solvespace_24x24[] = { -/* columns rows colors chars-per-pixel */ -"24 24 5 1 ", -" c black", -". c #1ED500", -"X c #DE00D6", -"o c #CBCBCB", -"O c None", -/* pixels */ -"OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOO OOOOOOOOOOOOOOOO", -"OOOOOOO OOOOOOOOOOOOOOOO", -"OOOOOOO OOOOOOOOOOOOOOOO", -"OOOOOOO OOOOOXOOOOOOOOOO", -"OOOOOOO OOOOOXoOOOOOOOOO", -"OOOOOOO OOOOOXoOOOOOOOOO", -"OOOOOOO OOOOOXoOOOOOOOOO", -"OOOOOOO OOOOOXoOOOOOOOOO", -"OOOOOOO OOOOOXoOOOOOOOOO", -"OOOOOOO OOXXXXXXXOOOOOOO", -"OOOOOOO OOOoooooooOOOOOO", -"OOOOOO...OOOOOOOOOOOOOOO", -"OOOO ... OOOO", -"OOOOOO...OOOOOOOOOOOOOOO", -"OOOOOOO OOOOOOOOOOOOOOOO", -"OOOOOOO OOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOO" -}; diff --git a/res/freedesktop/solvespace-32x32.xpm b/res/freedesktop/solvespace-32x32.xpm deleted file mode 100644 index 0d7c59872..000000000 --- a/res/freedesktop/solvespace-32x32.xpm +++ /dev/null @@ -1,43 +0,0 @@ -/* XPM */ -static char *solvespace_32x32[] = { -/* columns rows colors chars-per-pixel */ -"32 32 5 1 ", -" c black", -". c #1ED500", -"X c #DE00D6", -"o c #CBCBCB", -"O c None", -/* pixels */ -"OOOOOO OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOXXOOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOXXoOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOXXoOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOXXoOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOXXoOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOXXoOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOXXoOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOXXoOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOXXoOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOXXoOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOXXoOOOOOOOOOOOO", -"OOOOOO OOOOXXXXXXXXXXXXOOOOOOOO", -"OOOOOO OOOOXXXXXXXXXXXXOOOOOOOO", -"OOOOOO OOOOOooooooooooooOOOOOOO", -"OOOOOO OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOO......OOOOOOOOOOOOOOOOOOOOOO", -"OOOO......OOOOOOOOOOOOOOOOOOOOOO", -" ...... ", -" ...... ", -"OOOO......OOOOOOOOOOOOOOOOOOOOOO", -"OOOO......OOOOOOOOOOOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOO OOOOOOOOOOOOOOOOOOOOOOOO" -}; diff --git a/res/freedesktop/solvespace-48x48.xpm b/res/freedesktop/solvespace-48x48.xpm deleted file mode 100644 index c5adf53b4..000000000 --- a/res/freedesktop/solvespace-48x48.xpm +++ /dev/null @@ -1,59 +0,0 @@ -/* XPM */ -static char *solvespace_48x48[] = { -/* columns rows colors chars-per-pixel */ -"48 48 5 1 ", -" c black", -". c #1ED500", -"X c #DE00D6", -"o c #CBCBCB", -"O c None", -/* pixels */ -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOXXOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOXXoOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOXXoOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOXXoOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOXXoOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOXXoOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOXXoOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOXXoOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOXXoOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOXXoOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOXXoOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOXXXXXXXXXXXXOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOXXXXXXXXXXXXOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOooooooooooooOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOO......OOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOO......OOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOO ...... OOOOOOOO", -"OOOOOOOO ...... OOOOOOOO", -"OOOOOOOOOOOO......OOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOO......OOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOO OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", -"OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO" -}; diff --git a/res/freedesktop/solvespace-flatpak.desktop.in b/res/freedesktop/solvespace-flatpak.desktop.in index b16ccf44e..f07f29342 100644 --- a/res/freedesktop/solvespace-flatpak.desktop.in +++ b/res/freedesktop/solvespace-flatpak.desktop.in @@ -2,9 +2,9 @@ Version=1.0 Name=SolveSpace Comment=A parametric 2d/3d CAD -Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace +Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace %f MimeType=application/x-solvespace Icon=com.solvespace.SolveSpace Type=Application -Categories=Graphics +Categories=Graphics;3DGraphics;Engineering; Keywords=parametric;cad;2d;3d; diff --git a/res/freedesktop/solvespace-snap.desktop b/res/freedesktop/solvespace-snap.desktop index 8441258c9..7beceb045 100644 --- a/res/freedesktop/solvespace-snap.desktop +++ b/res/freedesktop/solvespace-snap.desktop @@ -2,9 +2,9 @@ Version=1.0 Name=SolveSpace Comment=A parametric 2d/3d CAD -Exec=solvespace +Exec=solvespace %f MimeType=application/x-solvespace Icon=${SNAP}/meta/icons/hicolor/scalable/apps/snap.solvespace.svg Type=Application -Categories=Graphics +Categories=Graphics;3DGraphics;Engineering; Keywords=parametric;cad;2d;3d; diff --git a/res/freedesktop/solvespace.desktop.in b/res/freedesktop/solvespace.desktop.in index 8c6fb24a9..a8e4db783 100644 --- a/res/freedesktop/solvespace.desktop.in +++ b/res/freedesktop/solvespace.desktop.in @@ -2,9 +2,9 @@ Version=1.0 Name=SolveSpace Comment=A parametric 2d/3d CAD -Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace +Exec=${CMAKE_INSTALL_FULL_BINDIR}/${SS_EXE} %f MimeType=application/x-solvespace Icon=solvespace Type=Application -Categories=Graphics +Categories=Graphics;3DGraphics;Engineering; Keywords=parametric;cad;2d;3d; diff --git a/res/freedesktop/solvespace.thumbnailer b/res/freedesktop/solvespace.thumbnailer new file mode 100644 index 000000000..2b84c7570 --- /dev/null +++ b/res/freedesktop/solvespace.thumbnailer @@ -0,0 +1,4 @@ +[Thumbnailer Entry] +TryExec=solvespace-cli +Exec=solvespace-cli thumbnail --view isometric --size %sx%s --output %o %i +MimeType=application/x-solvespace diff --git a/res/icons/text-window/constraint-dimo.png b/res/icons/text-window/constraint-dimo.png new file mode 100644 index 000000000..bbe6e22c9 Binary files /dev/null and b/res/icons/text-window/constraint-dimo.png differ diff --git a/res/icons/text-window/constraint-wo.png b/res/icons/text-window/constraint-wo.png new file mode 100644 index 000000000..c3a445eff Binary files /dev/null and b/res/icons/text-window/constraint-wo.png differ diff --git a/res/locales.txt b/res/locales.txt index daedc309c..377d65754 100644 --- a/res/locales.txt +++ b/res/locales.txt @@ -1,9 +1,12 @@ # 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. +cs-CZ,1029,Česky de-DE,0407,Deutsch en-US,0409,English (US) fr-FR,040C,Français +es-AR,2C0A,español (AR) ru-RU,0419,Русский tr-TR,041F,Türkçe uk-UA,0422,Українська zh-CN,0804,简体中文 +ja-JP,0411,日本語 diff --git a/res/locales/cs_CZ.po b/res/locales/cs_CZ.po new file mode 100644 index 000000000..b0a3e3da1 --- /dev/null +++ b/res/locales/cs_CZ.po @@ -0,0 +1,2300 @@ +# Czech translations for SolveSpace package. +# Copyright (C) 2022 the SolveSpace authors +# This file is distributed under the same license as the SolveSpace package. +# Pavel Stržínek , 2022 +# +msgid "" +msgstr "" +"Project-Id-Version: SolveSpace 3.1\n" +"Report-Msgid-Bugs-To: phkahler@gmail.com\n" +"POT-Creation-Date: 2025-01-26 21:04+0200\n" +"PO-Revision-Date: 2022-06-19 20:16+0200\n" +"Last-Translator: Pavel Stržínek \n" +"Language-Team: none\n" +"Language: cs_CZ\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: clipboard.cpp:314 +msgid "" +"Cut, paste, and copy work only in a workplane.\n" +"\n" +"Activate one with Sketch -> In Workplane." +msgstr "" +"Kopírování, vkládání a vyjímání je funkční jen v pracovní rovině.\n" +"\n" +"Aktivuj ji přes Náčrt -> V pracovní rovině." + +#: clipboard.cpp:331 +msgid "Clipboard is empty; nothing to paste." +msgstr "Schránka je prázdná; nic ke vložení." + +#: clipboard.cpp:378 +msgid "Number of copies to paste must be at least one." +msgstr "Je třeba zadat alespoň jednu kopii ke vložení." + +#: clipboard.cpp:394 textscreens.cpp:879 +msgid "Scale cannot be zero." +msgstr "Měřítko nemůže být nulové." + +#: clipboard.cpp:436 +msgid "Select one point to define origin of rotation." +msgstr "Vyber jeden bod pro určení počátku otočení." + +#: clipboard.cpp:448 +msgid "Select two points to define translation vector." +msgstr "Vyber dva body pro určení vektoru posunutí." + +#: clipboard.cpp:458 +msgid "" +"Transformation is identity. So all copies will be exactly on top of each " +"other." +msgstr "Transformace je identická. Všechny kopie budou přesně nad sebou." + +#: clipboard.cpp:462 +msgid "Too many items to paste; split this into smaller pastes." +msgstr "Příliš mnoho položek ke vložení; rozděl je a vlož po částech." + +#: clipboard.cpp:467 +msgid "No workplane active." +msgstr "Není aktivní žádná pracovní rovina." + +#: confscreen.cpp:410 +msgid "Bad format: specify coordinates as x, y, z" +msgstr "Chybný formát: zadej souřadnice jako x, y, z" + +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 +msgid "Bad format: specify color as r, g, b" +msgstr "Chybný formát: zadej barvu jako r, g, b" + +#: confscreen.cpp:446 +msgid "" +"The perspective factor will have no effect until you enable View -> Use " +"Perspective Projection." +msgstr "" +"Faktor perspektivy nebude mít žádný vliv, dokud nezvolíš Pohled -> Použít " +"perspektivní projekci." + +#: confscreen.cpp:464 confscreen.cpp:474 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "Urči počet platných číslic za desetinnou čárkou mezi 0 a %d." + +#: confscreen.cpp:486 +msgid "Export scale must not be zero!" +msgstr "Měřítko exportu nesmí být nulové!" + +#: confscreen.cpp:498 +msgid "Cutter radius offset must not be negative!" +msgstr "Posunutí poloměru výřezu nesmí být záporné!" + +#: confscreen.cpp:557 +msgid "Bad value: autosave interval should be positive" +msgstr "Chybná hodnota: interval automatického ukládání by měl být kladný" + +#: confscreen.cpp:560 +msgid "Bad format: specify interval in integral minutes" +msgstr "Chybný formát: zadej interval v celých minutách" + +#: constraint.cpp:12 +msgctxt "constr-name" +msgid "pts-coincident" +msgstr "shoda-bodů" + +#: constraint.cpp:13 +msgctxt "constr-name" +msgid "pt-pt-distance" +msgstr "vzdálenost-dvou-bodů" + +#: constraint.cpp:14 +msgctxt "constr-name" +msgid "pt-line-distance" +msgstr "vzdálenost-bodu-a-úsečky" + +#: constraint.cpp:15 +msgctxt "constr-name" +msgid "pt-plane-distance" +msgstr "vzdálenost-bodu-a-roviny" + +#: constraint.cpp:16 +msgctxt "constr-name" +msgid "pt-face-distance" +msgstr "vzdálenost-bodu-a-stěny" + +#: constraint.cpp:17 +msgctxt "constr-name" +msgid "proj-pt-pt-distance" +msgstr "proj-vzdálenost-dvou-bodů" + +#: constraint.cpp:18 +msgctxt "constr-name" +msgid "pt-in-plane" +msgstr "bod-v-rovině" + +#: constraint.cpp:19 +msgctxt "constr-name" +msgid "pt-on-line" +msgstr "bod-na-přímce" + +#: constraint.cpp:20 +msgctxt "constr-name" +msgid "pt-on-face" +msgstr "bod-na-stěně" + +#: constraint.cpp:21 +msgctxt "constr-name" +msgid "eq-length" +msgstr "shodná-délka" + +#: constraint.cpp:22 +msgctxt "constr-name" +msgid "eq-length-and-pt-ln-dist" +msgstr "shodná-délka-a-vzdálenost-bodu-a-úsečky" + +#: constraint.cpp:23 +msgctxt "constr-name" +msgid "eq-pt-line-distances" +msgstr "shodná-vzdálenost-bodu-a-úsečky" + +#: constraint.cpp:24 +msgctxt "constr-name" +msgid "length-ratio" +msgstr "poměr-délky" + +#: constraint.cpp:25 +msgctxt "constr-name" +msgid "arc-arc-length-ratio" +msgstr "poměr-délky-dvou-oblouků" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "arc-line-length-ratio" +msgstr "poměr-délky-oblouku-a-úsečky" + +#: constraint.cpp:27 +msgctxt "constr-name" +msgid "length-difference" +msgstr "rozdíl-délky" + +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "arc-arc-len-difference" +msgstr "rozdíl-délky-dvou-oblouků" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "arc-line-len-difference" +msgstr "rozdíl-délky-oblouku-a-úsečky" + +#: constraint.cpp:30 +msgctxt "constr-name" +msgid "symmetric" +msgstr "symetrie" + +#: constraint.cpp:31 +msgctxt "constr-name" +msgid "symmetric-h" +msgstr "symetrie-h" + +#: constraint.cpp:32 +msgctxt "constr-name" +msgid "symmetric-v" +msgstr "symetrie-v" + +#: constraint.cpp:33 +msgctxt "constr-name" +msgid "symmetric-line" +msgstr "symetrie-přímka" + +#: constraint.cpp:34 +msgctxt "constr-name" +msgid "at-midpoint" +msgstr "ve-středu" + +#: constraint.cpp:35 +msgctxt "constr-name" +msgid "horizontal" +msgstr "horizontála" + +#: constraint.cpp:36 +msgctxt "constr-name" +msgid "vertical" +msgstr "vertikála" + +#: constraint.cpp:37 +msgctxt "constr-name" +msgid "diameter" +msgstr "průměr" + +#: constraint.cpp:38 +msgctxt "constr-name" +msgid "pt-on-circle" +msgstr "bod-na-kruhu" + +#: constraint.cpp:39 +msgctxt "constr-name" +msgid "same-orientation" +msgstr "shodná-orientace" + +#: constraint.cpp:40 +msgctxt "constr-name" +msgid "angle" +msgstr "úhel" + +#: constraint.cpp:41 +msgctxt "constr-name" +msgid "parallel" +msgstr "rovnoběžnost" + +#: constraint.cpp:42 +msgctxt "constr-name" +msgid "arc-line-tangent" +msgstr "tečný-oblouk-a-úsečka" + +#: constraint.cpp:43 +msgctxt "constr-name" +msgid "cubic-line-tangent" +msgstr "tečná-splajna-a-úsečka" + +#: constraint.cpp:44 +msgctxt "constr-name" +msgid "curve-curve-tangent" +msgstr "tečné-dvě-křivky" + +#: constraint.cpp:45 +msgctxt "constr-name" +msgid "perpendicular" +msgstr "kolmost" + +#: constraint.cpp:46 +msgctxt "constr-name" +msgid "eq-radius" +msgstr "shodný-poloměr" + +#: constraint.cpp:47 +msgctxt "constr-name" +msgid "eq-angle" +msgstr "shodný-úhel" + +#: constraint.cpp:48 +msgctxt "constr-name" +msgid "eq-line-len-arc-len" +msgstr "shodná-délka-úsečky-a-oblouku" + +#: constraint.cpp:49 +msgctxt "constr-name" +msgid "lock-where-dragged" +msgstr "zámek-kam-přetažen" + +#: constraint.cpp:50 +msgctxt "constr-name" +msgid "comment" +msgstr "komentář" + +#: constraint.cpp:151 +msgid "" +"The point you selected does not belong to the arc. The arc and line segment " +"do not share an end point.\n" +"\n" +"Select the end point of the arc at which you want it to be tangent to the " +"line." +msgstr "" +"Vámi vybraný bod nepatří do oblouku. Oblouk a úsečka " +"nemají společný koncový bod.\n" +"\n" +"Vyberte koncový bod oblouku, ve kterém má být tečný k " +"přímce." + +#: constraint.cpp:158 +msgid "" +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be " +"tangent to the line." +msgstr "" +"Oblouk a tečná úsečka musejí mít společný koncový bod. Omez je nejdříve " +"příkazem Omezit -> Na bodě / křivce / ploše.\n" +"\n" +"Případně vyberte koncový bod oblouku, ve kterém má být " +"tečný k přímce." + +#: constraint.cpp:186 +msgid "" +"The point you selected is not an end point of the cubic spline. The spline " +"and line segment do not share an end point.\n" +"\n" +"Select the end point of the spline at which you want it to be tangent to the " +"line." +msgstr "" +"Vybraný bod není koncovým bodem splajnu. Splajn " +"a úsečka nemají společný koncový bod.\n" +"\n" +"Vyberte koncový bod splajnu, ve kterém má být tečný k " +"přímce." + +#: constraint.cpp:193 +msgid "" +"The tangent cubic spline and line segment must share an endpoint. Constrain " +"them with Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it " +"to be tangent to the line." +msgstr "" +"Tečný splajn a úsečka musejí mít společný koncový bod. Omez je nejdříve " +"příkazem Omezit -> Na bodě / křivce / ploše.\n" +"\n" +"Případně vyberte koncový bod splajnu, ve kterém má být tečný k " +"přímce." + +#: constraint.cpp:237 +msgid "" +"The points you selected are not end points of the two curves. The curves do " +"not share an end point.\n" +"\n" +"Select the end points of both curves at which you want them to be tangent to " +"each other." +msgstr "" +"Vybrané body nejsou koncovými body dvou křivek. Křivky " +"nemají společný koncový bod.\n" +"\n" +"Vyberte koncové body obou křivek, v nichž mají být vůči sobě " +"tečné" + +#: constraint.cpp:244 +msgid "" +"The curves must share an endpoint. Constrain them with Constrain -> On Point " +"before constraining tangent.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." +msgstr "" +"Křivky musejí mít společný koncový bod. Omez je příkazem Omezit -> V bodě " +"před omezením tečny.\n" +"\n" +"Případně vyberte koncové body obou křivek, v nichž mají být vůči sobě " +"tečné" + +#: constraint.cpp:303 +msgid "" +"Bad selection for distance / diameter constraint. This constraint can apply " +"to:\n" +"\n" +" * two points (distance between points)\n" +" * a line segment (length)\n" +" * two points and a line segment or normal (projected distance)\n" +" * a workplane and a point (minimum distance)\n" +" * a line segment and a point (minimum distance)\n" +" * a plane face and a point (minimum distance)\n" +" * a circle or an arc (diameter)\n" +msgstr "" +"Chybný výběr pro omezení vzdálenosti / průměru. Toto omezení lze použít " +"pro:\n" +"\n" +" * dva body (vzdálenost mezi body)\n" +" * úsečku (délka)\n" +" * dva body a úsečku nebo normálu (promítnutá vzdálenost)\n" +" * pracovní rovinu a bod (minimální vzdálenost)\n" +" * úsečku a bod (minimální vzdálenost)\n" +" * rovinnou plochu a bod (minimální vzdálenost)\n" +" * kružnici nebo oblouk (průměr)\n" + +#: constraint.cpp:366 +msgid "" +"Bad selection for on point / curve / plane constraint. This constraint can " +"apply to:\n" +"\n" +" * two or more points (points coincident)\n" +" * a point and a workplane (point in plane)\n" +" * a point and a line segment (point on line)\n" +" * a point and a circle or arc (point on curve)\n" +" * a point and one to three plane faces (point on face(s))\n" +msgstr "" +"Chybný výběr pro omezení na bod / křivku / rovinu. Toto omezení lze použít " +"pro:\n" +"\n" +" * dva a více bodů (body se shodují)\n" +" * bod a pracovní rovinu (bod v rovině)\n" +" * bod a úsečku (bod na přímce)\n" +" * bod a kružnici nebo oblouk (bod na křivce)\n" +" * bod a jednu až tři rovinné plochy (bod na ploše/plochách)\n" + +#: constraint.cpp:427 +msgid "" +"Bad selection for equal length / radius constraint. This constraint can " +"apply to:\n" +"\n" +" * two or more line segments (equal length)\n" +" * two line segments and two points (equal point-line distances)\n" +" * a line segment and two points (equal point-line distances)\n" +" * a line segment, and a point and line segment (point-line distance " +"equals length)\n" +" * two or more circles or arcs (equal radius)\n" +" * a line segment and an arc (line segment length equals arc length)\n" +msgstr "" +"Chybný výběr pro omezení stejné délky / poloměru. Toto omezení lze použít " +"pro:\n" +"\n" +" * dvě a více úseček (stejné délky)\n" +" * dvě úsečky a dva body (stejné vzdálenosti bodů od úseček)\n" +" * úsečku a dva body (stejné vzdálenosti bodů od přímky)\n" +" * úsečku a bod a úsečku (vzdálenost bodu od přímky se rovná délce)\n" +" * dvě a více kružnic nebo oblouků (stejný poloměr)\n" +" * úsečku a oblouk (délka úsečky se rovná délce oblouku)\n" + +#: constraint.cpp:480 +msgid "" +"Bad selection for length ratio constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" +msgstr "" +"Chybný výběr pro omezení poměru délky. Toto omezení lze použít pro:\n" +"\n" +" * dvě úsečky\n" +" * dva oblouky\n" +" * jeden oblouk a jednu úsečku\n" + +#: constraint.cpp:515 +msgid "" +"Bad selection for length difference constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" +msgstr "" +"Chybný výběr pro omezení rozdílu délek. Toto omezení lze použít pro:\n" +"\n" +" * dvě úsečky\n" +" * dva oblouky\n" +" * jeden oblouk a jednu úsečku\n" + +#: constraint.cpp:550 +msgid "" +"Bad selection for at midpoint constraint. This constraint can apply to:\n" +"\n" +" * a line segment and a point (point at midpoint)\n" +" * a line segment and a workplane (line's midpoint on plane)\n" +msgstr "" +"Chybný výběr pro omezení ve středovém bodě. Toto omezení lze použít pro: \n" +"\n" +" * úsečku a bod (bod ve středovém bodě)\n" +" * úsečku a pracovní rovinu (střed úsečky v rovině)\n" + +#: constraint.cpp:608 +msgid "" +"Bad selection for symmetric constraint. This constraint can apply to:\n" +"\n" +" * two points or a line segment (symmetric about workplane's coordinate " +"axis)\n" +" * line segment, and two points or a line segment (symmetric about line " +"segment)\n" +" * workplane, and two points or a line segment (symmetric about " +"workplane)\n" +msgstr "" +"Chybný výběr pro symetrické omezení. Toto omezení lze použít pro:\n" +"\n" +" * dva body nebo úsečku (symetrickou vůči souřadnicím osy pracovní " +"roviny)\n" +" * úsečku a dva body nebo úsečku (symetrická k úsečce)\n" +" * pracovní rovinu a dva body nebo úsečku (symetrická k pracovní rovině)\n" + +#: constraint.cpp:623 +msgid "" +"A workplane must be active when constraining symmetric without an explicit " +"symmetry plane." +msgstr "" +"Pracovní rovina musí být aktivní, pokud je omezena symetricky bez explicitní " +"symetrické roviny." + +#: constraint.cpp:663 +msgid "" +"Activate a workplane (with Sketch -> In Workplane) before applying a " +"horizontal or vertical constraint." +msgstr "" +"Aktivuj pracovní rovinu (pomocí Náčrt -> V pracovní rovině) před použitím " +"omezení horizontály nebo vertikály." + +#: constraint.cpp:679 +msgid "" +"Bad selection for horizontal / vertical constraint. This constraint can " +"apply to:\n" +"\n" +" * two or more points\n" +" * one or more line segments\n" +msgstr "" +"Chybný výběr pro horizontální / vertikální omezení. Toto omezení lze použít " +"pro:\n" +"\n" +" * dva a více bodů\n" +" * jednu a více úseček\n" + +#: constraint.cpp:697 +msgid "" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" +"\n" +" * two normals\n" +msgstr "" +"Chybný výběr pro omezení stejné orientace. Toto omezení lze použít pro: \n" +" \n" +" * dvě normály\n" + +#: constraint.cpp:748 +msgid "Must select an angle constraint." +msgstr "Je nutné vybrat omezení úhlu." + +#: constraint.cpp:761 +msgid "Must select a constraint with associated label." +msgstr "Je nutné vybrat omezení s přiřazeným štítkem." + +#: constraint.cpp:784 +msgid "" +"Bad selection for angle constraint. This constraint can apply to:\n" +"\n" +"Angle between:\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +"\n" +"Equal angles:\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" +msgstr "" +"Chybný výběr pro omezení úhlu. Toto omezení lze použít pro: \n" +"\n" +"Úhel mezi:\n" +" * dvěma úsečkami\n" +" * úsečkou a normálou\n" +" * dvěma normálami\n" +"\n" +"Shodné úhly:\n" +" * čtyři úsečky nebo normály (shodný úhel mezi A,B a C,D)\n" +" * tři úsečky nebo normály (shodný úhel mezi A,B a B,C)\n" + +#: constraint.cpp:872 +msgid "Curve-curve tangency must apply in workplane." +msgstr "Tečnost dvou křivek musí být použita v pracovní rovině." + +#: constraint.cpp:887 +msgid "" +"Bad selection for parallel / tangent constraint. This constraint can apply " +"to:\n" +"\n" +" * two faces\n" +" * two or more line segments (parallel)\n" +" * one or more line segments and one or more normals (parallel)\n" +" * two or more normals (parallel)\n" +" * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +" * two line segments, arcs, or beziers, that do not share an endpoint and " +"the end point(s) of the curve(s) (tangent)\n" +msgstr "" +"Chybný výběr pro rovnoběžné / tečné omezení. Toto omezení lze použít pro:\n" +" \n" +" * dvě plochy\n" +" * dvě a více úseček (rovnoběžné)\n" +" * jednu a více úseček a jednu a více normál (rovnoběžné)\n" +" * dvě a více normál (rovnoběžné)\n" +" * dvě úsečky, oblouky nebo beziéry, které mají společný koncový bod " +"(tečné)\n" +" * dvě úsečky, oblouky nebo beziéry, které nemají společný koncový bod, a " +"koncový(é) bod(y) křivky(ek) (tečné)\n" + +#: constraint.cpp:914 +msgid "" +"Bad selection for perpendicular constraint. This constraint can apply to:\n" +"\n" +" * two faces\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"Chybný výběr pro omezení kolmosti. Toto omezení lze použít pro: \n" +"\n" +" * dvě plochy\n" +" * dvě úsečky\n" +" * úsečku a normálu\n" +" * dvě normály\n" + +#: constraint.cpp:931 +msgid "" +"Bad selection for lock point where dragged constraint. This constraint can " +"apply to:\n" +"\n" +" * a point\n" +msgstr "" +"Chybný výběr pro omezení uzamčení v místě přetažení. Toto omezení lze použít " +"pro: \n" +"\n" +" * bod\n" + +#: constraint.cpp:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "NOVÝ KOMENTÁŘ -- UPRAVIT DVOJKLIKEM" + +#: constraint.cpp:952 +msgid "click center of comment text" +msgstr "klikni na střed textu komentáře" + +#: export.cpp:19 +msgid "" +"No solid model present; draw one with extrudes and revolves, or use Export " +"2d View to export bare lines and curves." +msgstr "" +"Není přítomen žádný model tělesa; vytvoř model pomocí extruzí a rotací nebo " +"použij Exportovat 2D pohled... pro export prostých čar a křivek." + +#: export.cpp:61 +msgid "" +"Bad selection for export section. Please select:\n" +"\n" +" * nothing, with an active workplane (workplane is section plane)\n" +" * a face (section plane through face)\n" +" * a point and two line segments (plane through point and parallel to " +"lines)\n" +msgstr "" +"Chybný výběr části pro export. Vyber prosím:\n" +"\n" +" * nic, s aktivní pracovní rovinou (pracovní rovina je rovina řezu)\n" +" * plochu (rovina řezu procházející plochou)\n" +" * bod a dvě úsečky (rovina procházející bodem a rovnoběžná s přímkami)\n" + +#: export.cpp:818 +msgid "Active group mesh is empty; nothing to export." +msgstr "Síť aktivní skupiny je prázdná; není co exportovat." + +#: exportvector.cpp:336 +msgid "freehand lines were replaced with continuous lines" +msgstr "volné úsečky byly nahrazeny souvislými" + +#: exportvector.cpp:338 +msgid "zigzag lines were replaced with continuous lines" +msgstr "klikaté úsečky byly nahrazeny souvislými" + +#: exportvector.cpp:592 +msgid "" +"Some aspects of the drawing have no DXF equivalent and were not exported:\n" +msgstr "" +"Některé prvky výkresu nemají ekvivalent ve formátu DXF a nebyly " +"exportovány:\n" + +#: exportvector.cpp:838 +msgid "" +"PDF page size exceeds 200 by 200 inches; many viewers may reject this file." +msgstr "" +"Velikost stránky PDF přesahuje 200 x 200 palců; mnoho prohlížečů může tento " +"soubor odmítnout." + +#: file.cpp:44 group.cpp:91 +msgctxt "group-name" +msgid "sketch-in-plane" +msgstr "náčrt-v-rovině" + +#: file.cpp:62 +msgctxt "group-name" +msgid "#references" +msgstr "#odkazy" + +#: file.cpp:555 +msgid "The file is empty. It may be corrupt." +msgstr "Soubor je prázdný. Může být poškozený." + +#: file.cpp:560 +msgid "" +"Unrecognized data in file. This file may be corrupt, or from a newer version " +"of the program." +msgstr "" +"Nerozpoznaná data v souboru. Tento soubor může být poškozený nebo pochází z " +"novější verze programu." + +#: file.cpp:876 +msgctxt "title" +msgid "Missing File" +msgstr "Chybějící soubor" + +#: file.cpp:877 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "Odkazovaný soubor “%s” není k dispozici." + +#: file.cpp:879 +msgctxt "dialog" +msgid "" +"Do you want to locate it manually?\n" +"\n" +"If you decline, any geometry that depends on the missing file will be " +"permanently removed." +msgstr "" +"Chceš jej vyhledat ručně?\n" +"\n" +"Pokud odmítneš, všechny geometrie, které závisí na chybějícím souboru, budou " +"trvale odstraněny." + +#: file.cpp:882 +msgctxt "button" +msgid "&Yes" +msgstr "&Ano" + +#: file.cpp:884 +msgctxt "button" +msgid "&No" +msgstr "&Ne" + +#: file.cpp:886 solvespace.cpp:652 +msgctxt "button" +msgid "&Cancel" +msgstr "&Zrušit" + +#: graphicswin.cpp:41 +msgid "&File" +msgstr "&Soubor" + +#: graphicswin.cpp:42 +msgid "&New" +msgstr "&Nový" + +#: graphicswin.cpp:43 +msgid "&Open..." +msgstr "&Otevřít..." + +#: graphicswin.cpp:44 +msgid "Open &Recent" +msgstr "Otevřít &nedávný" + +#: graphicswin.cpp:45 +msgid "&Save" +msgstr "&Uložit" + +#: graphicswin.cpp:46 +msgid "Save &As..." +msgstr "Uložit &jako..." + +#: graphicswin.cpp:48 +msgid "Export &Image..." +msgstr "Exportovat &Obrázek..." + +#: graphicswin.cpp:49 +msgid "Export 2d &View..." +msgstr "Exportovat 2D &pohled..." + +#: graphicswin.cpp:50 +msgid "Export 2d &Section..." +msgstr "Exportovat 2D čá&st..." + +#: graphicswin.cpp:51 +msgid "Export 3d &Wireframe..." +msgstr "Exportovat 3D &drátěný model..." + +#: graphicswin.cpp:52 +msgid "Export Triangle &Mesh..." +msgstr "Exportovat &trojúhelníkovou síť (mesh)..." + +#: graphicswin.cpp:53 +msgid "Export &Surfaces..." +msgstr "Exportovat &povrchy..." + +#: graphicswin.cpp:54 +msgid "Im&port..." +msgstr "&Importovat..." + +#: graphicswin.cpp:57 +msgid "E&xit" +msgstr "U&končit" + +#: graphicswin.cpp:60 +msgid "&Edit" +msgstr "Up&ravit" + +#: graphicswin.cpp:61 +msgid "&Undo" +msgstr "&Zpět" + +#: graphicswin.cpp:62 +msgid "&Redo" +msgstr "Z&novu" + +#: graphicswin.cpp:63 +msgid "Re&generate All" +msgstr "Znovu vše &generovat" + +#: graphicswin.cpp:65 +msgid "Snap Selection to &Grid" +msgstr "Přichytit výběr k &mřížce" + +#: graphicswin.cpp:66 +msgid "Rotate Imported &90°" +msgstr "Otočit importované o &90°" + +#: graphicswin.cpp:68 +msgid "Cu&t" +msgstr "Vyjmou&t" + +#: graphicswin.cpp:69 +msgid "&Copy" +msgstr "&Kopírovat" + +#: graphicswin.cpp:70 +msgid "&Paste" +msgstr "V&ložit" + +#: graphicswin.cpp:71 +msgid "Paste &Transformed..." +msgstr "Vložit &transformované..." + +#: graphicswin.cpp:72 +msgid "&Delete" +msgstr "O&dstranit" + +#: graphicswin.cpp:74 +msgid "Select &Edge Chain" +msgstr "Vybrat ř&etězec hran" + +#: graphicswin.cpp:75 +msgid "Select &All" +msgstr "Vybr&at vše" + +#: graphicswin.cpp:76 +msgid "&Unselect All" +msgstr "Zr&ušit výběr všeho" + +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "Sty&ly čar..." + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "&Projekční pohled..." + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "Nastave&ní..." + +#: graphicswin.cpp:84 +msgid "&View" +msgstr "&Zobrazení" + +#: graphicswin.cpp:85 +msgid "Zoom &In" +msgstr "Přiblíž&it" + +#: graphicswin.cpp:86 +msgid "Zoom &Out" +msgstr "&Oddálit" + +#: graphicswin.cpp:87 +msgid "Zoom To &Fit" +msgstr "Přiblížit na &míru" + +#: graphicswin.cpp:89 +msgid "Align View to &Workplane" +msgstr "Zarovnat pohled na pracovní rovinu" + +#: graphicswin.cpp:90 +msgid "Nearest &Ortho View" +msgstr "Nejbližší &ortogonální pohled" + +#: graphicswin.cpp:91 +msgid "Nearest &Isometric View" +msgstr "Nejbližší &izometrický pohled" + +#: graphicswin.cpp:92 +msgid "&Center View At Point" +msgstr "&Pohled na střed v bodě" + +#: graphicswin.cpp:94 +msgid "Show Snap &Grid" +msgstr "Zobrazit mřížku přichy&cení" + +#: graphicswin.cpp:95 +msgid "Darken Inactive Solids" +msgstr "Ztmavit neaktivní tělesa" + +#: graphicswin.cpp:96 +msgid "Use &Perspective Projection" +msgstr "Použít &perspektivní projekci" + +#: graphicswin.cpp:97 +msgid "Show E&xploded View" +msgstr "Zobrazit &rozbalený pohled" + +#: graphicswin.cpp:98 +msgid "Dimension &Units" +msgstr "&Jednotky rozměru" + +#: graphicswin.cpp:99 +msgid "Dimensions in &Millimeters" +msgstr "Rozměry v &milimetrech" + +#: graphicswin.cpp:100 +msgid "Dimensions in M&eters" +msgstr "Rozměry v m&etrech" + +#: graphicswin.cpp:101 +msgid "Dimensions in &Inches" +msgstr "Rozměry v &palcích" + +#: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "Rozměry ve &stopách a palcích" + +#: graphicswin.cpp:104 +msgid "Show &Toolbar" +msgstr "Zobrazit panel &nástrojů" + +#: graphicswin.cpp:105 +msgid "Show Property Bro&wser" +msgstr "Zobrazit ¶metry" + +#: graphicswin.cpp:107 +msgid "&Full Screen" +msgstr "Na &celou obrazovku" + +#: graphicswin.cpp:109 +msgid "&New Group" +msgstr "&Nová skupina" + +#: graphicswin.cpp:110 +msgid "Sketch In &3d" +msgstr "Náčrt ve &3D" + +#: graphicswin.cpp:111 +msgid "Sketch In New &Workplane" +msgstr "Náčrt v nové pracovní &rovině" + +#: graphicswin.cpp:113 +msgid "Step &Translating" +msgstr "Krokový &posun" + +#: graphicswin.cpp:114 +msgid "Step &Rotating" +msgstr "Krokové &otočení" + +#: graphicswin.cpp:116 +msgid "E&xtrude" +msgstr "Extruze (e&xtrude)" + +#: graphicswin.cpp:117 +msgid "&Helix" +msgstr "Šroubovice (&helix)" + +#: graphicswin.cpp:118 +msgid "&Lathe" +msgstr "Plná rotace (&lathe)" + +#: graphicswin.cpp:119 +msgid "Re&volve" +msgstr "Volná rotace (re&volve)" + +#: graphicswin.cpp:121 +msgid "Link / Assemble..." +msgstr "Odkaz / Sestavení..." + +#: graphicswin.cpp:122 +msgid "Link Recent" +msgstr "Odkaz na nedávný" + +#: graphicswin.cpp:124 +msgid "&Sketch" +msgstr "&Náčrt" + +#: graphicswin.cpp:125 +msgid "In &Workplane" +msgstr "V &pracovní rovině" + +#: graphicswin.cpp:126 +msgid "Anywhere In &3d" +msgstr "Kdekoliv ve &3D" + +#: graphicswin.cpp:128 +msgid "Datum &Point" +msgstr "Vztažný &Bod" + +#: graphicswin.cpp:129 +msgid "Wor&kplane" +msgstr "&Pracovní rovina" + +#: graphicswin.cpp:131 +msgid "Line &Segment" +msgstr "Úsečka" + +#: graphicswin.cpp:132 +msgid "C&onstruction Line Segment" +msgstr "Konstrukční úsečka" + +#: graphicswin.cpp:133 +msgid "&Rectangle" +msgstr "&Obdélník" + +#: graphicswin.cpp:134 +msgid "&Circle" +msgstr "&Kružnice" + +#: graphicswin.cpp:135 +msgid "&Arc of a Circle" +msgstr "&Oblouk kružnice" + +#: graphicswin.cpp:136 +msgid "&Bezier Cubic Spline" +msgstr "&Bézierův kubický splajn" + +#: graphicswin.cpp:138 +msgid "&Text in TrueType Font" +msgstr "&Text v písmu TrueType" + +#: graphicswin.cpp:139 +msgid "I&mage" +msgstr "&Obrázek" + +#: graphicswin.cpp:141 +msgid "To&ggle Construction" +msgstr "Přepnout &konstrukci" + +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" +msgstr "Tečný &oblouk v bodě" + +#: graphicswin.cpp:143 +msgid "Split Curves at &Intersection" +msgstr "Rozdělit kř&ivky v průsečíku" + +#: graphicswin.cpp:145 +msgid "&Constrain" +msgstr "Ome&zení" + +#: graphicswin.cpp:146 +msgid "&Distance / Diameter" +msgstr "&Vzdálenost / průměr" + +#: graphicswin.cpp:147 +msgid "Re&ference Dimension" +msgstr "Re&ferenční rozměr" + +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "Úhe&l / shodný úhel" + +#: graphicswin.cpp:149 +msgid "Reference An&gle" +msgstr "Refe&renční úhel" + +#: graphicswin.cpp:150 +msgid "Other S&upplementary Angle" +msgstr "Další doplňkový úh&el" + +#: graphicswin.cpp:151 +msgid "Toggle R&eference Dim" +msgstr "Přepnout r&eferenční rozměr" + +#: graphicswin.cpp:153 +msgid "&Horizontal" +msgstr "&Horizontála" + +#: graphicswin.cpp:154 +msgid "&Vertical" +msgstr "&Vertikála" + +#: graphicswin.cpp:156 +msgid "&On Point / Curve / Plane" +msgstr "&Na bodě / křivce / ploše" + +#: graphicswin.cpp:157 +msgid "E&qual Length / Radius" +msgstr "Shodná dél&ka / poloměr" + +#: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "Poměr délky / oblo&uku" + +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Rozdíl &délky / oblouku" + +#: graphicswin.cpp:160 +msgid "At &Midpoint" +msgstr "Ve středové&m bodě" + +#: graphicswin.cpp:161 +msgid "S&ymmetric" +msgstr "S&ymetrie" + +#: graphicswin.cpp:162 +msgid "Para&llel / Tangent" +msgstr "Rovno&běžnost / tečna" + +#: graphicswin.cpp:163 +msgid "&Perpendicular" +msgstr "&Kolmost" + +#: graphicswin.cpp:164 +msgid "Same Orient&ation" +msgstr "Shodná orient&ace" + +#: graphicswin.cpp:165 +msgid "Lock Point Where &Dragged" +msgstr "Uzamčení v místě pře&tažení" + +#: graphicswin.cpp:167 +msgid "Comment" +msgstr "Komentář" + +#: graphicswin.cpp:169 +msgid "&Analyze" +msgstr "&Analyzovat" + +#: graphicswin.cpp:170 +msgid "Measure &Volume" +msgstr "Měření &objemu" + +#: graphicswin.cpp:171 +msgid "Measure A&rea" +msgstr "Měření &plochy" + +#: graphicswin.cpp:172 +msgid "Measure &Perimeter" +msgstr "Měření ob&vodu" + +#: graphicswin.cpp:173 +msgid "Show &Interfering Parts" +msgstr "Zobrazit kol&idující části" + +#: graphicswin.cpp:174 +msgid "Show &Naked Edges" +msgstr "Zobrazit ob&nažené hrany" + +#: graphicswin.cpp:175 +msgid "Show &Center of Mass" +msgstr "Zobrazit &těžiště" + +#: graphicswin.cpp:177 +msgid "Show &Underconstrained Points" +msgstr "Zobrazit &nedostatečně omezené body" + +#: graphicswin.cpp:179 +msgid "&Trace Point" +msgstr "&Trasovat bod" + +#: graphicswin.cpp:180 +msgid "&Stop Tracing..." +msgstr "&Zastavit trasování..." + +#: graphicswin.cpp:181 +msgid "Step &Dimension..." +msgstr "&Rozměr kroku..." + +#: graphicswin.cpp:183 +msgid "&Help" +msgstr "&Nápověda" + +#: graphicswin.cpp:184 +msgid "&Language" +msgstr "&Jazyk" + +#: graphicswin.cpp:185 +msgid "&Website / Manual" +msgstr "&Web / Manuál" + +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "Revize na &GitHubu" + +#: graphicswin.cpp:188 +msgid "&About" +msgstr "O &aplikaci" + +#: graphicswin.cpp:362 +msgid "(no recent files)" +msgstr "(žádné nedávné soubory)" + +#: graphicswin.cpp:370 +#, c-format +msgid "File '%s' does not exist." +msgstr "Soubor '%s' neexistuje." + +#: graphicswin.cpp:779 +msgid "No workplane is active, so the grid will not appear." +msgstr "Žádná pracovní rovina není aktivní, proto nebude mřížka zobrazena." + +#: graphicswin.cpp:794 +msgid "" +"The perspective factor is set to zero, so the view will always be a parallel " +"projection.\n" +"\n" +"For a perspective projection, modify the perspective factor in the " +"configuration screen. A value around 0.3 is typical." +msgstr "" +"Faktor perspektivy je nastaven na nulu, proto bude pohled vždy rovnoběžnou " +"projekcí.\n" +"\n" +"Pro perspektivní projekci uprav faktor perspektivy v konfigurační obrazovce. " +"Typická hodnota je kolem 0,3." + +#: graphicswin.cpp:884 +msgid "" +"Select a point; this point will become the center of the view on screen." +msgstr "Vyber bod; tento bod se stane středem pohledu na obrazovce." + +#: graphicswin.cpp:1193 +msgid "No additional entities share endpoints with the selected entities." +msgstr "Žádné další entity nesdílejí koncové body s vybranými entitami." + +#: graphicswin.cpp:1211 +msgid "" +"To use this command, select a point or other entity from an linked part, or " +"make a link group the active group." +msgstr "" +"Chceš-li použít tento příkaz, vyber bod nebo jinou entitu z propojené části " +"nebo vyber skupinu propojení jako aktivní skupinu." + +#: graphicswin.cpp:1234 +msgid "" +"No workplane is active. Activate a workplane (with Sketch -> In Workplane) " +"to define the plane for the snap grid." +msgstr "" +"Žádná pracovní rovina není aktivní. Aktivuj pracovní rovinu (pomocí Náčrt -> " +"V pracovní rovině) pro nastavení roviny přichytávání na mřížku." + +#: graphicswin.cpp:1241 +msgid "" +"Can't snap these items to grid; select points, text comments, or constraints " +"with a label. To snap a line, select its endpoints." +msgstr "" +"Tyto položky nelze přichytit k mřížce; vyber body, textové komentáře nebo " +"omezení se štítkem. Chceš-li přichytit úsečku, vyber její koncové body." + +#: graphicswin.cpp:1326 +msgid "No workplane selected. Activating default workplane for this group." +msgstr "" +"Není vybrána žádná pracovní rovina. Aktivuji výchozí pracovní rovinu pro " +"tuto skupinu." + +#: graphicswin.cpp:1329 +msgid "" +"No workplane is selected, and the active group does not have a default " +"workplane. Try selecting a workplane, or activating a sketch-in-new-" +"workplane group." +msgstr "" +"Není vybrána žádná pracovní rovina a aktivní skupina nemá výchozí pracovní " +"rovinu přiřazenu. Zkus vybrat pracovní rovinu nebo aktivovat skupinu náčrt-v-" +"rovině." + +#: graphicswin.cpp:1350 +msgid "" +"Bad selection for tangent arc at point. Select a single point, or select " +"nothing to set up arc parameters." +msgstr "" +"Chybný výběr tečného oblouku v bodě. Vyber jeden bod nebo výběr zruš pro " +"nastavení parametrů oblouku." + +#: graphicswin.cpp:1361 +msgid "click point on arc (draws anti-clockwise)" +msgstr "klikni na bod oblouku (kresleno proti směru hodinových ručiček)" + +#: graphicswin.cpp:1362 +msgid "click to place datum point" +msgstr "klikni pro umístění vztažného bodu" + +#: graphicswin.cpp:1363 +msgid "click first point of line segment" +msgstr "klikni na první bod úsečky" + +#: graphicswin.cpp:1365 +msgid "click first point of construction line segment" +msgstr "klikni na první bod konstrukční úsečky" + +#: graphicswin.cpp:1366 +msgid "click first point of cubic segment" +msgstr "klikni na první bod segmentu splajnu" + +#: graphicswin.cpp:1367 +msgid "click center of circle" +msgstr "klikni na střed kružnice" + +#: graphicswin.cpp:1368 +msgid "click origin of workplane" +msgstr "klikni na počátek pracovní roviny" + +#: graphicswin.cpp:1369 +msgid "click one corner of rectangle" +msgstr "klikni na jeden roh obdélníku" + +#: graphicswin.cpp:1370 +msgid "click top left of text" +msgstr "klikni na levý horní roh textu" + +#: graphicswin.cpp:1376 +msgid "click top left of image" +msgstr "klikni na levý horní roh obrázku" + +#: graphicswin.cpp:1402 +msgid "" +"No entities are selected. Select entities before trying to toggle their " +"construction state." +msgstr "" +"Nejsou vybrány žádné entity. Před přepnutím stavu konstrukce nějaké entity " +"vyber." + +#: group.cpp:86 +msgctxt "group-name" +msgid "sketch-in-3d" +msgstr "náčrt-ve-3D" + +#: group.cpp:154 +msgid "" +"Bad selection for new sketch in workplane. This group can be created with:\n" +"\n" +" * a point (through the point, orthogonal to coordinate axes)\n" +" * a point and two line segments (through the point, parallel to the " +"lines)\n" +" * a point and a normal (through the point, orthogonal to the normal)\n" +" * a workplane (copy of the workplane)\n" +msgstr "" +"Chybný výběr nového náčrtu v pracovním plánu. Tuto skupinu lze vytvořit " +"pomocí:\n" +"\n" +" * bodu (přes bod, kolmo na souřadnicové osy)\n" +" * bodu a dvou úseček (procházející bodem, rovnoběžná s přímkami)\n" +" * bodu a normály (procházející bodem, kolmá na normálu)\n" +" * pracovní roviny (kopie pracovní roviny)\n" + +#: group.cpp:170 +msgid "" +"Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " +"will be extruded normal to the workplane." +msgstr "" +"Před extruzí aktivuj pracovní rovinu (Náčrt -> V pracovní rovině). Náčrt " +"bude extrudován ve směru normály k pracovní rovině." + +#: group.cpp:179 +msgctxt "group-name" +msgid "extrude" +msgstr "extruze" + +#: group.cpp:184 +msgid "Lathe operation can only be applied to planar sketches." +msgstr "Operace plné rotace lze použít pouze na rovinné náčrty.." + +#: group.cpp:195 +msgid "" +"Bad selection for new lathe group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"Chybný výběr pro novou skupinu plné rotace. Tuto skupinu lze vytvořit " +"pomocí:\n" +"\n" +" * bodu a úsečky nebo normály (rotací kolem osy rovnoběžné k přímce / " +"normále, procházející bodem)\n" +" * úsečky (rotací kolem úsečky)\n" + +#: group.cpp:205 +msgctxt "group-name" +msgid "lathe" +msgstr "plná-rotace" + +#: group.cpp:210 +msgid "Revolve operation can only be applied to planar sketches." +msgstr "Operaci volné rotace lze použít pouze na rovinné náčrty." + +#: group.cpp:221 +msgid "" +"Bad selection for new revolve group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"Chybný výběr pro novou skupinu volné rotace. Tuto skupinu lze vytvořit " +"pomocí:\n" +"\n" +" * bodu a úsečky nebo normály (rotace kolem osy rovnoběžné s přímkou / " +"normálou, procházející bodem)\n" +" * úsečky (rotace kolem úsečky)\n" + +#: group.cpp:233 +msgctxt "group-name" +msgid "revolve" +msgstr "volná-rotace" + +#: group.cpp:238 +msgid "Helix operation can only be applied to planar sketches." +msgstr "Operaci šroubovice lze použít pouze na rovinné náčrty." + +#: group.cpp:249 +msgid "" +"Bad selection for new helix group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"Chybný výběr pro novou skupinu šroubovice. Tuto skupinu lze vytvořit " +"pomocí:\n" +"\n" +" * bodu a úsečky nebo normály (rotací kolem osy rovnoběžné k přímce / " +"normále, procházející bodem)\n" +" * úsečky (rotací kolem úsečky)\n" + +#: group.cpp:261 +msgctxt "group-name" +msgid "helix" +msgstr "šroubovice" + +#: group.cpp:274 +msgid "" +"Bad selection for new rotation. This group can be created with:\n" +"\n" +" * a point, while locked in workplane (rotate in plane, about that " +"point)\n" +" * a point and a line or a normal (rotate about an axis through the " +"point, and parallel to line / normal)\n" +msgstr "" +"Chybný výběr pro nové krokové otočení. Tuto skupinu lze vytvořit pomocí:\n" +"\n" +" * bodu, přičemž je uzamčen v pracovní rovině (otočení v rovině, kolem " +"tohoto bodu)\n" +" * bodu a úsečky nebo normály (otočení kolem osy procházející bodem a " +"rovnoběžně s přímkou / normálou)\n" + +#: group.cpp:287 +msgctxt "group-name" +msgid "rotate" +msgstr "otočení" + +#: group.cpp:298 +msgctxt "group-name" +msgid "translate" +msgstr "posun" + +#: group.cpp:422 +msgid "(unnamed)" +msgstr "(nepojmenované)" + +#: groupmesh.cpp:710 +msgid "not closed contour, or not all same style!" +msgstr "Obrys není uzavřený nebo není celý v jednotném stylu!" + +#: groupmesh.cpp:723 +msgid "points not all coplanar!" +msgstr "Ne všechny body jsou v rovině!" + +#: groupmesh.cpp:725 +msgid "contour is self-intersecting!" +msgstr "Obrys se sám protíná!" + +#: groupmesh.cpp:727 +msgid "zero-length edge!" +msgstr "Nulová délka hrany!" + +#: importmesh.cpp:136 +msgid "Text-formated STL files are not currently supported" +msgstr "Textové soubory STL nejsou v této chvíli podporovány." + +#: modify.cpp:252 +msgid "Must be sketching in workplane to create tangent arc." +msgstr "Pro vytvoření tečného oblouku je nutné kreslit v pracovní rovině." + +#: modify.cpp:299 +msgid "" +"To create a tangent arc, select a point where two non-construction lines or " +"circles in this group and workplane join." +msgstr "" +"Chceš-li vytvořit tečný oblouk, vyber bod, kde se s pracovní rovinou stýkají " +"dvě nekonstrukční úsečky nebo kružnice v této skupině." + +#: modify.cpp:386 +msgid "" +"Couldn't round this corner. Try a smaller radius, or try creating the " +"desired geometry by hand with tangency constraints." +msgstr "" +"Tento roh nelze zaoblit. Zkus menší poloměr nebo zkus vytvořit požadovanou " +"geometrii ručně pomocí tečných omezení." + +#: modify.cpp:595 +msgid "Couldn't split this entity; lines, circles, or cubics only." +msgstr "" +"Tuto entitu se nepodařilo rozdělit; pouze úsečky, kružnice nebo splajny." + +#: modify.cpp:622 +msgid "Must be sketching in workplane to split." +msgstr "Rozdělení lze provést pouze při náčrtu v pracovní rovině." + +#: modify.cpp:629 +msgid "" +"Select two entities that intersect each other (e.g. two lines/circles/arcs " +"or a line/circle/arc and a point)." +msgstr "" +"Vyber dvě entity, které se vzájemně protínají (např. dvě čáry / kružnice / " +"oblouky nebo úsečka / kružnice / oblouk a bod)." + +#: modify.cpp:734 +msgid "Can't split; no intersection found." +msgstr "Nelze rozdělit, nebyla nalezena žádná průsečnice." + +#: mouse.cpp:558 +msgid "Assign to Style" +msgstr "Přiřadit ke stylu" + +#: mouse.cpp:574 +msgid "No Style" +msgstr "Žádný styl" + +#: mouse.cpp:577 +msgid "Newly Created Custom Style..." +msgstr "Nově vytvořený vlastní styl..." + +#: mouse.cpp:584 +msgid "Group Info" +msgstr "Info o skupině" + +#: mouse.cpp:604 +msgid "Style Info" +msgstr "Info o stylu" + +#: mouse.cpp:624 +msgid "Select Edge Chain" +msgstr "Vybrat řetězec hran" + +#: mouse.cpp:630 +msgid "Toggle Reference Dimension" +msgstr "Přepnout referenční rozměr" + +#: mouse.cpp:636 +msgid "Other Supplementary Angle" +msgstr "Další doplňkový úhel" + +#: mouse.cpp:641 +msgid "Snap to Grid" +msgstr "Přichytit k mřížce" + +#: mouse.cpp:650 +msgid "Remove Spline Point" +msgstr "Odebrat bod splajnu" + +#: mouse.cpp:685 +msgid "Add Spline Point" +msgstr "Přidat bod splajnu" + +#: mouse.cpp:689 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "Nelze přidat bod splajnu: bylo dosaženo maximálního počtu bodů." + +#: mouse.cpp:714 +msgid "Toggle Construction" +msgstr "Přepnout konstrukci" + +#: mouse.cpp:730 +msgid "Delete Point-Coincident Constraint" +msgstr "Odstranit omezení kolidujícího bodu" + +#: mouse.cpp:748 +msgid "Cut" +msgstr "Vyjmout" + +#: mouse.cpp:750 +msgid "Copy" +msgstr "Kopírovat" + +#: mouse.cpp:754 +msgid "Select All" +msgstr "Vybrat vše" + +#: mouse.cpp:759 +msgid "Paste" +msgstr "Vložit" + +#: mouse.cpp:761 +msgid "Paste Transformed..." +msgstr "Vložit transformované..." + +#: mouse.cpp:766 +msgid "Delete" +msgstr "Odstranit" + +#: mouse.cpp:769 +msgid "Unselect All" +msgstr "Zrušit výběr všeho" + +#: mouse.cpp:776 +msgid "Unselect Hovered" +msgstr "Zrušit výběr při najetí" + +#: mouse.cpp:785 +msgid "Zoom to Fit" +msgstr "Přiblížit na míru" + +#: mouse.cpp:987 mouse.cpp:1276 +msgid "click next point of line, or press Esc" +msgstr "klikni na další bod úsečky nebo stiskni klávesu Esc" + +#: mouse.cpp:993 +msgid "" +"Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Nelze nakreslit obdélník ve 3D; nejprve aktivuj pracovní rovinu pomocí Náčrt " +"-> V pracovní rovině." + +#: mouse.cpp:1027 +msgid "click to place other corner of rectangle" +msgstr "kliknutím umísti další roh obdélníku" + +#: mouse.cpp:1048 +msgid "click to set radius" +msgstr "kliknutím nastav poloměr" + +#: mouse.cpp:1053 +msgid "" +"Can't draw arc in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Nelze nakreslit oblouk ve 3D; nejprve aktivuj pracovní rovinu pomocí Náčrt -" +"> V pracovní rovině." + +#: mouse.cpp:1072 +msgid "click to place point" +msgstr "kliknutím umísti bod" + +#: mouse.cpp:1088 +msgid "click next point of cubic, or press Esc" +msgstr "klikni na další bod splajnu nebo stiskni Esc" + +#: mouse.cpp:1093 +msgid "" +"Sketching in a workplane already; sketch in 3d before creating new workplane." +msgstr "" +"Kreslení v pracovní rovině již probíhá; před vytvořením nové pracovní roviny " +"kresli ve 3D." + +#: mouse.cpp:1109 +msgid "" +"Can't draw text in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Nelze kreslit text ve 3D; nejprve aktivuj pracovní rovinu pomocí Náčrt -> V " +"pracovní rovině." + +#: mouse.cpp:1126 +msgid "click to place bottom right of text" +msgstr "klikni pro umístění pravého dolního rohu textu" + +#: mouse.cpp:1132 +msgid "" +"Can't draw image in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Nelze nakreslit obrázek ve 3d; nejprve aktivuj pracovní rovinu pomocí Náčrt -" +"> V pracovní rovině." + +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "SolveSpace modely" + +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "VŠE" + +#: platform/gui.cpp:91 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "IDF deska plošných spojů" + +#: platform/gui.cpp:92 +msgctxt "file-type" +msgid "STL triangle mesh" +msgstr "STL trojúhelníková síť" + +#: platform/gui.cpp:96 +msgctxt "file-type" +msgid "PNG image" +msgstr "PNG obrázek" + +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "STL mesh" +msgstr "STL síť (mesh)" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Wavefront OBJ síť (mesh)" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "Three.js-compatibilní síť (mesh), s prohlížečem" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "Three.js-compatibilní síť (mesh), bez prohlížeče" + +#: platform/gui.cpp:104 +msgctxt "file-type" +msgid "VRML text file" +msgstr "VRML textový soubor" + +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "STEP file" +msgstr "STEP soubor" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "PDF file" +msgstr "PDF soubor" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "Zapouzdřený PostScript" + +#: platform/gui.cpp:114 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "SVG soubor" + +#: platform/gui.cpp:116 platform/gui.cpp:123 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "DXF soubor (AutoCAD 2007)" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "HPGL file" +msgstr "HPGL soubor" + +#: platform/gui.cpp:118 +msgctxt "file-type" +msgid "G Code" +msgstr "G kód (G Code)" + +#: platform/gui.cpp:127 +msgctxt "file-type" +msgid "AutoCAD DXF and DWG files" +msgstr "AutoCAD DXF a DWG soubory" + +#: platform/gui.cpp:131 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "CSV soubor" + +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 +msgid "untitled" +msgstr "nepojmenovaný" + +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 +msgctxt "title" +msgid "Save File" +msgstr "Uložit soubor" + +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 +msgctxt "title" +msgid "Open File" +msgstr "Otevřít soubor" + +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 +msgctxt "button" +msgid "_Cancel" +msgstr "_Zrušit" + +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 +msgctxt "button" +msgid "_Save" +msgstr "_Uložit" + +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 +msgctxt "button" +msgid "_Open" +msgstr "_Otevřít" + +#: solvespace.cpp:175 +msgctxt "title" +msgid "Autosave Available" +msgstr "Dostupné automatické ukládání" + +#: solvespace.cpp:176 +msgctxt "dialog" +msgid "An autosave file is available for this sketch." +msgstr "Pro tento náčrt je k dispozici automatické ukládání souboru." + +#: solvespace.cpp:177 +msgctxt "dialog" +msgid "Do you want to load the autosave file instead?" +msgstr "Chceš místo toho načíst automaticky uložený soubor?" + +#: solvespace.cpp:178 +msgctxt "button" +msgid "&Load autosave" +msgstr "&Načíst automaticky uložený" + +#: solvespace.cpp:180 +msgctxt "button" +msgid "Do&n't Load" +msgstr "&Nenačítat" + +#: solvespace.cpp:640 +msgctxt "title" +msgid "Modified File" +msgstr "Upravený soubor" + +#: solvespace.cpp:642 +#, c-format +msgctxt "dialog" +msgid "Do you want to save the changes you made to the sketch “%s”?" +msgstr "Chceš uložit změny, které jsi provedl v náčrtu “%s”?" + +#: solvespace.cpp:645 +msgctxt "dialog" +msgid "Do you want to save the changes you made to the new sketch?" +msgstr "Chceš provedené změny uložit do nového náčrtu?" + +#: solvespace.cpp:648 +msgctxt "dialog" +msgid "Your changes will be lost if you don't save them." +msgstr "Pokud změny neuložíš, budou ztraceny." + +#: solvespace.cpp:649 +msgctxt "button" +msgid "&Save" +msgstr "&Uložit" + +#: solvespace.cpp:651 +msgctxt "button" +msgid "Do&n't Save" +msgstr "&Neukládat" + +#: solvespace.cpp:672 +msgctxt "title" +msgid "(new sketch)" +msgstr "(nový náčrt)" + +#: solvespace.cpp:683 +msgctxt "title" +msgid "Property Browser" +msgstr "Parametry" + +#: solvespace.cpp:746 +msgid "" +"Constraints are currently shown, and will be exported in the toolpath. This " +"is probably not what you want; hide them by clicking the link at the top of " +"the text window." +msgstr "" +"Omezení jsou aktuálně zobrazena a budou exportována do cesty nástroje. To " +"pravděpodobně není to, co chceš; skryj je kliknutím na odkaz v horní části " +"textového okna." + +#: solvespace.cpp:834 +#, c-format +msgid "" +"Can't identify file type from file extension of filename '%s'; try .dxf or ." +"dwg." +msgstr "" +"Nelze určit typ souboru podle přípony názvu souboru '%s'; zkus .dxf nebo ." +"dwg." + +#: solvespace.cpp:886 +msgid "Constraint must have a label, and must not be a reference dimension." +msgstr "Omezení musí mít popisek a nesmí být referenčním rozměrem." + +#: solvespace.cpp:890 +msgid "Bad selection for step dimension; select a constraint." +msgstr "Chybný výběr rozměru kroku; vyber omezení." + +#: solvespace.cpp:914 +msgid "The assembly does not interfere, good." +msgstr "V pořádku, sestava se nepřekrývá." + +#: solvespace.cpp:930 +#, c-format +msgid "" +"The volume of the solid model is:\n" +"\n" +" %s" +msgstr "" +"Objem modelu tělesa je:\n" +"\n" +" %s" + +#: solvespace.cpp:939 +#, c-format +msgid "" +"\n" +"The volume of current group mesh is:\n" +"\n" +" %s" +msgstr "" +"\n" +"Objem aktuální mesh skupiny je:\n" +"\n" +" %s" + +#: solvespace.cpp:944 +msgid "" +"\n" +"\n" +"Curved surfaces have been approximated as triangles.\n" +"This introduces error, typically of around 1%." +msgstr "" +"\n" +"\n" +"Zakřivené plochy byly aproximovány jako trojúhelníky.\n" +"To přináší chybu, obvykle kolem 1%%." + +#: solvespace.cpp:959 +#, c-format +msgid "" +"The surface area of the selected faces is:\n" +"\n" +" %s\n" +"\n" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." +msgstr "" +"Plocha vybraných stěn je:\n" +"\n" +" %s\n" +"\n" +"Křivky byly aproximovány jako po částech lineární.\n" +"To přináší chybu, obvykle kolem 1%%." + +#: solvespace.cpp:968 +msgid "" +"This group does not contain a correctly-formed 2d closed area. It is open, " +"not coplanar, or self-intersecting." +msgstr "" +"Tato skupina neobsahuje správně vytvořenou uzavřenou 2D oblast. Je otevřená, " +"není koplanární ani se neprotíná." + +#: solvespace.cpp:980 +#, c-format +msgid "" +"The area of the region sketched in this group is:\n" +"\n" +" %s\n" +"\n" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." +msgstr "" +"Plocha regionu zakresleného v této skupině je:\n" +"\n" +" %s\n" +"\n" +"Křivky byly aproximovány jako po částech lineární.\n" +"To přináší chybu, obvykle kolem 1%%." + +#: solvespace.cpp:1000 +#, c-format +msgid "" +"The total length of the selected entities is:\n" +"\n" +" %s\n" +"\n" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." +msgstr "" +"Celková délka vybraných entit je:\n" +"\n" +" %s\n" +"\n" +"Křivky byly aproximovány jako po částech lineární.\n" +"To přináší chybu, obvykle kolem 1%%." + +#: solvespace.cpp:1006 +msgid "Bad selection for perimeter; select line segments, arcs, and curves." +msgstr "Chybný výběr obvodu; vyber úsečky, oblouky a křivky." + +#: solvespace.cpp:1022 +msgid "Bad selection for trace; select a single point." +msgstr "Chybný výběr pro trasování; vyber jeden bod." + +#: solvespace.cpp:1049 +#, c-format +msgid "Couldn't write to '%s'" +msgstr "Nelze zapisovat do '%s'" + +#: solvespace.cpp:1079 +msgid "The mesh is self-intersecting (NOT okay, invalid)." +msgstr "Síť se sama protíná (NENÍ v pořádku, je neplatná)." + +#: solvespace.cpp:1080 +msgid "The mesh is not self-intersecting (okay, valid)." +msgstr "Síť se sama neprotíná (je v pořádku, je platná)." + +#: solvespace.cpp:1082 +msgid "The mesh has naked edges (NOT okay, invalid)." +msgstr "Síť má obnažené hrany (NENÍ v pořádku, je neplatná)." + +#: solvespace.cpp:1083 +msgid "The mesh is watertight (okay, valid)." +msgstr "Síť je vodotěsná (je v pořádku, je platná)." + +#: solvespace.cpp:1086 +#, c-format +msgid "" +"\n" +"\n" +"The model contains %d triangles, from %d surfaces." +msgstr "" +"\n" +"\n" +"Model obsahuje %d trojúhelníků z %d povrchů." + +#: solvespace.cpp:1090 +#, c-format +msgid "" +"%s\n" +"\n" +"%s\n" +"\n" +"Zero problematic edges, good.%s" +msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"Žádné problematické hrany, v pořádku.%s" + +#: solvespace.cpp:1093 +#, c-format +msgid "" +"%s\n" +"\n" +"%s\n" +"\n" +"%d problematic edges, bad.%s" +msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"Chyba, počet problematických hran: %d.%s" + +#: solvespace.cpp:1106 +#, c-format +msgid "" +"This is SolveSpace version %s.\n" +"\n" +"For more information, see http://solvespace.com/\n" +"\n" +"SolveSpace is free software: you are free to modify\n" +"and/or redistribute it under the terms of the GNU\n" +"General Public License (GPL) version 3 or later.\n" +"\n" +"There is NO WARRANTY, to the extent permitted by\n" +"law. For details, visit http://gnu.org/licenses/\n" +"\n" +"© 2008-%d Jonathan Westhues and other authors.\n" +msgstr "" +"Toto je SolveSpace verze %s.\n" +"\n" +"Další informace naleznete na adrese http://solvespace.com/\n" +"\n" +"SolveSpace je svobodný software: můžete jej svobodně upravovat\n" +"a/nebo jej šířit za podmínek GNU\n" +"General Public License (GPL) verze 3 nebo novější.\n" +"\n" +"V rozsahu povoleném zákonem není poskytována ŽÁDNÁ ZÁRUKA.\n" +"Podrobnosti najdete na adrese http://gnu.org/licenses/\n" +"\n" +"© 2008-%d Jonathan Westhues a další autoři.\n" + +#: style.cpp:185 +msgid "" +"Can't assign style to an entity that's derived from another entity; try " +"assigning a style to this entity's parent." +msgstr "" +"Nelze přiřadit styl entitě, která je odvozena od jiné entity; zkus přiřadit " +"styl nadřazené entitě." + +#: style.cpp:735 +msgid "Style name cannot be empty" +msgstr "Název stylu nemůže být prázdný" + +#: textscreens.cpp:837 +msgid "Can't repeat fewer than 1 time." +msgstr "Nelze opakovat méně než 1krát." + +#: textscreens.cpp:841 +msgid "Can't repeat more than 999 times." +msgstr "Nelze opakovat více než 999krát." + +#: textscreens.cpp:866 +msgid "Group name cannot be empty" +msgstr "Název skupiny nemůže být prázdný" + +#: textscreens.cpp:918 +msgid "Opacity must be between zero and one." +msgstr "Neprůhlednost musí být mezi nulou a jedničkou." + +#: textscreens.cpp:953 +msgid "Radius cannot be zero or negative." +msgstr "Poloměr nemůže být nulový nebo záporný." + +#: toolbar.cpp:18 +msgid "Sketch line segment" +msgstr "Náčrt úsečky" + +#: toolbar.cpp:20 +msgid "Sketch rectangle" +msgstr "Náčrt obdélníku" + +#: toolbar.cpp:22 +msgid "Sketch circle" +msgstr "Náčrt kružnice" + +#: toolbar.cpp:24 +msgid "Sketch arc of a circle" +msgstr "Náčrt oblouku kružnice" + +#: toolbar.cpp:26 +msgid "Sketch curves from text in a TrueType font" +msgstr "Náčrt křivek z textu v písmu TrueType" + +#: toolbar.cpp:28 +msgid "Sketch image from a file" +msgstr "Náčrt obrázku ze souboru" + +#: toolbar.cpp:30 +msgid "Create tangent arc at selected point" +msgstr "Vytvořit tečný oblouk ve vybraném bodě" + +#: toolbar.cpp:32 +msgid "Sketch cubic Bezier spline" +msgstr "Náčrt kubického Bézierova splajnu" + +#: toolbar.cpp:34 +msgid "Sketch datum point" +msgstr "Náčrt vztažného bodu" + +#: toolbar.cpp:36 +msgid "Toggle construction" +msgstr "Přepnout konstrukci" + +#: toolbar.cpp:38 +msgid "Split lines / curves where they intersect" +msgstr "Rozdělení úseček / křivek v místě jejich průsečíku" + +#: toolbar.cpp:42 +msgid "Constrain distance / diameter / length" +msgstr "Omezení vzdálenosti / průměru / délky" + +#: toolbar.cpp:44 +msgid "Constrain angle" +msgstr "Omezení úhlu" + +#: toolbar.cpp:46 +msgid "Constrain to be horizontal" +msgstr "Omezení horizontály" + +#: toolbar.cpp:48 +msgid "Constrain to be vertical" +msgstr "Omezení vertikály" + +#: toolbar.cpp:50 +msgid "Constrain to be parallel or tangent" +msgstr "Omezení rovnoběžnosti nebo tečny" + +#: toolbar.cpp:52 +msgid "Constrain to be perpendicular" +msgstr "Omezení kolmosti" + +#: toolbar.cpp:54 +msgid "Constrain point on line / curve / plane / point" +msgstr "Omezení bodu na přímce / křivce / rovině / bodu" + +#: toolbar.cpp:56 +msgid "Constrain symmetric" +msgstr "Omezení symetrie" + +#: toolbar.cpp:58 +msgid "Constrain equal length / radius / angle" +msgstr "Omezení shodné délky / poloměru / úhlu" + +#: toolbar.cpp:60 +msgid "Constrain normals in same orientation" +msgstr "Omezení normál se stejnou orientací" + +#: toolbar.cpp:62 +msgid "Other supplementary angle" +msgstr "Další doplňkový úhel" + +#: toolbar.cpp:64 +msgid "Toggle reference dimension" +msgstr "Přepnout referenční rozměr" + +#: toolbar.cpp:68 +msgid "New group extruding active sketch" +msgstr "Nová skupina extruzí aktivního náčrtu" + +#: toolbar.cpp:70 +msgid "New group rotating active sketch" +msgstr "Nová skupina plnou rotací aktivního náčrtu" + +#: toolbar.cpp:72 +msgid "New group helix from active sketch" +msgstr "Nová skupina šroubovicí aktivního náčrtu" + +#: toolbar.cpp:74 +msgid "New group revolve active sketch" +msgstr "Nová skupina volnou rotací aktivního náčrtu" + +#: toolbar.cpp:76 +msgid "New group step and repeat rotating" +msgstr "Nová skupina krokovým otočením" + +#: toolbar.cpp:78 +msgid "New group step and repeat translating" +msgstr "Nová skupina krokovým posunem" + +#: toolbar.cpp:80 +msgid "New group in new workplane (thru given entities)" +msgstr "Nová skupina v pracovní rovině (přes dané entity)" + +#: toolbar.cpp:82 +msgid "New group in 3d" +msgstr "Nová skupina ve 3D" + +#: toolbar.cpp:84 +msgid "New group linking / assembling file" +msgstr "Nová skupina odkazem / sestavením souboru" + +#: toolbar.cpp:88 +msgid "Nearest isometric view" +msgstr "Nejbližší isometrický pohled" + +#: toolbar.cpp:90 +msgid "Align view to active workplane" +msgstr "Zarovnat pohled na aktivní pracovní rovinu" + +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "Chyba" + +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "Zpráva" + +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "&OK" + +#: view.cpp:127 +msgid "Scale cannot be zero or negative." +msgstr "Měřítko nemůže být nulové nebo záporné." + +#: view.cpp:139 view.cpp:148 +msgid "Bad format: specify x, y, z" +msgstr "Chybný formát: zadej x, y, z" diff --git a/res/locales/de_DE.po b/res/locales/de_DE.po index bbd3fcda6..ab509c33c 100644 --- a/res/locales/de_DE.po +++ b/res/locales/de_DE.po @@ -5,19 +5,19 @@ msgid "" msgstr "" "Project-Id-Version: SolveSpace 3.0\n" -"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" -"POT-Creation-Date: 2021-02-01 15:45+0200\n" -"PO-Revision-Date: 2018-07-19 06:55+0000\n" +"Report-Msgid-Bugs-To: phkahler@gmail.com\n" +"POT-Creation-Date: 2025-01-26 21:04+0200\n" +"PO-Revision-Date: 2022-04-30 16:44+0200\n" "Last-Translator: Reini Urban \n" "Language-Team: none\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Zanata 4.5.0\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"X-Generator: Poedit 2.4.2\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: clipboard.cpp:310 +#: clipboard.cpp:314 msgid "" "Cut, paste, and copy work only in a workplane.\n" "\n" @@ -26,29 +26,29 @@ msgstr "" "Ausschneiden, Einfügen und Kopieren sind nur in einer Arbeitsebene " "zulässig.\n" "\n" -"Aktivieren Sie eine mit Skizze -> In Arbeitsebene" +"Aktivieren Sie eine mit \"Skizze -> In Arbeitsebene\"." -#: clipboard.cpp:327 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." msgstr "Zwischenablage ist leer; es gibt nichts einzufügen." -#: clipboard.cpp:374 +#: clipboard.cpp:378 msgid "Number of copies to paste must be at least one." msgstr "Die Anzahl der einzufügenden Kopien muss mind. 1 sein." -#: clipboard.cpp:390 textscreens.cpp:783 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "Maßstab kann nicht Null sein." -#: clipboard.cpp:432 +#: clipboard.cpp:436 msgid "Select one point to define origin of rotation." msgstr "Wählen Sie einen Punkt, um den Drehmittelpunkt zu definieren." -#: clipboard.cpp:444 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." msgstr "Wählen Sie zwei Punkte, um den Verschiebungsvektor zu definieren." -#: clipboard.cpp:454 +#: clipboard.cpp:458 msgid "" "Transformation is identity. So all copies will be exactly on top of each " "other." @@ -56,25 +56,25 @@ msgstr "" "Die Transformation ist die Identität. Alle Kopien werden deckungsgleich " "übereinanderliegen." -#: clipboard.cpp:458 +#: clipboard.cpp:462 msgid "Too many items to paste; split this into smaller pastes." msgstr "" "Zuviele Objekte zum Einfügen; teilen Sie diese in kleinere " "Einfügeoperationen auf." -#: clipboard.cpp:463 +#: clipboard.cpp:467 msgid "No workplane active." msgstr "Es ist keine Arbeitsebene aktiv." -#: confscreen.cpp:418 +#: confscreen.cpp:410 msgid "Bad format: specify coordinates as x, y, z" msgstr "Ungültiges Format: geben Sie Koordinaten als x, y, z an" -#: confscreen.cpp:428 style.cpp:659 textscreens.cpp:805 +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 msgid "Bad format: specify color as r, g, b" msgstr "Ungültiges Format: geben Sie Farben als r, g, b an" -#: confscreen.cpp:454 +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." @@ -82,25 +82,25 @@ msgstr "" "Der Perspektivfaktor wird sich nicht auswirken, bis Sie Ansicht -> " "Perspektive Projektion aktivieren." -#: confscreen.cpp:467 confscreen.cpp:477 +#: confscreen.cpp:464 confscreen.cpp:474 #, c-format msgid "Specify between 0 and %d digits after the decimal." msgstr "Geben Sie 0 bis %d Ziffern nach dem Dezimalzeichen an." -#: confscreen.cpp:489 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" msgstr "Der Exportmaßstab darf nicht Null sein!" -#: confscreen.cpp:501 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" msgstr "Der Werkzeugradialabstand darf nicht negativ sein!" -#: confscreen.cpp:555 +#: confscreen.cpp:557 msgid "Bad value: autosave interval should be positive" msgstr "" "Ungültiger Wert: Interval für automatisches Speichern muss positiv sein" -#: confscreen.cpp:558 +#: confscreen.cpp:560 msgid "Bad format: specify interval in integral minutes" msgstr "Ungültiges Format: geben Sie das Interval in ganzen Minuten an" @@ -171,141 +171,221 @@ msgstr "Längenverhältnis" #: constraint.cpp:25 msgctxt "constr-name" +msgid "arc-arc-length-ratio" +msgstr "Bogen-Bogen-Längenverhältnis" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "arc-line-length-ratio" +msgstr "Bogen-Linien-Längenverhältnis" + +#: constraint.cpp:27 +msgctxt "constr-name" msgid "length-difference" msgstr "Längendifferenz" -#: constraint.cpp:26 +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "arc-arc-len-difference" +msgstr "Bogen-Bogen-Längendifferenz" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "arc-line-len-difference" +msgstr "Bogen-Linien-Längendifferenz" + +#: constraint.cpp:30 msgctxt "constr-name" msgid "symmetric" msgstr "Symmetrisch" -#: constraint.cpp:27 +#: constraint.cpp:31 msgctxt "constr-name" msgid "symmetric-h" msgstr "Symmetrisch-H" -#: constraint.cpp:28 +#: constraint.cpp:32 msgctxt "constr-name" msgid "symmetric-v" msgstr "Symmetrisch-V" -#: constraint.cpp:29 +#: constraint.cpp:33 msgctxt "constr-name" msgid "symmetric-line" msgstr "Symmetrisch-Linie" -#: constraint.cpp:30 +#: constraint.cpp:34 msgctxt "constr-name" msgid "at-midpoint" msgstr "auf-Mittelpunkt" -#: constraint.cpp:31 +#: constraint.cpp:35 msgctxt "constr-name" msgid "horizontal" msgstr "Horizontal" -#: constraint.cpp:32 +#: constraint.cpp:36 msgctxt "constr-name" msgid "vertical" msgstr "Vertikal" -#: constraint.cpp:33 +#: constraint.cpp:37 msgctxt "constr-name" msgid "diameter" msgstr "Durchmesser" -#: constraint.cpp:34 +#: constraint.cpp:38 msgctxt "constr-name" msgid "pt-on-circle" msgstr "Pkt-auf-Kreis" -#: constraint.cpp:35 +#: constraint.cpp:39 msgctxt "constr-name" msgid "same-orientation" msgstr "gl-Orientierung" -#: constraint.cpp:36 +#: constraint.cpp:40 msgctxt "constr-name" msgid "angle" msgstr "Winkel" -#: constraint.cpp:37 +#: constraint.cpp:41 msgctxt "constr-name" msgid "parallel" msgstr "Parallel" -#: constraint.cpp:38 +#: constraint.cpp:42 msgctxt "constr-name" msgid "arc-line-tangent" msgstr "Bogen-Linie-Tangente" -#: constraint.cpp:39 +#: constraint.cpp:43 msgctxt "constr-name" msgid "cubic-line-tangent" msgstr "Kub-Linie-Tangente" -#: constraint.cpp:40 +#: constraint.cpp:44 msgctxt "constr-name" msgid "curve-curve-tangent" msgstr "Kurve-Kurve-Tangente" -#: constraint.cpp:41 +#: constraint.cpp:45 msgctxt "constr-name" msgid "perpendicular" msgstr "Rechtwinklig" -#: constraint.cpp:42 +#: constraint.cpp:46 msgctxt "constr-name" msgid "eq-radius" msgstr "gl-Radius" -#: constraint.cpp:43 +#: constraint.cpp:47 msgctxt "constr-name" msgid "eq-angle" msgstr "gl-Winkel" -#: constraint.cpp:44 +#: constraint.cpp:48 msgctxt "constr-name" msgid "eq-line-len-arc-len" msgstr "gl-Linie-Länge-Bogen-Länge" -#: constraint.cpp:45 +#: constraint.cpp:49 msgctxt "constr-name" msgid "lock-where-dragged" msgstr "Fix-an-Position" -#: constraint.cpp:46 +#: constraint.cpp:50 msgctxt "constr-name" msgid "comment" msgstr "Kommentar" -#: constraint.cpp:140 +#: constraint.cpp:151 +msgid "" +"The point you selected does not belong to the arc. The arc and line segment " +"do not share an end point.\n" +"\n" +"Select the end point of the arc at which you want it to be tangent to the " +"line." +msgstr "" +"Der ausgewählte Punkt gehört nicht zum Bogen. Der Bogen und das " +"Liniensegment haben keinen gemeinsamen Endpunkt.\n" +"\n" +"Wählen Sie den Endpunkt des Bogens aus, an dem Sie ihn tangential zur Linie " +"anpassen möchten." + +#: constraint.cpp:158 msgid "" "The tangent arc and line segment must share an endpoint. Constrain them with " -"Constrain -> On Point before constraining tangent." +"Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be " +"tangent to the line." msgstr "" "Die Bogentangente und das Liniensegment müssen einen gemeinsamen Endpunkt " "haben. Schränken Sie mit \"Einschränkung / Auf Punkt\" ein, bevor Sie die " -"Tangente einschränken. -> Sc" +"Tangente einschränken.\n" +"\n" +"Alternativ können Sie den Endpunkt des Bogens auswählen, an dem Sie die " +"Tangente an die Linie anpassen möchten." -#: constraint.cpp:158 +#: constraint.cpp:186 msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them " -"with Constrain -> On Point before constraining tangent." +"The point you selected is not an end point of the cubic spline. The spline " +"and line segment do not share an end point.\n" +"\n" +"Select the end point of the spline at which you want it to be tangent to the " +"line." +msgstr "" +"Der ausgewählte Punkt ist kein Endpunkt der kubischen Spline. Die Spline und " +"das Liniensegment haben keinen gemeinsamen Endpunkt.\n" +"\n" +"Wählen Sie den Endpunkt der Spline aus, an dem Sie sie tangential zur Linie " +"anpassen möchten." + +#: constraint.cpp:193 +msgid "" +"The tangent cubic spline and line segment must share an endpoint. Constrain " +"them with Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it " +"to be tangent to the line." msgstr "" "Die Kurventangente und das Liniensegment müssen einen gemeinsamen Endpunkt " "haben. Schränken Sie mit \"Einschränkung / Auf Punkt\" ein, bevor Sie die " -"Tangente einschränken. -> Sc" +"Tangente einschränken.\n" +"\n" +"Alternativ können Sie den Endpunkt der kubischen Spline auswählen, an dem " +"Sie die Tangente an die Linie anpassen möchten." + +#: constraint.cpp:237 +msgid "" +"The points you selected are not end points of the two curves. The curves do " +"not share an end point.\n" +"\n" +"Select the end points of both curves at which you want them to be tangent to " +"each other." +msgstr "" +"Die ausgewählten Punkte sind keine Endpunkte der beiden Kurven. Die Kurven " +"haben keinen gemeinsamen Endpunkt.\n" +"\n" +"Wählen Sie die Endpunkte beider Kurven aus, an denen Sie sie tangential " +"zueinander anpassen möchten." -#: constraint.cpp:183 +#: constraint.cpp:244 msgid "" "The curves must share an endpoint. Constrain them with Constrain -> On Point " -"before constraining tangent." +"before constraining tangent.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." msgstr "" "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.\n" +"\n" +"Alternativ können Sie die Endpunkte beider Kurven auswählen, an denen Sie " +"sie tangential zueinander anpassen möchten." -#: constraint.cpp:231 +#: constraint.cpp:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply " "to:\n" @@ -329,81 +409,83 @@ msgstr "" " * eine Seitenfläche und ein Punkt [minimaler Abstand]\n" " * ein Kreis oder ein Bogen [Durchmesser]\n" -#: constraint.cpp:284 +#: constraint.cpp:366 msgid "" "Bad selection for on point / curve / plane constraint. This constraint can " "apply to:\n" "\n" -" * two points (points coincident)\n" +" * two or more points (points coincident)\n" " * a point and a workplane (point in plane)\n" " * a point and a line segment (point on line)\n" " * a point and a circle or arc (point on curve)\n" -" * a point and a plane face (point on face)\n" +" * a point and one to three plane faces (point on face(s))\n" msgstr "" "Ungültige Auswahl für Einschränkung \"Auf Punkt / Kurve / Ebene\". Diese " "Einschränkung ist anwendbar auf:\n" "\n" -" * zwei Punkte [deckungsgleich]\n" +" * zwei oder mehr Punkte [deckungsgleich]\n" " * einen Punkt und eine Arbeitsebene [Punkt auf Ebene]\n" " * einen Punkt und ein Liniensegment [Punkt auf Linie]\n" " * einen Punkt und einen Kreis oder Bogen [Punkt auf Kurve]\n" -" * einen Punkt und eine Seitenfläche [Punkt auf Fläche]\n" +" * einen Punkt und ein bis drei Seitenflächen [Punkt auf Fläche]\n" -#: constraint.cpp:346 +#: constraint.cpp:427 msgid "" "Bad selection for equal length / radius constraint. This constraint can " "apply to:\n" "\n" -" * two line segments (equal length)\n" +" * two or more line segments (equal length)\n" " * two line segments and two points (equal point-line distances)\n" " * a line segment and two points (equal point-line distances)\n" " * a line segment, and a point and line segment (point-line distance " "equals length)\n" -" * four line segments or normals (equal angle between A,B and C,D)\n" -" * three line segments or normals (equal angle between A,B and B,C)\n" -" * two circles or arcs (equal radius)\n" +" * two or more circles or arcs (equal radius)\n" " * a line segment and an arc (line segment length equals arc length)\n" msgstr "" "Ungültige Auswahl für Einschränkung \"gleicher Abstand / Radius\". Diese " "Einschränkung ist anwendbar auf:\n" "\n" -" * zwei Liniensegmente [gleiche Länge]\n" +" * zwei oder mehr Liniensegmente [gleiche Länge]\n" " * zwei Liniensegmente und zwei Punkte [gleiche Punkt-Linien-Abstände]\n" " * ein Liniensegment und zwei Punkte [gleiche Punkt-Linien-Abstände]\n" " * ein Liniensegment und ein Punkt oder Liniensegment [Abstand Punkt-" "Linie gleich Länge]\n" -" * vier Liniensegmente oder Normale [gleicher Winkel zwischen A,B und C," -"D]\n" -" * drei Liniensegmente oder Normale [gleicher Winkel zwischen A,B und B," -"C]\n" -" * zwei Kreise oder Bögen [gleicher Radius]\n" +" * zwei oder mehr Kreise oder Bögen [gleicher Radius]\n" " * ein Liniensegment und ein Bogen [Länge des Liniensegments gleich " "Bogenlänge]\n" -#: constraint.cpp:385 +#: constraint.cpp:480 msgid "" "Bad selection for length ratio constraint. This constraint can apply to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" 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:402 +#: constraint.cpp:515 msgid "" "Bad selection for length difference constraint. This constraint can apply " "to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" 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:428 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" @@ -417,7 +499,7 @@ msgstr "" " * ein Liniensegment und eine Arbeitsebene [Mittelpunkt der Linie auf " "Ebene]\n" -#: constraint.cpp:486 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -438,7 +520,7 @@ msgstr "" " * eine Arbeitsebene und zwei Punkte oder ein Liniensegment [symmetrisch " "zu Arbeitsebene]\n" -#: constraint.cpp:500 +#: constraint.cpp:623 msgid "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." @@ -446,7 +528,7 @@ msgstr "" "Eine Arbeitsebene muss aktiv sein, um die Symmetrie ohne explizite " "Symmetrieebene einzuschränken." -#: constraint.cpp:530 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." @@ -454,21 +536,21 @@ msgstr "" "Aktivieren Sie eine Arbeitsebene (mit Skizze -> In Arbeitsebene), bevor Sie " "horizontal oder vertikal einschränken." -#: constraint.cpp:543 +#: constraint.cpp:679 msgid "" "Bad selection for horizontal / vertical constraint. This constraint can " "apply to:\n" "\n" -" * two points\n" -" * a line segment\n" +" * two or more points\n" +" * one or more line segments\n" msgstr "" "Ungültige Auswahl für Einschränkung \"horizontal / vertikal\". Diese " "Einschränkung ist anwendbar auf:\n" "\n" -" * zwei Punkte\n" -" * ein Liniensegment\n" +" * zwei oder mehr Punkte\n" +" * ein oder mehr Liniensegmente\n" -#: constraint.cpp:564 +#: constraint.cpp:697 msgid "" "Bad selection for same orientation constraint. This constraint can apply " "to:\n" @@ -480,57 +562,76 @@ msgstr "" "\n" " * zwei Normale\n" -#: constraint.cpp:614 +#: constraint.cpp:748 msgid "Must select an angle constraint." msgstr "Sie müssen einen eingeschränkten Winkel auswählen." -#: constraint.cpp:627 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." msgstr "Sie müssen eine Einschränkung mit zugeordneter Kennzeichnung angeben." -#: constraint.cpp:638 +#: constraint.cpp:784 msgid "" "Bad selection for angle constraint. This constraint can apply to:\n" "\n" +"Angle between:\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" +"\n" +"Equal angles:\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" msgstr "" "Ungültige Auswahl für Einschränkung \"Winkel\". Diese Einschränkung ist " "anwendbar auf:\n" "\n" +"Winkel zwischen:\n" " * zwei Liniensegmente\n" " * ein Liniensegment und eine Normale\n" " * zwei Normale\n" +"\n" +"Gleicher Winkel:\n" +" * vier Liniensegmente oder Normale [gleicher Winkel zwischen A,B und C," +"D]\n" +" * drei Liniensegmente oder Normale [gleicher Winkel zwischen A,B und B," +"C]\n" -#: constraint.cpp:701 +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." msgstr "" "Die Kurven-Kurven-Tangente muss in der Arbeitsebene eingeschränkt werden." -#: constraint.cpp:711 +#: constraint.cpp:887 msgid "" "Bad selection for parallel / tangent constraint. This constraint can apply " "to:\n" "\n" -" * two line segments (parallel)\n" -" * a line segment and a normal (parallel)\n" -" * two normals (parallel)\n" +" * two faces\n" +" * two or more line segments (parallel)\n" +" * one or more line segments and one or more normals (parallel)\n" +" * two or more normals (parallel)\n" " * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +" * two line segments, arcs, or beziers, that do not share an endpoint and " +"the end point(s) of the curve(s) (tangent)\n" msgstr "" "Ungültige Auswahl für Einschränkung \"Parallel / Tangente\". Diese " "Einschränkung ist anwendbar auf:\n" "\n" -" * zwei Liniensegmente [parallel]\n" -" * ein Liniensegment und eine Normale [parallel]\n" -" * zwei Normalen [parallel]\n" +" * zwei Flächen\n" +" * zwei oder mehr Liniensegmente [parallel]\n" +" * ein oder mehr Liniensegmente und eine oder mehr Normalen [parallel]\n" +" * zwei oder mehr Normalen [parallel]\n" " * zwei Liniensegmente, Bögen oder Beziers mit gemeinsamem Endpunkt " "[Tangente]\n" +" * zwei Liniensegmente, Bögen oder Beziers ohne gemeinsamen Endpunkt und " +"die Endpunkte der Kurve(n) [Tangente]\n" -#: constraint.cpp:729 +#: constraint.cpp:914 msgid "" "Bad selection for perpendicular constraint. This constraint can apply to:\n" "\n" +" * two faces\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" @@ -538,11 +639,12 @@ msgstr "" "Ungültige Auswahl für Einschränkung \"Rechtwinklig\". Diese Einschränkung " "ist anwendbar auf:\n" "\n" +" * zwei Flächen\n" " * zwei Liniensegmente\n" " * ein Liniensegment und eine Normale\n" " * zwei Normale\n" -#: constraint.cpp:744 +#: constraint.cpp:931 msgid "" "Bad selection for lock point where dragged constraint. This constraint can " "apply to:\n" @@ -554,7 +656,11 @@ msgstr "" "\n" " * einen Punkt\n" -#: constraint.cpp:755 +#: constraint.cpp:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "NEUER KOMMENTAR -- DOPPELKLICKEN ZUM BEARBEITEN" + +#: constraint.cpp:952 msgid "click center of comment text" msgstr "Klicken Sie auf die Mitte des Kommentartextes" @@ -564,7 +670,7 @@ msgid "" "2d View to export bare lines and curves." msgstr "" "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 msgid "" @@ -583,26 +689,26 @@ msgstr "" " * einen Punkt und zwei Liniensegmente [Schnittebene durch Punkt und " "parallel zu Linien]\n" -#: export.cpp:822 +#: export.cpp:818 msgid "Active group mesh is empty; nothing to export." msgstr "Das Netz der aktiven Gruppe ist leer; es gibt nichts zu exportieren." -#: exportvector.cpp:337 +#: exportvector.cpp:336 msgid "freehand lines were replaced with continuous lines" msgstr "Freihandlinien wurden mit durchgehenden Linien ersetzt" -#: exportvector.cpp:339 +#: exportvector.cpp:338 msgid "zigzag lines were replaced with continuous lines" msgstr "Zickzacklinien wurden mit durchgehenden Linien ersetzt" -#: exportvector.cpp:593 +#: exportvector.cpp:592 msgid "" "Some aspects of the drawing have no DXF equivalent and were not exported:\n" msgstr "" "Teile der Zeichnung haben keine Entsprechung in DXF und wurden nicht " "exportiert:\n" -#: exportvector.cpp:839 +#: exportvector.cpp:838 msgid "" "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." msgstr "" @@ -619,11 +725,11 @@ msgctxt "group-name" msgid "#references" msgstr "#Referenzen" -#: file.cpp:552 +#: file.cpp:555 msgid "The file is empty. It may be corrupt." msgstr "Die Datei ist leer. Es kann beschädigt sein." -#: file.cpp:557 +#: file.cpp:560 msgid "" "Unrecognized data in file. This file may be corrupt, or from a newer version " "of the program." @@ -631,18 +737,18 @@ msgstr "" "Nicht erkannte Daten in der Datei. Diese Datei könnte beschädigt sein oder " "von einer neueren Version des Programms stammen." -#: file.cpp:867 +#: file.cpp:876 msgctxt "title" msgid "Missing File" msgstr "Fehlende Datei" -#: file.cpp:868 +#: file.cpp:877 #, c-format msgctxt "dialog" msgid "The linked file “%s” is not present." msgstr "Die verlinkte Datei “%s” fehlt." -#: file.cpp:870 +#: file.cpp:879 msgctxt "dialog" msgid "" "Do you want to locate it manually?\n" @@ -654,17 +760,17 @@ msgstr "" "Falls Sie ablehnen, wird jegliche mit der fehlenden Datei verknüpfte " "Geometrie verworfen." -#: file.cpp:873 +#: file.cpp:882 msgctxt "button" msgid "&Yes" msgstr "&Ja" -#: file.cpp:875 +#: file.cpp:884 msgctxt "button" msgid "&No" msgstr "&Nein" -#: file.cpp:877 solvespace.cpp:569 +#: file.cpp:886 solvespace.cpp:652 msgctxt "button" msgid "&Cancel" msgstr "&Abbrechen" @@ -679,7 +785,7 @@ msgstr "&Neu" #: graphicswin.cpp:43 msgid "&Open..." -msgstr "&Öffnen" +msgstr "&Öffnen..." #: graphicswin.cpp:44 msgid "Open &Recent" @@ -707,7 +813,7 @@ msgstr "Exportiere 2D-Auswahl…" #: graphicswin.cpp:51 msgid "Export 3d &Wireframe..." -msgstr "Exportiere 3D-Drahtgittermodell" +msgstr "Exportiere 3D-Drahtgittermodell..." #: graphicswin.cpp:52 msgid "Export Triangle &Mesh..." @@ -719,7 +825,7 @@ msgstr "Exportiere Oberflächen…" #: graphicswin.cpp:54 msgid "Im&port..." -msgstr "Im&port…" +msgstr "Im&portiere…" #: graphicswin.cpp:57 msgid "E&xit" @@ -838,296 +944,308 @@ msgid "Use &Perspective Projection" msgstr "Perspektivische Projektion" #: graphicswin.cpp:97 +msgid "Show E&xploded View" +msgstr "Zeige e&xplodierte Ansicht" + +#: graphicswin.cpp:98 msgid "Dimension &Units" msgstr "Maßeinheit" -#: graphicswin.cpp:98 +#: graphicswin.cpp:99 msgid "Dimensions in &Millimeters" msgstr "Maße in Millimeter" -#: graphicswin.cpp:99 +#: graphicswin.cpp:100 msgid "Dimensions in M&eters" msgstr "Masse in M&etern" -#: graphicswin.cpp:100 +#: graphicswin.cpp:101 msgid "Dimensions in &Inches" msgstr "Maße in Zoll" #: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "Maße in &Fuß und Inch" + +#: graphicswin.cpp:104 msgid "Show &Toolbar" msgstr "Werkzeugleiste anzeigen" -#: graphicswin.cpp:103 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" msgstr "Attributbrowser anzeigen" -#: graphicswin.cpp:105 +#: graphicswin.cpp:107 msgid "&Full Screen" msgstr "Vollbildschirm" -#: graphicswin.cpp:107 +#: graphicswin.cpp:109 msgid "&New Group" msgstr "Neue Gruppe" -#: graphicswin.cpp:108 +#: graphicswin.cpp:110 msgid "Sketch In &3d" msgstr "In 3D skizzieren" -#: graphicswin.cpp:109 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" msgstr "In neuer Arbeitsebene skizzieren" -#: graphicswin.cpp:111 +#: graphicswin.cpp:113 msgid "Step &Translating" msgstr "Kopieren und verschieben" -#: graphicswin.cpp:112 +#: graphicswin.cpp:114 msgid "Step &Rotating" msgstr "Kopieren und drehen" -#: graphicswin.cpp:114 +#: graphicswin.cpp:116 msgid "E&xtrude" msgstr "E&xtrudieren" -#: graphicswin.cpp:115 +#: graphicswin.cpp:117 msgid "&Helix" msgstr "&Helix" -#: graphicswin.cpp:116 +#: graphicswin.cpp:118 msgid "&Lathe" msgstr "R&otieren" -#: graphicswin.cpp:117 +#: graphicswin.cpp:119 msgid "Re&volve" msgstr "D&rehen" -#: graphicswin.cpp:119 +#: graphicswin.cpp:121 msgid "Link / Assemble..." -msgstr "Verknüpfen / Zusammensetzen" +msgstr "Verknüpfen / Zusammensetzen..." -#: graphicswin.cpp:120 +#: graphicswin.cpp:122 msgid "Link Recent" msgstr "Letzte verknüpfen" -#: graphicswin.cpp:122 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "&Skizze" -#: graphicswin.cpp:123 +#: graphicswin.cpp:125 msgid "In &Workplane" msgstr "In Arbeitsebene" -#: graphicswin.cpp:124 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" msgstr "Im 3D-Raum" -#: graphicswin.cpp:126 +#: graphicswin.cpp:128 msgid "Datum &Point" msgstr "Bezugspunkt" -#: graphicswin.cpp:127 -msgid "&Workplane" +#: graphicswin.cpp:129 +msgid "Wor&kplane" msgstr "Arbeits&ebene" -#: graphicswin.cpp:129 +#: graphicswin.cpp:131 msgid "Line &Segment" msgstr "&Linie" -#: graphicswin.cpp:130 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" msgstr "K&onstruktionslinie" -#: graphicswin.cpp:131 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "&Rechteck" -#: graphicswin.cpp:132 +#: graphicswin.cpp:134 msgid "&Circle" msgstr "&Kreis" -#: graphicswin.cpp:133 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" msgstr "Kreisbogen" -#: graphicswin.cpp:134 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" msgstr "Kubischer &Bezier-Spline" -#: graphicswin.cpp:136 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" msgstr "&Text in Truetype-Font" -#: graphicswin.cpp:137 -msgid "&Image" +#: graphicswin.cpp:139 +msgid "I&mage" msgstr "B&ild" -#: graphicswin.cpp:139 +#: graphicswin.cpp:141 msgid "To&ggle Construction" msgstr "Konstruktionselement an/aus" -#: graphicswin.cpp:140 -msgid "Tangent &Arc at Point" +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" msgstr "Bogentangente an Punkt" -#: graphicswin.cpp:141 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" msgstr "Kurven im Schnittpunkt trennen" -#: graphicswin.cpp:143 +#: graphicswin.cpp:145 msgid "&Constrain" msgstr "&Einschränkung" -#: graphicswin.cpp:144 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "Abstand / Durchmesser" -#: graphicswin.cpp:145 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" msgstr "Referenzangabe" -#: graphicswin.cpp:146 -msgid "A&ngle" -msgstr "Winkel" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "Winkel / Gleicher Winkel" -#: graphicswin.cpp:147 +#: graphicswin.cpp:149 msgid "Reference An&gle" msgstr "Referenzwinkel" -#: graphicswin.cpp:148 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" msgstr "Komplementärwinkel" -#: graphicswin.cpp:149 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" msgstr "Referenzangabe ein/aus" -#: graphicswin.cpp:151 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "Horizontal" -#: graphicswin.cpp:152 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "&Vertikal" -#: graphicswin.cpp:154 +#: graphicswin.cpp:156 msgid "&On Point / Curve / Plane" msgstr "Auf Punkt / Kurve / Ebene" -#: graphicswin.cpp:155 -msgid "E&qual Length / Radius / Angle" -msgstr "Gleicher Abstand / Radius / Winkel" - -#: graphicswin.cpp:156 -msgid "Length Ra&tio" -msgstr "Längenverhältnis" - #: graphicswin.cpp:157 -msgid "Length Diff&erence" -msgstr "Längendifferenz" +msgid "E&qual Length / Radius" +msgstr "Gleicher Abstand / Radius" #: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "Länge / Bogen Verhäl&tnis" + +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Länge / Bogen Diff&erenz" + +#: graphicswin.cpp:160 msgid "At &Midpoint" msgstr "Auf &Mittelpunkt" -#: graphicswin.cpp:159 +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "Symmetrisch" -#: graphicswin.cpp:160 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "Paral&llel / Tangente" -#: graphicswin.cpp:161 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "Rechtwinklig" -#: graphicswin.cpp:162 +#: graphicswin.cpp:164 msgid "Same Orient&ation" msgstr "Gleiche Orientierung" -#: graphicswin.cpp:163 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "Punkt an Position fixieren" -#: graphicswin.cpp:165 +#: graphicswin.cpp:167 msgid "Comment" msgstr "Kommentar" -#: graphicswin.cpp:167 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "&Analyse" -#: graphicswin.cpp:168 +#: graphicswin.cpp:170 msgid "Measure &Volume" msgstr "&Volumen bestimmen" -#: graphicswin.cpp:169 +#: graphicswin.cpp:171 msgid "Measure A&rea" msgstr "Fläche bestimmen" -#: graphicswin.cpp:170 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" msgstr "Umfang bestimmen" -#: graphicswin.cpp:171 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" msgstr "Überlagernde Teile anzeigen" -#: graphicswin.cpp:172 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" msgstr "Freiliegende Kanten anzeigen" -#: graphicswin.cpp:173 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" msgstr "Massenmittelpunkt anzeigen" -#: graphicswin.cpp:175 +#: graphicswin.cpp:177 msgid "Show &Underconstrained Points" msgstr "&Unterbeschränkte Punkte anzeigen" -#: graphicswin.cpp:177 +#: graphicswin.cpp:179 msgid "&Trace Point" msgstr "Punkt nachzeichnen" -#: graphicswin.cpp:178 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." -msgstr "Nachzeichnen beenden" +msgstr "Nachzeichnen beenden..." -#: graphicswin.cpp:179 +#: graphicswin.cpp:181 msgid "Step &Dimension..." msgstr "Schrittgröße…" -#: graphicswin.cpp:181 +#: graphicswin.cpp:183 msgid "&Help" msgstr "&Hilfe" -#: graphicswin.cpp:182 +#: graphicswin.cpp:184 msgid "&Language" msgstr "Sprache" -#: graphicswin.cpp:183 +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "&Website / Anleitung" -#: graphicswin.cpp:185 +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "&Gehe zu GitHub commit" + +#: graphicswin.cpp:188 msgid "&About" msgstr "Über" -#: graphicswin.cpp:355 +#: graphicswin.cpp:362 msgid "(no recent files)" msgstr "(keine vorhergehenden Dateien)" -#: graphicswin.cpp:363 +#: graphicswin.cpp:370 #, c-format msgid "File '%s' does not exist." msgstr "Datei '%s' existiert nicht." -#: graphicswin.cpp:725 +#: graphicswin.cpp:779 msgid "No workplane is active, so the grid will not appear." msgstr "" "Das Raster wird nicht angezeigt, weil keine Arbeitsebene ausgewählt ist." -#: graphicswin.cpp:740 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel " "projection.\n" @@ -1141,20 +1259,20 @@ msgstr "" "Ändern Sie den Faktor für die Perspektivprojektion in der " "Konfigurationsmaske. Ein typischer Wert ist ca. 0,3." -#: graphicswin.cpp:819 +#: graphicswin.cpp:884 msgid "" "Select a point; this point will become the center of the view on screen." msgstr "" "Wählen Sie einen Punkt aus; dieser Punkt wird im Mittelpunkt der " "Bildschirmansicht sein." -#: graphicswin.cpp:1114 +#: graphicswin.cpp:1193 msgid "No additional entities share endpoints with the selected entities." msgstr "" "Die ausgewählten Objekte teilen keine gemeinsamen Endpunkte mit anderen " "Objekten." -#: graphicswin.cpp:1132 +#: graphicswin.cpp:1211 msgid "" "To use this command, select a point or other entity from an linked part, or " "make a link group the active group." @@ -1162,7 +1280,7 @@ msgstr "" "Für diesen Befehl wählen Sie einen Punkt oder ein anderes Objekt von einem " "verknüpften Teil aus, oder aktivieren Sie eine verknüpfte Gruppe." -#: graphicswin.cpp:1155 +#: graphicswin.cpp:1234 msgid "" "No workplane is active. Activate a workplane (with Sketch -> In Workplane) " "to define the plane for the snap grid." @@ -1171,7 +1289,7 @@ msgstr "" "(mit Skizze -> In Arbeitsebene), um die Ebene für das Gitterraster zu " "definieren." -#: graphicswin.cpp:1162 +#: graphicswin.cpp:1241 msgid "" "Can't snap these items to grid; select points, text comments, or constraints " "with a label. To snap a line, select its endpoints." @@ -1180,13 +1298,13 @@ msgstr "" "für Punkte, Textkommentare, oder Einschränkungen mit einer Bezeichnung. Um " "eine Linie auf das Raster auszurichten, wählen Sie deren Endpunkte aus." -#: graphicswin.cpp:1247 +#: graphicswin.cpp:1326 msgid "No workplane selected. Activating default workplane for this group." msgstr "" "Es wurde keine Arbeitsebene ausgewählt. Die Standard-Arbeitsebene für diese " "Gruppe wird aktiviert." -#: graphicswin.cpp:1250 +#: graphicswin.cpp:1329 msgid "" "No workplane is selected, and the active group does not have a default " "workplane. Try selecting a workplane, or activating a sketch-in-new-" @@ -1196,7 +1314,7 @@ msgstr "" "standardmäßige Arbeitsebene. Wählen Sie eine Arbeitsebene aus, oder " "erstellen Sie eine Gruppe in einer neuen Arbeitsebene." -#: graphicswin.cpp:1271 +#: graphicswin.cpp:1350 msgid "" "Bad selection for tangent arc at point. Select a single point, or select " "nothing to set up arc parameters." @@ -1204,48 +1322,48 @@ msgstr "" "Ungültige Auswahl für Bogentangente an Punkt. Wählen Sie einen einzelnen " "Punkt. Um die Bogenparameter anzugeben, wählen Sie nichts aus." -#: graphicswin.cpp:1282 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" msgstr "" "Erstellen Sie einen Punkt auf dem Bogen (zeichnet im Gegenuhrzeigersinn)" -#: graphicswin.cpp:1283 +#: graphicswin.cpp:1362 msgid "click to place datum point" msgstr "Klicken Sie, um einen Bezugspunkt zu platzieren" -#: graphicswin.cpp:1284 +#: graphicswin.cpp:1363 msgid "click first point of line segment" msgstr "Klicken Sie auf den ersten Punkt des Liniensegments" -#: graphicswin.cpp:1286 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" msgstr "Klicken Sie auf den ersten Punkt der Konstruktionslinie" -#: graphicswin.cpp:1287 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" msgstr "Klicken Sie auf den ersten Punkt der kubischen Linie" -#: graphicswin.cpp:1288 +#: graphicswin.cpp:1367 msgid "click center of circle" msgstr "Klicken Sie auf den Kreismittelpunkt" -#: graphicswin.cpp:1289 +#: graphicswin.cpp:1368 msgid "click origin of workplane" msgstr "Klicken Sie auf den Ursprungspunkt der Arbeitsebene" -#: graphicswin.cpp:1290 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" msgstr "Klicken Sie auf eine Ecke des Rechtecks" -#: graphicswin.cpp:1291 +#: graphicswin.cpp:1370 msgid "click top left of text" msgstr "Klicken Sie auf die obere linke Ecke des Texts" -#: graphicswin.cpp:1297 +#: graphicswin.cpp:1376 msgid "click top left of image" msgstr "Klicken Sie auf die obere linke Ecke des Bilds" -#: graphicswin.cpp:1309 +#: graphicswin.cpp:1402 msgid "" "No entities are selected. Select entities before trying to toggle their " "construction state." @@ -1258,41 +1376,41 @@ msgctxt "group-name" msgid "sketch-in-3d" msgstr "Skizze-in-3D" -#: group.cpp:142 +#: group.cpp:154 msgid "" "Bad selection for new sketch in workplane. This group can be created with:\n" "\n" " * a point (through the point, orthogonal to coordinate axes)\n" " * a point and two line segments (through the point, parallel to the " "lines)\n" +" * a point and a normal (through the point, orthogonal to the normal)\n" " * a workplane (copy of the workplane)\n" msgstr "" -"Ungültige Auswahl für Skizze in neuer Arbeitsebene. Diese Gruppe kann " +"Ungültige Auswahl für neue Skizze in der Arbeitsebene. Diese Gruppe kann " "erstellt werden mit:\n" "\n" -" * einem Punkt (durch den Punkt, orthogonal zu den Koordinatenachsen)\n" -" * einem Punkt und zwei Liniensegmenten (durch den Punkt, parallel zu den " -"Linien)\n" -" * einer Arbeitsebene (Kopie der Arbeitsebene)\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:154 +#: group.cpp:170 msgid "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " "will be extruded normal to the workplane." msgstr "" "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:163 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" msgstr "Extrusion" -#: group.cpp:168 +#: group.cpp:184 msgid "Lathe operation can only be applied to planar sketches." msgstr "Rotieren kann nur mit planaren Skizzen ausgeführt werden." -#: group.cpp:179 +#: group.cpp:195 msgid "" "Bad selection for new lathe group. This group can be created with:\n" "\n" @@ -1307,16 +1425,16 @@ msgstr "" "eine Achse parallel zur Linie/Normalen, durch den Punkt)\n" " * einem Liniensegment (Drehung um das Liniensegment)\n" -#: group.cpp:189 +#: group.cpp:205 msgctxt "group-name" msgid "lathe" msgstr "Drehung" -#: group.cpp:194 +#: group.cpp:210 msgid "Revolve operation can only be applied to planar sketches." msgstr "Revolve kann nur mit planaren Skizzen ausgeführt werden." -#: group.cpp:205 +#: group.cpp:221 msgid "" "Bad selection for new revolve group. This group can be created with:\n" "\n" @@ -1331,16 +1449,16 @@ msgstr "" "Achse parallel zu Linie / Normale, durch Punkt)\n" " * einem Liniensegment (gedreht um Liniensegment)\n" -#: group.cpp:217 +#: group.cpp:233 msgctxt "group-name" msgid "revolve" msgstr "Revolve" -#: group.cpp:222 +#: group.cpp:238 msgid "Helix operation can only be applied to planar sketches." msgstr "Helix kann nur mit planaren Skizzen ausgeführt werden." -#: group.cpp:233 +#: group.cpp:249 msgid "" "Bad selection for new helix group. This group can be created with:\n" "\n" @@ -1355,12 +1473,12 @@ msgstr "" "Achse parallel zu Linie / Normale, durch Punkt)\n" " * einem Liniensegment (gedreht um Liniensegment)\n" -#: group.cpp:245 +#: group.cpp:261 msgctxt "group-name" msgid "helix" msgstr "Helix" -#: group.cpp:258 +#: group.cpp:274 msgid "" "Bad selection for new rotation. This group can be created with:\n" "\n" @@ -1376,69 +1494,73 @@ msgstr "" " * einem Punkt und einer Linie oder einer Normale (gedreht um eine Achse " "durch den Punkt, parallel zur Linie / Normale)\n" -#: group.cpp:271 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" msgstr "Drehen" -#: group.cpp:282 +#: group.cpp:298 msgctxt "group-name" msgid "translate" msgstr "Versetzen" -#: group.cpp:400 +#: group.cpp:422 msgid "(unnamed)" msgstr "unbenannt" -#: groupmesh.cpp:709 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" msgstr "Kontur nicht geschlossen, oder kein einheitlicher Linientyp!" -#: groupmesh.cpp:722 +#: groupmesh.cpp:723 msgid "points not all coplanar!" msgstr "Punkte sind nicht alle koplanar!" -#: groupmesh.cpp:724 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" msgstr "Kontur überschneidet sich selbst!" -#: groupmesh.cpp:726 +#: groupmesh.cpp:727 msgid "zero-length edge!" msgstr "Kante mit Länge Null!" -#: modify.cpp:254 +#: importmesh.cpp:136 +msgid "Text-formated STL files are not currently supported" +msgstr "Text-formatierte STL Dateien werden aktuell nicht unterstützt" + +#: modify.cpp:252 msgid "Must be sketching in workplane to create tangent arc." msgstr "Eine Bogentangente kann nur in einer Arbeitsebene erstellt werden." -#: modify.cpp:301 +#: modify.cpp:299 msgid "" "To create a tangent arc, select a point where two non-construction lines or " "circles in this group and workplane join." msgstr "" "Um eine Bogentangente zu erstellen, wählen Sie einen Punkt, in dem sich zwei " "nicht-Konstruktionslinien oder -kreise in dieser Gruppe und Arbeitsebene " -"treffen. " +"treffen." -#: modify.cpp:388 +#: modify.cpp:386 msgid "" "Couldn't round this corner. Try a smaller radius, or try creating the " "desired geometry by hand with tangency constraints." msgstr "" "Diese Ecke konnte nicht abgerundet werden. Versuchen Sie einen kleineren " -"Radius, oder erstellen Sie die gewünschte Geometrie von Hand mit \"Tangente" -"\"-Einschränkungen." +"Radius, oder erstellen Sie die gewünschte Geometrie von Hand mit " +"\"Tangente\"-Einschränkungen." -#: modify.cpp:597 +#: modify.cpp:595 msgid "Couldn't split this entity; lines, circles, or cubics only." msgstr "" "Dieses Objekt konnte nicht geteilt werden. Dies geht nur für Linien, Kreise " "oder kubische Splines." -#: modify.cpp:624 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." msgstr "Trennen ist nur in einer Arbeitsebene möglich." -#: modify.cpp:631 +#: modify.cpp:629 msgid "" "Select two entities that intersect each other (e.g. two lines/circles/arcs " "or a line/circle/arc and a point)." @@ -1446,61 +1568,61 @@ msgstr "" "Wählen Sie zwei Objekte aus, die sich schneiden (z.B. zwei Linien/Kreise/" "Bögen, oder eine Linie/Kreis/Bogen und ein Punkt)." -#: modify.cpp:736 +#: modify.cpp:734 msgid "Can't split; no intersection found." msgstr "Trennen nicht möglich; keine Überschneidung gefunden." -#: mouse.cpp:559 +#: mouse.cpp:558 msgid "Assign to Style" msgstr "Linientyp zuordnen" -#: mouse.cpp:575 +#: mouse.cpp:574 msgid "No Style" msgstr "Kein Linientyp" -#: mouse.cpp:578 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." msgstr "Neu erstellter benutzerdefinierter Linientyp…" -#: mouse.cpp:585 +#: mouse.cpp:584 msgid "Group Info" msgstr "Info zu Gruppe" -#: mouse.cpp:605 +#: mouse.cpp:604 msgid "Style Info" msgstr "Info zu Linientyp" -#: mouse.cpp:625 +#: mouse.cpp:624 msgid "Select Edge Chain" msgstr "Kantenverlauf auswählen" -#: mouse.cpp:631 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" msgstr "Von/zu Referenzangabe wechseln" -#: mouse.cpp:637 +#: mouse.cpp:636 msgid "Other Supplementary Angle" msgstr "Anderer Komplementärwinkel" -#: mouse.cpp:642 +#: mouse.cpp:641 msgid "Snap to Grid" msgstr "Auf Raster ausrichten" -#: mouse.cpp:651 +#: mouse.cpp:650 msgid "Remove Spline Point" msgstr "Spline-Punkt löschen" -#: mouse.cpp:686 +#: mouse.cpp:685 msgid "Add Spline Point" msgstr "Spline-Punkt hinzufügen" -#: mouse.cpp:690 +#: mouse.cpp:689 msgid "Cannot add spline point: maximum number of points reached." msgstr "" "Spline-Punkt kann nicht hinzugefügt werden: maximale Anzahl der Punkte " "erreicht." -#: mouse.cpp:715 +#: mouse.cpp:714 msgid "Toggle Construction" msgstr "Konstruktionselement an/aus" @@ -1508,47 +1630,47 @@ msgstr "Konstruktionselement an/aus" msgid "Delete Point-Coincident Constraint" msgstr "Einschränkung \"Punkte deckungsgleich\" löschen" -#: mouse.cpp:749 +#: mouse.cpp:748 msgid "Cut" msgstr "Ausschneiden" -#: mouse.cpp:751 +#: mouse.cpp:750 msgid "Copy" msgstr "Kopieren" -#: mouse.cpp:755 +#: mouse.cpp:754 msgid "Select All" msgstr "Alle auswählen" -#: mouse.cpp:760 +#: mouse.cpp:759 msgid "Paste" msgstr "Einfügen" -#: mouse.cpp:762 +#: mouse.cpp:761 msgid "Paste Transformed..." msgstr "Einfügen und transformieren…" -#: mouse.cpp:767 +#: mouse.cpp:766 msgid "Delete" msgstr "Löschen" -#: mouse.cpp:770 +#: mouse.cpp:769 msgid "Unselect All" msgstr "Alle deselektieren" -#: mouse.cpp:777 +#: mouse.cpp:776 msgid "Unselect Hovered" msgstr "Aktive deselektieren" -#: mouse.cpp:786 +#: mouse.cpp:785 msgid "Zoom to Fit" msgstr "Zoom an Bildschirm anpassen" -#: mouse.cpp:988 mouse.cpp:1275 +#: mouse.cpp:987 mouse.cpp:1276 msgid "click next point of line, or press Esc" msgstr "Klicken Sie auf den nächsten Punkt der Linie, oder drücken Sie Esc" -#: mouse.cpp:994 +#: mouse.cpp:993 msgid "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." @@ -1556,7 +1678,7 @@ msgstr "" "Ein Rechteck kann nicht in 3D erstellt werden. Aktivieren Sie zuerst eine " "Arbeitsebene mit \"Skizze -> In Arbeitsebene\"." -#: mouse.cpp:1028 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" msgstr "Klicken Sie auf die gegenüberliegende Ecke des Rechtecks" @@ -1608,194 +1730,200 @@ msgstr "" "Das Bild kann nicht in 3D erstellt werden. Aktivieren Sie zuerst eine " "Arbeitsebene mit \"Skizze -> In Arbeitsebene\"." -#: mouse.cpp:1159 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" -msgstr "NEUER KOMMENTAR -- DOPPELKLICKEN ZUM BEARBEITEN" - -#: platform/gui.cpp:85 platform/gui.cpp:89 solvespace.cpp:511 +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 msgctxt "file-type" msgid "SolveSpace models" msgstr "SolveSpace-Modelle" -#: platform/gui.cpp:90 +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "ALLE" + +#: platform/gui.cpp:91 msgctxt "file-type" msgid "IDF circuit board" msgstr "IDF Leiterplatte" -#: platform/gui.cpp:94 +#: platform/gui.cpp:92 +msgctxt "file-type" +msgid "STL triangle mesh" +msgstr "STL-Dreiecks-Netz" + +#: platform/gui.cpp:96 msgctxt "file-type" msgid "PNG image" msgstr "PNG-Datei" -#: platform/gui.cpp:98 +#: platform/gui.cpp:100 msgctxt "file-type" msgid "STL mesh" msgstr "STL-Netz" -#: platform/gui.cpp:99 +#: platform/gui.cpp:101 msgctxt "file-type" msgid "Wavefront OBJ mesh" msgstr "Wavefront OBJ-Netz" -#: platform/gui.cpp:100 +#: platform/gui.cpp:102 msgctxt "file-type" msgid "Three.js-compatible mesh, with viewer" msgstr "Three.js-kompatibles Netz, mit Ansicht" -#: platform/gui.cpp:101 +#: platform/gui.cpp:103 msgctxt "file-type" msgid "Three.js-compatible mesh, mesh only" msgstr "Three.js-kompatibles Netz, nur Netz" -#: platform/gui.cpp:102 +#: platform/gui.cpp:104 msgctxt "file-type" msgid "VRML text file" msgstr "VRML Textdatei" -#: platform/gui.cpp:106 platform/gui.cpp:113 platform/gui.cpp:120 +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 msgctxt "file-type" msgid "STEP file" msgstr "STEP-Datei" -#: platform/gui.cpp:110 +#: platform/gui.cpp:112 msgctxt "file-type" msgid "PDF file" msgstr "PDF-Datei" -#: platform/gui.cpp:111 +#: platform/gui.cpp:113 msgctxt "file-type" msgid "Encapsulated PostScript" msgstr "Eingebettetes Postscript" -#: platform/gui.cpp:112 +#: platform/gui.cpp:114 msgctxt "file-type" msgid "Scalable Vector Graphics" msgstr "Skalierbare Vektorgrafik" -#: platform/gui.cpp:114 platform/gui.cpp:121 +#: platform/gui.cpp:116 platform/gui.cpp:123 msgctxt "file-type" msgid "DXF file (AutoCAD 2007)" msgstr "DXF-Datei (AutoCAD 2007)" -#: platform/gui.cpp:115 +#: platform/gui.cpp:117 msgctxt "file-type" msgid "HPGL file" msgstr "HPGL-Datei" -#: platform/gui.cpp:116 +#: platform/gui.cpp:118 msgctxt "file-type" msgid "G Code" msgstr "G-Code" -#: platform/gui.cpp:125 +#: platform/gui.cpp:127 msgctxt "file-type" msgid "AutoCAD DXF and DWG files" msgstr "AutoCAD DXF- und DWG-Dateien" -#: platform/gui.cpp:129 +#: platform/gui.cpp:131 msgctxt "file-type" msgid "Comma-separated values" msgstr "Werte durch Komma getrennt (CSV)" -#: platform/guigtk.cpp:1324 platform/guimac.mm:1363 platform/guiwin.cpp:1639 +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" msgstr "unbenannt" -#: platform/guigtk.cpp:1335 platform/guigtk.cpp:1368 platform/guimac.mm:1321 -#: platform/guiwin.cpp:1582 +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 msgctxt "title" msgid "Save File" msgstr "Datei speichern" -#: platform/guigtk.cpp:1336 platform/guigtk.cpp:1369 platform/guimac.mm:1304 -#: platform/guiwin.cpp:1584 +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 msgctxt "title" msgid "Open File" msgstr "Datei öffnen" -#: platform/guigtk.cpp:1339 platform/guigtk.cpp:1375 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" msgid "_Cancel" msgstr "_Abbrechen" -#: platform/guigtk.cpp:1340 platform/guigtk.cpp:1373 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" msgid "_Save" msgstr "_Speichern" -#: platform/guigtk.cpp:1341 platform/guigtk.cpp:1374 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" msgid "_Open" msgstr "_Öffnen" -#: solvespace.cpp:169 +#: solvespace.cpp:175 msgctxt "title" msgid "Autosave Available" msgstr "Automatische Sicherungsdatei verfügbar" -#: solvespace.cpp:170 +#: solvespace.cpp:176 msgctxt "dialog" msgid "An autosave file is available for this sketch." msgstr "Eine automatische Sicherung ist für diese Skizze verfügbar." -#: solvespace.cpp:171 +#: solvespace.cpp:177 msgctxt "dialog" msgid "Do you want to load the autosave file instead?" msgstr "Wollen Sie die automatische Sicherungsdatei stattdessen laden?" -#: solvespace.cpp:172 +#: solvespace.cpp:178 msgctxt "button" msgid "&Load autosave" msgstr "AutoDatei &öffnen" -#: solvespace.cpp:174 +#: solvespace.cpp:180 msgctxt "button" msgid "Do&n't Load" msgstr "&Nicht laden" -#: solvespace.cpp:557 +#: solvespace.cpp:640 msgctxt "title" msgid "Modified File" msgstr "Geänderte Datei" -#: solvespace.cpp:559 +#: solvespace.cpp:642 #, c-format msgctxt "dialog" msgid "Do you want to save the changes you made to the sketch “%s”?" msgstr "Wollen Sie die Änderungen an der Skizze “%s” sichern?" -#: solvespace.cpp:562 +#: solvespace.cpp:645 msgctxt "dialog" msgid "Do you want to save the changes you made to the new sketch?" msgstr "Wollen Sie die Änderungen an der Skizze sichern?" -#: solvespace.cpp:565 +#: solvespace.cpp:648 msgctxt "dialog" msgid "Your changes will be lost if you don't save them." msgstr "Ihre Änderungen werden verworfen, wenn sie nicht abgespeichert werden." -#: solvespace.cpp:566 +#: solvespace.cpp:649 msgctxt "button" msgid "&Save" msgstr "&Sichern" -#: solvespace.cpp:568 +#: solvespace.cpp:651 msgctxt "button" msgid "Do&n't Save" msgstr "&Verwerfen" # solvespace.cpp:557 -#: solvespace.cpp:589 +#: solvespace.cpp:672 msgctxt "title" msgid "(new sketch)" msgstr "(Neue Skizze)" -#: solvespace.cpp:596 +#: solvespace.cpp:683 msgctxt "title" msgid "Property Browser" msgstr "Attribut-Browser" -#: solvespace.cpp:658 +#: solvespace.cpp:746 msgid "" "Constraints are currently shown, and will be exported in the toolpath. This " "is probably not what you want; hide them by clicking the link at the top of " @@ -1805,7 +1933,7 @@ msgstr "" "wahrscheinlich nicht. Verstecken Sie sie in dem auf den Link oben im " "Textfenster klicken." -#: solvespace.cpp:730 +#: solvespace.cpp:834 #, c-format msgid "" "Can't identify file type from file extension of filename '%s'; try .dxf or ." @@ -1814,23 +1942,23 @@ msgstr "" "Kann den Dateityp der Datei '%s' nicht auf Grund der Dateierweiterung " "erkennen. Versuchen Sie .dxf oder .dwg." -#: solvespace.cpp:778 +#: solvespace.cpp:886 msgid "Constraint must have a label, and must not be a reference dimension." msgstr "" "Die Einschränkung muss einen Namen haben, und darf keine " "Referenzdimensionierung sein." -#: solvespace.cpp:782 +#: solvespace.cpp:890 msgid "Bad selection for step dimension; select a constraint." msgstr "" "Falsche Auswahl für die Schrittdimensionierung. Wählen Sie eine " "Einschränkung." -#: solvespace.cpp:806 +#: solvespace.cpp:914 msgid "The assembly does not interfere, good." msgstr "Der Zusammenbau funktioniert, gut." -#: solvespace.cpp:822 +#: solvespace.cpp:930 #, c-format msgid "" "The volume of the solid model is:\n" @@ -1841,7 +1969,7 @@ msgstr "" "\n" " %s" -#: solvespace.cpp:831 +#: solvespace.cpp:939 #, c-format msgid "" "\n" @@ -1854,7 +1982,7 @@ msgstr "" "\n" " %s" -#: solvespace.cpp:836 +#: solvespace.cpp:944 msgid "" "\n" "\n" @@ -1866,7 +1994,7 @@ msgstr "" "Gekrümmte Flächen wurden als Dreiecksnetz angenähert.\n" "Das verursacht Fehler, typischerweise um 1%." -#: solvespace.cpp:851 +#: solvespace.cpp:959 #, c-format msgid "" "The surface area of the selected faces is:\n" @@ -1883,7 +2011,7 @@ msgstr "" "Kurven wurden als gerade Linienstücke angenähert.\n" "Das verursacht Fehler, typischerweise um 1%%." -#: solvespace.cpp:860 +#: solvespace.cpp:968 msgid "" "This group does not contain a correctly-formed 2d closed area. It is open, " "not coplanar, or self-intersecting." @@ -1891,7 +2019,7 @@ msgstr "" "Diese Gruppe beinhaltet keine korrekt geschlossene 2D Fläche. Sie ist offen, " "nicht koplanar, oder überschneidet sich selbst." -#: solvespace.cpp:872 +#: solvespace.cpp:980 #, c-format msgid "" "The area of the region sketched in this group is:\n" @@ -1908,7 +2036,7 @@ msgstr "" "Kurven wurden als gerade Linienstücke angenähert.\n" "Das verursacht Fehler, typischerweise um 1%%." -#: solvespace.cpp:892 +#: solvespace.cpp:1000 #, c-format msgid "" "The total length of the selected entities is:\n" @@ -1925,36 +2053,36 @@ msgstr "" "Kurven wurden als gerade Linienstücke angenähert.\n" "Das verursacht Fehler, typischerweise um 1%%." -#: solvespace.cpp:898 +#: solvespace.cpp:1006 msgid "Bad selection for perimeter; select line segments, arcs, and curves." msgstr "Falsche Auswahl für Umfang. Wähle Liniensegmente, Bögen und Kurven." -#: solvespace.cpp:914 +#: solvespace.cpp:1022 msgid "Bad selection for trace; select a single point." msgstr "Falsche Auswahl für Punkt nachzeichnen. Wähle einen einzelnen Punkt." -#: solvespace.cpp:941 +#: solvespace.cpp:1049 #, c-format msgid "Couldn't write to '%s'" msgstr "Konnte '%s' nicht schreiben" -#: solvespace.cpp:971 +#: solvespace.cpp:1079 msgid "The mesh is self-intersecting (NOT okay, invalid)." msgstr "Das Netz schneidet sich selbst: Falsch, ungültig." -#: solvespace.cpp:972 +#: solvespace.cpp:1080 msgid "The mesh is not self-intersecting (okay, valid)." msgstr "Das Netz schneidet sich nicht: Gut, gültig." -#: solvespace.cpp:974 +#: solvespace.cpp:1082 msgid "The mesh has naked edges (NOT okay, invalid)." msgstr "Das Netz hat lose Kanten: Falsch, ungültig." -#: solvespace.cpp:975 +#: solvespace.cpp:1083 msgid "The mesh is watertight (okay, valid)." msgstr "Das Netz hat keine lose Kanten: Gut, gültig." -#: solvespace.cpp:978 +#: solvespace.cpp:1086 #, c-format msgid "" "\n" @@ -1965,7 +2093,7 @@ msgstr "" "\n" "Das Modell hat %d Dreiecke, von %d Flächen." -#: solvespace.cpp:982 +#: solvespace.cpp:1090 #, c-format msgid "" "%s\n" @@ -1980,7 +2108,7 @@ msgstr "" "\n" "Keine problematischen Kanten, gut.%s" -#: solvespace.cpp:985 +#: solvespace.cpp:1093 #, c-format msgid "" "%s\n" @@ -1995,7 +2123,7 @@ msgstr "" "\n" "%d problematische Kanten, schlecht.%s" -#: solvespace.cpp:998 +#: solvespace.cpp:1106 #, c-format msgid "" "This is SolveSpace version %s.\n" @@ -2025,7 +2153,7 @@ msgstr "" "\n" "© 2008-%d Jonathan Westhues und andere.\n" -#: style.cpp:166 +#: style.cpp:185 msgid "" "Can't assign style to an entity that's derived from another entity; try " "assigning a style to this entity's parent." @@ -2034,27 +2162,27 @@ msgstr "" "Objekt abgeleitet wurde. Versuchen Sie, dem übergeordneten Objekt einen Typ " "zuzuordnen." -#: style.cpp:665 +#: style.cpp:735 msgid "Style name cannot be empty" -msgstr "Name des Linientyps kann nicht leer sein." +msgstr "Name des Linientyps kann nicht leer sein" -#: textscreens.cpp:741 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." msgstr "Nicht weniger als 1 Wiederholung möglich." -#: textscreens.cpp:745 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." msgstr "Nicht mehr als 999 Wiederholungen möglich." -#: textscreens.cpp:770 +#: textscreens.cpp:866 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:813 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." msgstr "Durchsichtigkeit muss zwischen Null und Eins sein." -#: textscreens.cpp:848 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "Radius darf nicht null oder negativ sein." @@ -2210,14 +2338,175 @@ msgctxt "button" msgid "&OK" msgstr "&OK" -#: view.cpp:78 +#: view.cpp:127 msgid "Scale cannot be zero or negative." msgstr "Der Maßstab kann nicht Null oder negativ sein." -#: view.cpp:90 view.cpp:99 +#: view.cpp:139 view.cpp:148 msgid "Bad format: specify x, y, z" msgstr "Ungültiges Format: geben Sie x, y, z ein" +#~ msgid "" +#~ "Bad selection for on point / curve / plane constraint. This constraint " +#~ "can apply to:\n" +#~ "\n" +#~ " * two points (points coincident)\n" +#~ " * a point and a workplane (point in plane)\n" +#~ " * a point and a line segment (point on line)\n" +#~ " * a point and a circle or arc (point on curve)\n" +#~ " * a point and a plane face (point on face)\n" +#~ msgstr "" +#~ "Ungültige Auswahl für Einschränkung \"Auf Punkt / Kurve / Ebene\". Diese " +#~ "Einschränkung ist anwendbar auf:\n" +#~ "\n" +#~ " * zwei Punkte [deckungsgleich]\n" +#~ " * einen Punkt und eine Arbeitsebene [Punkt auf Ebene]\n" +#~ " * einen Punkt und ein Liniensegment [Punkt auf Linie]\n" +#~ " * einen Punkt und einen Kreis oder Bogen [Punkt auf Kurve]\n" +#~ " * einen Punkt und eine Seitenfläche [Punkt auf Fläche]\n" + +#~ msgid "" +#~ "Bad selection for equal length / radius constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (equal length)\n" +#~ " * two line segments and two points (equal point-line distances)\n" +#~ " * a line segment and two points (equal point-line distances)\n" +#~ " * a line segment, and a point and line segment (point-line distance " +#~ "equals length)\n" +#~ " * four line segments or normals (equal angle between A,B and C,D)\n" +#~ " * three line segments or normals (equal angle between A,B and B,C)\n" +#~ " * two circles or arcs (equal radius)\n" +#~ " * a line segment and an arc (line segment length equals arc length)\n" +#~ msgstr "" +#~ "Ungültige Auswahl für Einschränkung \"gleicher Abstand / Radius\". Diese " +#~ "Einschränkung ist anwendbar auf:\n" +#~ "\n" +#~ " * zwei Liniensegmente [gleiche Länge]\n" +#~ " * zwei Liniensegmente und zwei Punkte [gleiche Punkt-Linien-" +#~ "Abstände]\n" +#~ " * ein Liniensegment und zwei Punkte [gleiche Punkt-Linien-Abstände]\n" +#~ " * ein Liniensegment und ein Punkt oder Liniensegment [Abstand Punkt-" +#~ "Linie gleich Länge]\n" +#~ " * vier Liniensegmente oder Normale [gleicher Winkel zwischen A,B und " +#~ "C,D]\n" +#~ " * drei Liniensegmente oder Normale [gleicher Winkel zwischen A,B und " +#~ "B,C]\n" +#~ " * zwei Kreise oder Bögen [gleicher Radius]\n" +#~ " * ein Liniensegment und ein Bogen [Länge des Liniensegments gleich " +#~ "Bogenlänge]\n" + +#~ msgid "" +#~ "Bad selection for horizontal / vertical constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two points\n" +#~ " * a line segment\n" +#~ msgstr "" +#~ "Ungültige Auswahl für Einschränkung \"horizontal / vertikal\". Diese " +#~ "Einschränkung ist anwendbar auf:\n" +#~ "\n" +#~ " * zwei Punkte\n" +#~ " * ein Liniensegment\n" + +#~ msgid "" +#~ "Bad selection for angle constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" +#~ msgstr "" +#~ "Ungültige Auswahl für Einschränkung \"Winkel\". Diese Einschränkung ist " +#~ "anwendbar auf:\n" +#~ "\n" +#~ " * zwei Liniensegmente\n" +#~ " * ein Liniensegment und eine Normale\n" +#~ " * zwei Normale\n" + +#~ msgid "" +#~ "Bad selection for parallel / tangent constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (parallel)\n" +#~ " * a line segment and a normal (parallel)\n" +#~ " * two normals (parallel)\n" +#~ " * two line segments, arcs, or beziers, that share an endpoint " +#~ "(tangent)\n" +#~ msgstr "" +#~ "Ungültige Auswahl für Einschränkung \"Parallel / Tangente\". Diese " +#~ "Einschränkung ist anwendbar auf:\n" +#~ "\n" +#~ " * zwei Liniensegmente [parallel]\n" +#~ " * ein Liniensegment und eine Normale [parallel]\n" +#~ " * zwei Normalen [parallel]\n" +#~ " * zwei Liniensegmente, Bögen oder Beziers mit gemeinsamem Endpunkt " +#~ "[Tangente]\n" + +#~ msgid "" +#~ "Bad selection for perpendicular constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" +#~ msgstr "" +#~ "Ungültige Auswahl für Einschränkung \"Rechtwinklig\". Diese Einschränkung " +#~ "ist anwendbar auf:\n" +#~ "\n" +#~ " * zwei Liniensegmente\n" +#~ " * ein Liniensegment und eine Normale\n" +#~ " * zwei Normale\n" + +#~ msgid "A&ngle" +#~ msgstr "Winkel" + +#~ msgid "E&qual Length / Radius / Angle" +#~ msgstr "Gleicher Abstand / Radius / Winkel" + +#~ msgid "" +#~ "Bad selection for length ratio constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ msgstr "" +#~ "Ungültige Auswahl für Einschränkung \"Längenverhältnis\". Diese " +#~ "Einschränkung ist anwendbar auf:\n" +#~ "\n" +#~ " * zwei Liniensegmente\n" + +#~ msgid "" +#~ "Bad selection for length difference constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ msgstr "" +#~ "Ungültige Auswahl für Einschränkung \"Längendifferenz\". Diese " +#~ "Einschränkung ist anwendbar auf:\n" +#~ "\n" +#~ " * zwei Liniensegmente\n" + +#~ msgid "Length Ra&tio" +#~ msgstr "Längenverhältnis" + +#~ msgid "Length Diff&erence" +#~ msgstr "Längendifferenz" + +#~ msgid "" +#~ "Bad selection for new sketch in workplane. This group can be created " +#~ "with:\n" +#~ "\n" +#~ " * a point (through the point, orthogonal to coordinate axes)\n" +#~ " * a point and two line segments (through the point, parallel to the " +#~ "lines)\n" +#~ " * a workplane (copy of the workplane)\n" +#~ msgstr "" +#~ "Ungültige Auswahl für Skizze in neuer Arbeitsebene. Diese Gruppe kann " +#~ "erstellt werden mit:\n" +#~ "\n" +#~ " * einem Punkt (durch den Punkt, orthogonal zu den Koordinatenachsen)\n" +#~ " * einem Punkt und zwei Liniensegmenten (durch den Punkt, parallel zu " +#~ "den Linien)\n" +#~ " * einer Arbeitsebene (Kopie der Arbeitsebene)\n" + #~ msgctxt "file-type" #~ msgid "Q3D Object file" #~ msgstr "Q3D Objektdatei" diff --git a/res/locales/en_US.po b/res/locales/en_US.po index 27f024442..4de392b9a 100644 --- a/res/locales/en_US.po +++ b/res/locales/en_US.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: SolveSpace 3.0\n" -"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" -"POT-Creation-Date: 2021-02-01 15:45+0200\n" +"Report-Msgid-Bugs-To: phkahler@gmail.com\n" +"POT-Creation-Date: 2025-01-26 21:04+0200\n" "PO-Revision-Date: 2017-01-05 10:30+0000\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: clipboard.cpp:310 +#: clipboard.cpp:314 msgid "" "Cut, paste, and copy work only in a workplane.\n" "\n" @@ -27,27 +27,27 @@ msgstr "" "\n" "Activate one with Sketch -> In Workplane." -#: clipboard.cpp:327 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." msgstr "Clipboard is empty; nothing to paste." -#: clipboard.cpp:374 +#: clipboard.cpp:378 msgid "Number of copies to paste must be at least one." msgstr "Number of copies to paste must be at least one." -#: clipboard.cpp:390 textscreens.cpp:783 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "Scale cannot be zero." -#: clipboard.cpp:432 +#: clipboard.cpp:436 msgid "Select one point to define origin of rotation." msgstr "Select one point to define origin of rotation." -#: clipboard.cpp:444 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." msgstr "Select two points to define translation vector." -#: clipboard.cpp:454 +#: clipboard.cpp:458 msgid "" "Transformation is identity. So all copies will be exactly on top of each " "other." @@ -55,23 +55,23 @@ msgstr "" "Transformation is identity. So all copies will be exactly on top of each " "other." -#: clipboard.cpp:458 +#: clipboard.cpp:462 msgid "Too many items to paste; split this into smaller pastes." msgstr "Too many items to paste; split this into smaller pastes." -#: clipboard.cpp:463 +#: clipboard.cpp:467 msgid "No workplane active." msgstr "No workplane active." -#: confscreen.cpp:418 +#: confscreen.cpp:410 msgid "Bad format: specify coordinates as x, y, z" msgstr "Bad format: specify coordinates as x, y, z" -#: confscreen.cpp:428 style.cpp:659 textscreens.cpp:805 +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 msgid "Bad format: specify color as r, g, b" msgstr "Bad format: specify color as r, g, b" -#: confscreen.cpp:454 +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." @@ -79,24 +79,24 @@ msgstr "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." -#: confscreen.cpp:467 confscreen.cpp:477 +#: confscreen.cpp:464 confscreen.cpp:474 #, c-format msgid "Specify between 0 and %d digits after the decimal." msgstr "Specify between 0 and %d digits after the decimal." -#: confscreen.cpp:489 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" msgstr "Export scale must not be zero!" -#: confscreen.cpp:501 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" msgstr "Cutter radius offset must not be negative!" -#: confscreen.cpp:555 +#: confscreen.cpp:557 msgid "Bad value: autosave interval should be positive" msgstr "Bad value: autosave interval should be positive" -#: confscreen.cpp:558 +#: confscreen.cpp:560 msgid "Bad format: specify interval in integral minutes" msgstr "Bad format: specify interval in integral minutes" @@ -167,139 +167,219 @@ msgstr "length-ratio" #: constraint.cpp:25 msgctxt "constr-name" +msgid "arc-arc-length-ratio" +msgstr "arc-arc-length-ratio" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "arc-line-length-ratio" +msgstr "arc-line-length-ratio" + +#: constraint.cpp:27 +msgctxt "constr-name" msgid "length-difference" msgstr "length-difference" -#: constraint.cpp:26 +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "arc-arc-len-difference" +msgstr "arc-arc-len-difference" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "arc-line-len-difference" +msgstr "arc-line-len-difference" + +#: constraint.cpp:30 msgctxt "constr-name" msgid "symmetric" msgstr "symmetric" -#: constraint.cpp:27 +#: constraint.cpp:31 msgctxt "constr-name" msgid "symmetric-h" msgstr "symmetric-h" -#: constraint.cpp:28 +#: constraint.cpp:32 msgctxt "constr-name" msgid "symmetric-v" msgstr "symmetric-v" -#: constraint.cpp:29 +#: constraint.cpp:33 msgctxt "constr-name" msgid "symmetric-line" msgstr "symmetric-line" -#: constraint.cpp:30 +#: constraint.cpp:34 msgctxt "constr-name" msgid "at-midpoint" msgstr "at-midpoint" -#: constraint.cpp:31 +#: constraint.cpp:35 msgctxt "constr-name" msgid "horizontal" msgstr "horizontal" -#: constraint.cpp:32 +#: constraint.cpp:36 msgctxt "constr-name" msgid "vertical" msgstr "vertical" -#: constraint.cpp:33 +#: constraint.cpp:37 msgctxt "constr-name" msgid "diameter" msgstr "diameter" -#: constraint.cpp:34 +#: constraint.cpp:38 msgctxt "constr-name" msgid "pt-on-circle" msgstr "pt-on-circle" -#: constraint.cpp:35 +#: constraint.cpp:39 msgctxt "constr-name" msgid "same-orientation" msgstr "same-orientation" -#: constraint.cpp:36 +#: constraint.cpp:40 msgctxt "constr-name" msgid "angle" msgstr "angle" -#: constraint.cpp:37 +#: constraint.cpp:41 msgctxt "constr-name" msgid "parallel" msgstr "parallel" -#: constraint.cpp:38 +#: constraint.cpp:42 msgctxt "constr-name" msgid "arc-line-tangent" msgstr "arc-line-tangent" -#: constraint.cpp:39 +#: constraint.cpp:43 msgctxt "constr-name" msgid "cubic-line-tangent" msgstr "cubic-line-tangent" -#: constraint.cpp:40 +#: constraint.cpp:44 msgctxt "constr-name" msgid "curve-curve-tangent" msgstr "curve-curve-tangent" -#: constraint.cpp:41 +#: constraint.cpp:45 msgctxt "constr-name" msgid "perpendicular" msgstr "perpendicular" -#: constraint.cpp:42 +#: constraint.cpp:46 msgctxt "constr-name" msgid "eq-radius" msgstr "eq-radius" -#: constraint.cpp:43 +#: constraint.cpp:47 msgctxt "constr-name" msgid "eq-angle" msgstr "eq-angle" -#: constraint.cpp:44 +#: constraint.cpp:48 msgctxt "constr-name" msgid "eq-line-len-arc-len" msgstr "eq-line-len-arc-len" -#: constraint.cpp:45 +#: constraint.cpp:49 msgctxt "constr-name" msgid "lock-where-dragged" msgstr "lock-where-dragged" -#: constraint.cpp:46 +#: constraint.cpp:50 msgctxt "constr-name" msgid "comment" msgstr "comment" -#: constraint.cpp:140 +#: constraint.cpp:151 +msgid "" +"The point you selected does not belong to the arc. The arc and line segment " +"do not share an end point.\n" +"\n" +"Select the end point of the arc at which you want it to be tangent to the " +"line." +msgstr "" +"The point you selected does not belong to the arc. The arc and line segment " +"do not share an end point.\n" +"\n" +"Select the end point of the arc at which you want it to be tangent to the " +"line." + +#: constraint.cpp:158 msgid "" "The tangent arc and line segment must share an endpoint. Constrain them with " -"Constrain -> On Point before constraining tangent." +"Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be " +"tangent to the line." msgstr "" "The tangent arc and line segment must share an endpoint. Constrain them with " -"Constrain -> On Point before constraining tangent." +"Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be " +"tangent to the line." -#: constraint.cpp:158 +#: constraint.cpp:186 +msgid "" +"The point you selected is not an end point of the cubic spline. The spline " +"and line segment do not share an end point.\n" +"\n" +"Select the end point of the spline at which you want it to be tangent to the " +"line." +msgstr "" +"The point you selected is not an end point of the cubic spline. The spline " +"and line segment do not share an end point.\n" +"\n" +"Select the end point of the spline at which you want it to be tangent to the " +"line." + +#: constraint.cpp:193 +msgid "" +"The tangent cubic spline and line segment must share an endpoint. Constrain " +"them with Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it " +"to be tangent to the line." +msgstr "" +"The tangent cubic spline and line segment must share an endpoint. Constrain " +"them with Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it " +"to be tangent to the line." + +#: constraint.cpp:237 msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them " -"with Constrain -> On Point before constraining tangent." +"The points you selected are not end points of the two curves. The curves do " +"not share an end point.\n" +"\n" +"Select the end points of both curves at which you want them to be tangent to " +"each other." msgstr "" -"The tangent cubic and line segment must share an endpoint. Constrain them " -"with Constrain -> On Point before constraining tangent." +"The points you selected are not end points of the two curves. The curves do " +"not share an end point.\n" +"\n" +"Select the end points of both curves at which you want them to be tangent to " +"each other." -#: constraint.cpp:183 +#: constraint.cpp:244 msgid "" "The curves must share an endpoint. Constrain them with Constrain -> On Point " -"before constraining tangent." +"before constraining tangent.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." msgstr "" "The curves must share an endpoint. Constrain them with Constrain -> On Point " -"before constraining tangent." +"before constraining tangent.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." -#: constraint.cpp:231 +#: constraint.cpp:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply " "to:\n" @@ -323,77 +403,81 @@ msgstr "" " * a plane face and a point (minimum distance)\n" " * a circle or an arc (diameter)\n" -#: constraint.cpp:284 +#: constraint.cpp:366 msgid "" "Bad selection for on point / curve / plane constraint. This constraint can " "apply to:\n" "\n" -" * two points (points coincident)\n" +" * two or more points (points coincident)\n" " * a point and a workplane (point in plane)\n" " * a point and a line segment (point on line)\n" " * a point and a circle or arc (point on curve)\n" -" * a point and a plane face (point on face)\n" +" * a point and one to three plane faces (point on face(s))\n" msgstr "" "Bad selection for on point / curve / plane constraint. This constraint can " "apply to:\n" "\n" -" * two points (points coincident)\n" +" * two or more points (points coincident)\n" " * a point and a workplane (point in plane)\n" " * a point and a line segment (point on line)\n" " * a point and a circle or arc (point on curve)\n" -" * a point and a plane face (point on face)\n" +" * a point and one to three plane faces (point on face(s))\n" -#: constraint.cpp:346 +#: constraint.cpp:427 msgid "" "Bad selection for equal length / radius constraint. This constraint can " "apply to:\n" "\n" -" * two line segments (equal length)\n" +" * two or more line segments (equal length)\n" " * two line segments and two points (equal point-line distances)\n" " * a line segment and two points (equal point-line distances)\n" " * a line segment, and a point and line segment (point-line distance " "equals length)\n" -" * four line segments or normals (equal angle between A,B and C,D)\n" -" * three line segments or normals (equal angle between A,B and B,C)\n" -" * two circles or arcs (equal radius)\n" +" * two or more circles or arcs (equal radius)\n" " * a line segment and an arc (line segment length equals arc length)\n" msgstr "" "Bad selection for equal length / radius constraint. This constraint can " "apply to:\n" "\n" -" * two line segments (equal length)\n" +" * two or more line segments (equal length)\n" " * two line segments and two points (equal point-line distances)\n" " * a line segment and two points (equal point-line distances)\n" " * a line segment, and a point and line segment (point-line distance " "equals length)\n" -" * four line segments or normals (equal angle between A,B and C,D)\n" -" * three line segments or normals (equal angle between A,B and B,C)\n" -" * two circles or arcs (equal radius)\n" +" * two or more circles or arcs (equal radius)\n" " * a line segment and an arc (line segment length equals arc length)\n" -#: constraint.cpp:385 +#: constraint.cpp:480 msgid "" "Bad selection for length ratio constraint. This constraint can apply to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" "Bad selection for length ratio constraint. This constraint can apply to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" -#: constraint.cpp:402 +#: constraint.cpp:515 msgid "" "Bad selection for length difference constraint. This constraint can apply " "to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" "Bad selection for length difference constraint. This constraint can apply " "to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" -#: constraint.cpp:428 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" @@ -405,7 +489,7 @@ msgstr "" " * a line segment and a point (point at midpoint)\n" " * a line segment and a workplane (line's midpoint on plane)\n" -#: constraint.cpp:486 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -425,7 +509,7 @@ msgstr "" " * workplane, and two points or a line segment (symmetric about " "workplane)\n" -#: constraint.cpp:500 +#: constraint.cpp:623 msgid "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." @@ -433,7 +517,7 @@ msgstr "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." -#: constraint.cpp:530 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." @@ -441,21 +525,21 @@ msgstr "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." -#: constraint.cpp:543 +#: constraint.cpp:679 msgid "" "Bad selection for horizontal / vertical constraint. This constraint can " "apply to:\n" "\n" -" * two points\n" -" * a line segment\n" +" * two or more points\n" +" * one or more line segments\n" msgstr "" "Bad selection for horizontal / vertical constraint. This constraint can " "apply to:\n" "\n" -" * two points\n" -" * a line segment\n" +" * two or more points\n" +" * one or more line segments\n" -#: constraint.cpp:564 +#: constraint.cpp:697 msgid "" "Bad selection for same orientation constraint. This constraint can apply " "to:\n" @@ -467,65 +551,83 @@ msgstr "" "\n" " * two normals\n" -#: constraint.cpp:614 +#: constraint.cpp:748 msgid "Must select an angle constraint." msgstr "Must select an angle constraint." -#: constraint.cpp:627 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." msgstr "Must select a constraint with associated label." -#: constraint.cpp:638 +#: constraint.cpp:784 msgid "" "Bad selection for angle constraint. This constraint can apply to:\n" "\n" +"Angle between:\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" +"\n" +"Equal angles:\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" msgstr "" "Bad selection for angle constraint. This constraint can apply to:\n" "\n" +"Angle between:\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" +"\n" +"Equal angles:\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" -#: constraint.cpp:701 +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." msgstr "Curve-curve tangency must apply in workplane." -#: constraint.cpp:711 +#: constraint.cpp:887 msgid "" "Bad selection for parallel / tangent constraint. This constraint can apply " "to:\n" "\n" -" * two line segments (parallel)\n" -" * a line segment and a normal (parallel)\n" -" * two normals (parallel)\n" +" * two faces\n" +" * two or more line segments (parallel)\n" +" * one or more line segments and one or more normals (parallel)\n" +" * two or more normals (parallel)\n" " * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +" * two line segments, arcs, or beziers, that do not share an endpoint and " +"the end point(s) of the curve(s) (tangent)\n" msgstr "" "Bad selection for parallel / tangent constraint. This constraint can apply " "to:\n" "\n" -" * two line segments (parallel)\n" -" * a line segment and a normal (parallel)\n" -" * two normals (parallel)\n" +" * two faces\n" +" * two or more line segments (parallel)\n" +" * one or more line segments and one or more normals (parallel)\n" +" * two or more normals (parallel)\n" " * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +" * two line segments, arcs, or beziers, that do not share an endpoint and " +"the end point(s) of the curve(s) (tangent)\n" -#: constraint.cpp:729 +#: constraint.cpp:914 msgid "" "Bad selection for perpendicular constraint. This constraint can apply to:\n" "\n" +" * two faces\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" msgstr "" "Bad selection for perpendicular constraint. This constraint can apply to:\n" "\n" +" * two faces\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" -#: constraint.cpp:744 +#: constraint.cpp:931 msgid "" "Bad selection for lock point where dragged constraint. This constraint can " "apply to:\n" @@ -537,7 +639,11 @@ msgstr "" "\n" " * a point\n" -#: constraint.cpp:755 +#: constraint.cpp:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "NEW COMMENT -- DOUBLE-CLICK TO EDIT" + +#: constraint.cpp:952 msgid "click center of comment text" msgstr "click center of comment text" @@ -565,25 +671,25 @@ msgstr "" " * a point and two line segments (plane through point and parallel to " "lines)\n" -#: export.cpp:822 +#: export.cpp:818 msgid "Active group mesh is empty; nothing to export." msgstr "Active group mesh is empty; nothing to export." -#: exportvector.cpp:337 +#: exportvector.cpp:336 msgid "freehand lines were replaced with continuous lines" msgstr "freehand lines were replaced with continuous lines" -#: exportvector.cpp:339 +#: exportvector.cpp:338 msgid "zigzag lines were replaced with continuous lines" msgstr "zigzag lines were replaced with continuous lines" -#: exportvector.cpp:593 +#: exportvector.cpp:592 msgid "" "Some aspects of the drawing have no DXF equivalent and were not exported:\n" msgstr "" "Some aspects of the drawing have no DXF equivalent and were not exported:\n" -#: exportvector.cpp:839 +#: exportvector.cpp:838 msgid "" "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." msgstr "" @@ -599,11 +705,11 @@ msgctxt "group-name" msgid "#references" msgstr "#references" -#: file.cpp:552 +#: file.cpp:555 msgid "The file is empty. It may be corrupt." msgstr "The file is empty. It may be corrupt." -#: file.cpp:557 +#: file.cpp:560 msgid "" "Unrecognized data in file. This file may be corrupt, or from a newer version " "of the program." @@ -611,18 +717,18 @@ msgstr "" "Unrecognized data in file. This file may be corrupt, or from a newer version " "of the program." -#: file.cpp:867 +#: file.cpp:876 msgctxt "title" msgid "Missing File" msgstr "Missing File" -#: file.cpp:868 +#: file.cpp:877 #, c-format msgctxt "dialog" msgid "The linked file “%s” is not present." msgstr "The linked file “%s” is not present." -#: file.cpp:870 +#: file.cpp:879 msgctxt "dialog" msgid "" "Do you want to locate it manually?\n" @@ -635,17 +741,17 @@ msgstr "" "If you decline, any geometry that depends on the missing file will be " "permanently removed." -#: file.cpp:873 +#: file.cpp:882 msgctxt "button" msgid "&Yes" msgstr "&Yes" -#: file.cpp:875 +#: file.cpp:884 msgctxt "button" msgid "&No" msgstr "&No" -#: file.cpp:877 solvespace.cpp:569 +#: file.cpp:886 solvespace.cpp:652 msgctxt "button" msgid "&Cancel" msgstr "&Cancel" @@ -819,295 +925,307 @@ msgid "Use &Perspective Projection" msgstr "Use &Perspective Projection" #: graphicswin.cpp:97 +msgid "Show E&xploded View" +msgstr "Show E&xploded View" + +#: graphicswin.cpp:98 msgid "Dimension &Units" msgstr "Dimension &Units" -#: graphicswin.cpp:98 +#: graphicswin.cpp:99 msgid "Dimensions in &Millimeters" msgstr "Dimensions in &Millimeters" -#: graphicswin.cpp:99 +#: graphicswin.cpp:100 msgid "Dimensions in M&eters" msgstr "Dimensions in M&eters" -#: graphicswin.cpp:100 +#: graphicswin.cpp:101 msgid "Dimensions in &Inches" msgstr "Dimensions in &Inches" #: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "Dimensions in &Feet and Inches" + +#: graphicswin.cpp:104 msgid "Show &Toolbar" msgstr "Show &Toolbar" -#: graphicswin.cpp:103 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" msgstr "Show Property Bro&wser" -#: graphicswin.cpp:105 +#: graphicswin.cpp:107 msgid "&Full Screen" msgstr "&Full Screen" -#: graphicswin.cpp:107 +#: graphicswin.cpp:109 msgid "&New Group" msgstr "&New Group" -#: graphicswin.cpp:108 +#: graphicswin.cpp:110 msgid "Sketch In &3d" msgstr "Sketch In &3d" -#: graphicswin.cpp:109 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" msgstr "Sketch In New &Workplane" -#: graphicswin.cpp:111 +#: graphicswin.cpp:113 msgid "Step &Translating" msgstr "Step &Translating" -#: graphicswin.cpp:112 +#: graphicswin.cpp:114 msgid "Step &Rotating" msgstr "Step &Rotating" -#: graphicswin.cpp:114 +#: graphicswin.cpp:116 msgid "E&xtrude" msgstr "E&xtrude" -#: graphicswin.cpp:115 +#: graphicswin.cpp:117 msgid "&Helix" msgstr "&Helix" -#: graphicswin.cpp:116 +#: graphicswin.cpp:118 msgid "&Lathe" msgstr "&Lathe" -#: graphicswin.cpp:117 +#: graphicswin.cpp:119 msgid "Re&volve" msgstr "Re&volve" -#: graphicswin.cpp:119 +#: graphicswin.cpp:121 msgid "Link / Assemble..." msgstr "Link / Assemble..." -#: graphicswin.cpp:120 +#: graphicswin.cpp:122 msgid "Link Recent" msgstr "Link Recent" -#: graphicswin.cpp:122 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "&Sketch" -#: graphicswin.cpp:123 +#: graphicswin.cpp:125 msgid "In &Workplane" msgstr "In &Workplane" -#: graphicswin.cpp:124 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" msgstr "Anywhere In &3d" -#: graphicswin.cpp:126 +#: graphicswin.cpp:128 msgid "Datum &Point" msgstr "Datum &Point" -#: graphicswin.cpp:127 -msgid "&Workplane" -msgstr "&Workplane" - #: graphicswin.cpp:129 +msgid "Wor&kplane" +msgstr "Wor&kplane" + +#: graphicswin.cpp:131 msgid "Line &Segment" msgstr "Line &Segment" -#: graphicswin.cpp:130 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" msgstr "C&onstruction Line Segment" -#: graphicswin.cpp:131 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "&Rectangle" -#: graphicswin.cpp:132 +#: graphicswin.cpp:134 msgid "&Circle" msgstr "&Circle" -#: graphicswin.cpp:133 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" msgstr "&Arc of a Circle" -#: graphicswin.cpp:134 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" msgstr "&Bezier Cubic Spline" -#: graphicswin.cpp:136 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" msgstr "&Text in TrueType Font" -#: graphicswin.cpp:137 -msgid "&Image" -msgstr "&Image" - #: graphicswin.cpp:139 +msgid "I&mage" +msgstr "I&mage" + +#: graphicswin.cpp:141 msgid "To&ggle Construction" msgstr "To&ggle Construction" -#: graphicswin.cpp:140 -msgid "Tangent &Arc at Point" -msgstr "Tangent &Arc at Point" +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" +msgstr "Ta&ngent Arc at Point" -#: graphicswin.cpp:141 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" msgstr "Split Curves at &Intersection" -#: graphicswin.cpp:143 +#: graphicswin.cpp:145 msgid "&Constrain" msgstr "&Constrain" -#: graphicswin.cpp:144 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "&Distance / Diameter" -#: graphicswin.cpp:145 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" msgstr "Re&ference Dimension" -#: graphicswin.cpp:146 -msgid "A&ngle" -msgstr "A&ngle" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "A&ngle / Equal Angle" -#: graphicswin.cpp:147 +#: graphicswin.cpp:149 msgid "Reference An&gle" msgstr "Reference An&gle" -#: graphicswin.cpp:148 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" msgstr "Other S&upplementary Angle" -#: graphicswin.cpp:149 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" msgstr "Toggle R&eference Dim" -#: graphicswin.cpp:151 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "&Horizontal" -#: graphicswin.cpp:152 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "&Vertical" -#: graphicswin.cpp:154 +#: graphicswin.cpp:156 msgid "&On Point / Curve / Plane" msgstr "&On Point / Curve / Plane" -#: graphicswin.cpp:155 -msgid "E&qual Length / Radius / Angle" -msgstr "E&qual Length / Radius / Angle" - -#: graphicswin.cpp:156 -msgid "Length Ra&tio" -msgstr "Length Ra&tio" - #: graphicswin.cpp:157 -msgid "Length Diff&erence" -msgstr "Length Diff&erence" +msgid "E&qual Length / Radius" +msgstr "E&qual Length / Radius" #: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "Length / Arc Ra&tio" + +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Length / Arc Diff&erence" + +#: graphicswin.cpp:160 msgid "At &Midpoint" msgstr "At &Midpoint" -#: graphicswin.cpp:159 +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "S&ymmetric" -#: graphicswin.cpp:160 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "Para&llel / Tangent" -#: graphicswin.cpp:161 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "&Perpendicular" -#: graphicswin.cpp:162 +#: graphicswin.cpp:164 msgid "Same Orient&ation" msgstr "Same Orient&ation" -#: graphicswin.cpp:163 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "Lock Point Where &Dragged" -#: graphicswin.cpp:165 +#: graphicswin.cpp:167 msgid "Comment" msgstr "Comment" -#: graphicswin.cpp:167 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "&Analyze" -#: graphicswin.cpp:168 +#: graphicswin.cpp:170 msgid "Measure &Volume" msgstr "Measure &Volume" -#: graphicswin.cpp:169 +#: graphicswin.cpp:171 msgid "Measure A&rea" msgstr "Measure A&rea" -#: graphicswin.cpp:170 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" msgstr "Measure &Perimeter" -#: graphicswin.cpp:171 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" msgstr "Show &Interfering Parts" -#: graphicswin.cpp:172 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" msgstr "Show &Naked Edges" -#: graphicswin.cpp:173 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" msgstr "Show &Center of Mass" -#: graphicswin.cpp:175 +#: graphicswin.cpp:177 msgid "Show &Underconstrained Points" msgstr "Show &Underconstrained Points" -#: graphicswin.cpp:177 +#: graphicswin.cpp:179 msgid "&Trace Point" msgstr "&Trace Point" -#: graphicswin.cpp:178 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." msgstr "&Stop Tracing..." -#: graphicswin.cpp:179 +#: graphicswin.cpp:181 msgid "Step &Dimension..." msgstr "Step &Dimension..." -#: graphicswin.cpp:181 +#: graphicswin.cpp:183 msgid "&Help" msgstr "&Help" -#: graphicswin.cpp:182 +#: graphicswin.cpp:184 msgid "&Language" msgstr "&Language" -#: graphicswin.cpp:183 +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "&Website / Manual" -#: graphicswin.cpp:185 +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "&Go to GitHub commit" + +#: graphicswin.cpp:188 msgid "&About" msgstr "&About" -#: graphicswin.cpp:355 +#: graphicswin.cpp:362 msgid "(no recent files)" msgstr "(no recent files)" -#: graphicswin.cpp:363 +#: graphicswin.cpp:370 #, c-format msgid "File '%s' does not exist." msgstr "File '%s' does not exist." -#: graphicswin.cpp:725 +#: graphicswin.cpp:779 msgid "No workplane is active, so the grid will not appear." msgstr "No workplane is active, so the grid will not appear." -#: graphicswin.cpp:740 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel " "projection.\n" @@ -1121,17 +1239,17 @@ msgstr "" "For a perspective projection, modify the perspective factor in the " "configuration screen. A value around 0.3 is typical." -#: graphicswin.cpp:819 +#: graphicswin.cpp:884 msgid "" "Select a point; this point will become the center of the view on screen." msgstr "" "Select a point; this point will become the center of the view on screen." -#: graphicswin.cpp:1114 +#: graphicswin.cpp:1193 msgid "No additional entities share endpoints with the selected entities." msgstr "No additional entities share endpoints with the selected entities." -#: graphicswin.cpp:1132 +#: graphicswin.cpp:1211 msgid "" "To use this command, select a point or other entity from an linked part, or " "make a link group the active group." @@ -1139,7 +1257,7 @@ msgstr "" "To use this command, select a point or other entity from an linked part, or " "make a link group the active group." -#: graphicswin.cpp:1155 +#: graphicswin.cpp:1234 msgid "" "No workplane is active. Activate a workplane (with Sketch -> In Workplane) " "to define the plane for the snap grid." @@ -1147,7 +1265,7 @@ msgstr "" "No workplane is active. Activate a workplane (with Sketch -> In Workplane) " "to define the plane for the snap grid." -#: graphicswin.cpp:1162 +#: graphicswin.cpp:1241 msgid "" "Can't snap these items to grid; select points, text comments, or constraints " "with a label. To snap a line, select its endpoints." @@ -1155,11 +1273,11 @@ msgstr "" "Can't snap these items to grid; select points, text comments, or constraints " "with a label. To snap a line, select its endpoints." -#: graphicswin.cpp:1247 +#: graphicswin.cpp:1326 msgid "No workplane selected. Activating default workplane for this group." msgstr "No workplane selected. Activating default workplane for this group." -#: graphicswin.cpp:1250 +#: graphicswin.cpp:1329 msgid "" "No workplane is selected, and the active group does not have a default " "workplane. Try selecting a workplane, or activating a sketch-in-new-" @@ -1169,7 +1287,7 @@ msgstr "" "workplane. Try selecting a workplane, or activating a sketch-in-new-" "workplane group." -#: graphicswin.cpp:1271 +#: graphicswin.cpp:1350 msgid "" "Bad selection for tangent arc at point. Select a single point, or select " "nothing to set up arc parameters." @@ -1177,47 +1295,47 @@ msgstr "" "Bad selection for tangent arc at point. Select a single point, or select " "nothing to set up arc parameters." -#: graphicswin.cpp:1282 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" msgstr "click point on arc (draws anti-clockwise)" -#: graphicswin.cpp:1283 +#: graphicswin.cpp:1362 msgid "click to place datum point" msgstr "click to place datum point" -#: graphicswin.cpp:1284 +#: graphicswin.cpp:1363 msgid "click first point of line segment" msgstr "click first point of line segment" -#: graphicswin.cpp:1286 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" msgstr "click first point of construction line segment" -#: graphicswin.cpp:1287 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" msgstr "click first point of cubic segment" -#: graphicswin.cpp:1288 +#: graphicswin.cpp:1367 msgid "click center of circle" msgstr "click center of circle" -#: graphicswin.cpp:1289 +#: graphicswin.cpp:1368 msgid "click origin of workplane" msgstr "click origin of workplane" -#: graphicswin.cpp:1290 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" msgstr "click one corner of rectangle" -#: graphicswin.cpp:1291 +#: graphicswin.cpp:1370 msgid "click top left of text" msgstr "click top left of text" -#: graphicswin.cpp:1297 +#: graphicswin.cpp:1376 msgid "click top left of image" msgstr "click top left of image" -#: graphicswin.cpp:1309 +#: graphicswin.cpp:1402 msgid "" "No entities are selected. Select entities before trying to toggle their " "construction state." @@ -1230,13 +1348,14 @@ msgctxt "group-name" msgid "sketch-in-3d" msgstr "sketch-in-3d" -#: group.cpp:142 +#: group.cpp:154 msgid "" "Bad selection for new sketch in workplane. This group can be created with:\n" "\n" " * a point (through the point, orthogonal to coordinate axes)\n" " * a point and two line segments (through the point, parallel to the " "lines)\n" +" * a point and a normal (through the point, orthogonal to the normal)\n" " * a workplane (copy of the workplane)\n" msgstr "" "Bad selection for new sketch in workplane. This group can be created with:\n" @@ -1244,9 +1363,10 @@ msgstr "" " * a point (through the point, orthogonal to coordinate axes)\n" " * a point and two line segments (through the point, parallel to the " "lines)\n" +" * a point and a normal (through the point, orthogonal to the normal)\n" " * a workplane (copy of the workplane)\n" -#: group.cpp:154 +#: group.cpp:170 msgid "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " "will be extruded normal to the workplane." @@ -1254,16 +1374,16 @@ msgstr "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " "will be extruded normal to the workplane." -#: group.cpp:163 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" msgstr "extrude" -#: group.cpp:168 +#: group.cpp:184 msgid "Lathe operation can only be applied to planar sketches." msgstr "Lathe operation can only be applied to planar sketches." -#: group.cpp:179 +#: group.cpp:195 msgid "" "Bad selection for new lathe group. This group can be created with:\n" "\n" @@ -1277,16 +1397,16 @@ msgstr "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" -#: group.cpp:189 +#: group.cpp:205 msgctxt "group-name" msgid "lathe" msgstr "lathe" -#: group.cpp:194 +#: group.cpp:210 msgid "Revolve operation can only be applied to planar sketches." msgstr "Revolve operation can only be applied to planar sketches." -#: group.cpp:205 +#: group.cpp:221 msgid "" "Bad selection for new revolve group. This group can be created with:\n" "\n" @@ -1300,16 +1420,16 @@ msgstr "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" -#: group.cpp:217 +#: group.cpp:233 msgctxt "group-name" msgid "revolve" msgstr "revolve" -#: group.cpp:222 +#: group.cpp:238 msgid "Helix operation can only be applied to planar sketches." msgstr "Helix operation can only be applied to planar sketches." -#: group.cpp:233 +#: group.cpp:249 msgid "" "Bad selection for new helix group. This group can be created with:\n" "\n" @@ -1323,12 +1443,12 @@ msgstr "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" -#: group.cpp:245 +#: group.cpp:261 msgctxt "group-name" msgid "helix" msgstr "helix" -#: group.cpp:258 +#: group.cpp:274 msgid "" "Bad selection for new rotation. This group can be created with:\n" "\n" @@ -1344,41 +1464,45 @@ msgstr "" " * a point and a line or a normal (rotate about an axis through the " "point, and parallel to line / normal)\n" -#: group.cpp:271 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" msgstr "rotate" -#: group.cpp:282 +#: group.cpp:298 msgctxt "group-name" msgid "translate" msgstr "translate" -#: group.cpp:400 +#: group.cpp:422 msgid "(unnamed)" msgstr "(unnamed)" -#: groupmesh.cpp:709 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" msgstr "not closed contour, or not all same style!" -#: groupmesh.cpp:722 +#: groupmesh.cpp:723 msgid "points not all coplanar!" msgstr "points not all coplanar!" -#: groupmesh.cpp:724 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" msgstr "contour is self-intersecting!" -#: groupmesh.cpp:726 +#: groupmesh.cpp:727 msgid "zero-length edge!" msgstr "zero-length edge!" -#: modify.cpp:254 +#: importmesh.cpp:136 +msgid "Text-formated STL files are not currently supported" +msgstr "Text-formated STL files are not currently supported" + +#: modify.cpp:252 msgid "Must be sketching in workplane to create tangent arc." msgstr "Must be sketching in workplane to create tangent arc." -#: modify.cpp:301 +#: modify.cpp:299 msgid "" "To create a tangent arc, select a point where two non-construction lines or " "circles in this group and workplane join." @@ -1386,7 +1510,7 @@ msgstr "" "To create a tangent arc, select a point where two non-construction lines or " "circles in this group and workplane join." -#: modify.cpp:388 +#: modify.cpp:386 msgid "" "Couldn't round this corner. Try a smaller radius, or try creating the " "desired geometry by hand with tangency constraints." @@ -1394,15 +1518,15 @@ msgstr "" "Couldn't round this corner. Try a smaller radius, or try creating the " "desired geometry by hand with tangency constraints." -#: modify.cpp:597 +#: modify.cpp:595 msgid "Couldn't split this entity; lines, circles, or cubics only." msgstr "Couldn't split this entity; lines, circles, or cubics only." -#: modify.cpp:624 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." msgstr "Must be sketching in workplane to split." -#: modify.cpp:631 +#: modify.cpp:629 msgid "" "Select two entities that intersect each other (e.g. two lines/circles/arcs " "or a line/circle/arc and a point)." @@ -1410,59 +1534,59 @@ msgstr "" "Select two entities that intersect each other (e.g. two lines/circles/arcs " "or a line/circle/arc and a point)." -#: modify.cpp:736 +#: modify.cpp:734 msgid "Can't split; no intersection found." msgstr "Can't split; no intersection found." -#: mouse.cpp:559 +#: mouse.cpp:558 msgid "Assign to Style" msgstr "Assign to Style" -#: mouse.cpp:575 +#: mouse.cpp:574 msgid "No Style" msgstr "No Style" -#: mouse.cpp:578 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." msgstr "Newly Created Custom Style..." -#: mouse.cpp:585 +#: mouse.cpp:584 msgid "Group Info" msgstr "Group Info" -#: mouse.cpp:605 +#: mouse.cpp:604 msgid "Style Info" msgstr "Style Info" -#: mouse.cpp:625 +#: mouse.cpp:624 msgid "Select Edge Chain" msgstr "Select Edge Chain" -#: mouse.cpp:631 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" msgstr "Toggle Reference Dimension" -#: mouse.cpp:637 +#: mouse.cpp:636 msgid "Other Supplementary Angle" msgstr "Other Supplementary Angle" -#: mouse.cpp:642 +#: mouse.cpp:641 msgid "Snap to Grid" msgstr "Snap to Grid" -#: mouse.cpp:651 +#: mouse.cpp:650 msgid "Remove Spline Point" msgstr "Remove Spline Point" -#: mouse.cpp:686 +#: mouse.cpp:685 msgid "Add Spline Point" msgstr "Add Spline Point" -#: mouse.cpp:690 +#: mouse.cpp:689 msgid "Cannot add spline point: maximum number of points reached." msgstr "Cannot add spline point: maximum number of points reached." -#: mouse.cpp:715 +#: mouse.cpp:714 msgid "Toggle Construction" msgstr "Toggle Construction" @@ -1470,47 +1594,47 @@ msgstr "Toggle Construction" msgid "Delete Point-Coincident Constraint" msgstr "Delete Point-Coincident Constraint" -#: mouse.cpp:749 +#: mouse.cpp:748 msgid "Cut" msgstr "Cut" -#: mouse.cpp:751 +#: mouse.cpp:750 msgid "Copy" msgstr "Copy" -#: mouse.cpp:755 +#: mouse.cpp:754 msgid "Select All" msgstr "Select All" -#: mouse.cpp:760 +#: mouse.cpp:759 msgid "Paste" msgstr "Paste" -#: mouse.cpp:762 +#: mouse.cpp:761 msgid "Paste Transformed..." msgstr "Paste Transformed..." -#: mouse.cpp:767 +#: mouse.cpp:766 msgid "Delete" msgstr "Delete" -#: mouse.cpp:770 +#: mouse.cpp:769 msgid "Unselect All" msgstr "Unselect All" -#: mouse.cpp:777 +#: mouse.cpp:776 msgid "Unselect Hovered" msgstr "Unselect Hovered" -#: mouse.cpp:786 +#: mouse.cpp:785 msgid "Zoom to Fit" msgstr "Zoom to Fit" -#: mouse.cpp:988 mouse.cpp:1275 +#: mouse.cpp:987 mouse.cpp:1276 msgid "click next point of line, or press Esc" msgstr "click next point of line, or press Esc" -#: mouse.cpp:994 +#: mouse.cpp:993 msgid "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." @@ -1518,7 +1642,7 @@ msgstr "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." -#: mouse.cpp:1028 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" msgstr "click to place other corner of rectangle" @@ -1568,193 +1692,199 @@ msgstr "" "Can't draw image in 3d; first, activate a workplane with Sketch -> In " "Workplane." -#: mouse.cpp:1159 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" -msgstr "NEW COMMENT -- DOUBLE-CLICK TO EDIT" - -#: platform/gui.cpp:85 platform/gui.cpp:89 solvespace.cpp:511 +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 msgctxt "file-type" msgid "SolveSpace models" msgstr "SolveSpace models" -#: platform/gui.cpp:90 +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "ALL" + +#: platform/gui.cpp:91 msgctxt "file-type" msgid "IDF circuit board" msgstr "IDF circuit board" -#: platform/gui.cpp:94 +#: platform/gui.cpp:92 +msgctxt "file-type" +msgid "STL triangle mesh" +msgstr "STL triangle mesh" + +#: platform/gui.cpp:96 msgctxt "file-type" msgid "PNG image" msgstr "PNG image" -#: platform/gui.cpp:98 +#: platform/gui.cpp:100 msgctxt "file-type" msgid "STL mesh" msgstr "STL mesh" -#: platform/gui.cpp:99 +#: platform/gui.cpp:101 msgctxt "file-type" msgid "Wavefront OBJ mesh" msgstr "Wavefront OBJ mesh" -#: platform/gui.cpp:100 +#: platform/gui.cpp:102 msgctxt "file-type" msgid "Three.js-compatible mesh, with viewer" msgstr "Three.js-compatible mesh, with viewer" -#: platform/gui.cpp:101 +#: platform/gui.cpp:103 msgctxt "file-type" msgid "Three.js-compatible mesh, mesh only" msgstr "Three.js-compatible mesh, mesh only" -#: platform/gui.cpp:102 +#: platform/gui.cpp:104 msgctxt "file-type" msgid "VRML text file" msgstr "VRML text file" -#: platform/gui.cpp:106 platform/gui.cpp:113 platform/gui.cpp:120 +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 msgctxt "file-type" msgid "STEP file" msgstr "STEP file" -#: platform/gui.cpp:110 +#: platform/gui.cpp:112 msgctxt "file-type" msgid "PDF file" msgstr "PDF file" -#: platform/gui.cpp:111 +#: platform/gui.cpp:113 msgctxt "file-type" msgid "Encapsulated PostScript" msgstr "Encapsulated PostScript" -#: platform/gui.cpp:112 +#: platform/gui.cpp:114 msgctxt "file-type" msgid "Scalable Vector Graphics" msgstr "Scalable Vector Graphics" -#: platform/gui.cpp:114 platform/gui.cpp:121 +#: platform/gui.cpp:116 platform/gui.cpp:123 msgctxt "file-type" msgid "DXF file (AutoCAD 2007)" msgstr "DXF file (AutoCAD 2007)" -#: platform/gui.cpp:115 +#: platform/gui.cpp:117 msgctxt "file-type" msgid "HPGL file" msgstr "HPGL file" -#: platform/gui.cpp:116 +#: platform/gui.cpp:118 msgctxt "file-type" msgid "G Code" msgstr "G Code" -#: platform/gui.cpp:125 +#: platform/gui.cpp:127 msgctxt "file-type" msgid "AutoCAD DXF and DWG files" msgstr "AutoCAD DXF and DWG files" -#: platform/gui.cpp:129 +#: platform/gui.cpp:131 msgctxt "file-type" msgid "Comma-separated values" msgstr "Comma-separated values" -#: platform/guigtk.cpp:1324 platform/guimac.mm:1363 platform/guiwin.cpp:1639 +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" msgstr "untitled" -#: platform/guigtk.cpp:1335 platform/guigtk.cpp:1368 platform/guimac.mm:1321 -#: platform/guiwin.cpp:1582 +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 msgctxt "title" msgid "Save File" msgstr "Save File" -#: platform/guigtk.cpp:1336 platform/guigtk.cpp:1369 platform/guimac.mm:1304 -#: platform/guiwin.cpp:1584 +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 msgctxt "title" msgid "Open File" msgstr "Open File" -#: platform/guigtk.cpp:1339 platform/guigtk.cpp:1375 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" msgid "_Cancel" msgstr "_Cancel" -#: platform/guigtk.cpp:1340 platform/guigtk.cpp:1373 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" msgid "_Save" msgstr "_Save" -#: platform/guigtk.cpp:1341 platform/guigtk.cpp:1374 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" msgid "_Open" msgstr "_Open" -#: solvespace.cpp:169 +#: solvespace.cpp:175 msgctxt "title" msgid "Autosave Available" msgstr "Autosave Available" -#: solvespace.cpp:170 +#: solvespace.cpp:176 msgctxt "dialog" msgid "An autosave file is available for this sketch." msgstr "An autosave file is available for this sketch." -#: solvespace.cpp:171 +#: solvespace.cpp:177 msgctxt "dialog" msgid "Do you want to load the autosave file instead?" msgstr "Do you want to load the autosave file instead?" -#: solvespace.cpp:172 +#: solvespace.cpp:178 msgctxt "button" msgid "&Load autosave" msgstr "&Load autosave" -#: solvespace.cpp:174 +#: solvespace.cpp:180 msgctxt "button" msgid "Do&n't Load" msgstr "Do&n't Load" -#: solvespace.cpp:557 +#: solvespace.cpp:640 msgctxt "title" msgid "Modified File" msgstr "Modified File" -#: solvespace.cpp:559 +#: solvespace.cpp:642 #, c-format msgctxt "dialog" msgid "Do you want to save the changes you made to the sketch “%s”?" msgstr "Do you want to save the changes you made to the sketch “%s”?" -#: solvespace.cpp:562 +#: solvespace.cpp:645 msgctxt "dialog" msgid "Do you want to save the changes you made to the new sketch?" msgstr "Do you want to save the changes you made to the new sketch?" -#: solvespace.cpp:565 +#: solvespace.cpp:648 msgctxt "dialog" msgid "Your changes will be lost if you don't save them." msgstr "Your changes will be lost if you don't save them." -#: solvespace.cpp:566 +#: solvespace.cpp:649 msgctxt "button" msgid "&Save" msgstr "&Save" -#: solvespace.cpp:568 +#: solvespace.cpp:651 msgctxt "button" msgid "Do&n't Save" msgstr "Do&n't Save" -#: solvespace.cpp:589 +#: solvespace.cpp:672 msgctxt "title" msgid "(new sketch)" msgstr "(new sketch)" -#: solvespace.cpp:596 +#: solvespace.cpp:683 msgctxt "title" msgid "Property Browser" msgstr "Property Browser" -#: solvespace.cpp:658 +#: solvespace.cpp:746 msgid "" "Constraints are currently shown, and will be exported in the toolpath. This " "is probably not what you want; hide them by clicking the link at the top of " @@ -1764,7 +1894,7 @@ msgstr "" "is probably not what you want; hide them by clicking the link at the top of " "the text window." -#: solvespace.cpp:730 +#: solvespace.cpp:834 #, c-format msgid "" "Can't identify file type from file extension of filename '%s'; try .dxf or ." @@ -1773,19 +1903,19 @@ msgstr "" "Can't identify file type from file extension of filename '%s'; try .dxf or ." "dwg." -#: solvespace.cpp:778 +#: solvespace.cpp:886 msgid "Constraint must have a label, and must not be a reference dimension." msgstr "Constraint must have a label, and must not be a reference dimension." -#: solvespace.cpp:782 +#: solvespace.cpp:890 msgid "Bad selection for step dimension; select a constraint." msgstr "Bad selection for step dimension; select a constraint." -#: solvespace.cpp:806 +#: solvespace.cpp:914 msgid "The assembly does not interfere, good." msgstr "The assembly does not interfere, good." -#: solvespace.cpp:822 +#: solvespace.cpp:930 #, c-format msgid "" "The volume of the solid model is:\n" @@ -1796,7 +1926,7 @@ msgstr "" "\n" " %s" -#: solvespace.cpp:831 +#: solvespace.cpp:939 #, c-format msgid "" "\n" @@ -1809,7 +1939,7 @@ msgstr "" "\n" " %s" -#: solvespace.cpp:836 +#: solvespace.cpp:944 msgid "" "\n" "\n" @@ -1821,7 +1951,7 @@ msgstr "" "Curved surfaces have been approximated as triangles.\n" "This introduces error, typically of around 1%." -#: solvespace.cpp:851 +#: solvespace.cpp:959 #, c-format msgid "" "The surface area of the selected faces is:\n" @@ -1838,7 +1968,7 @@ msgstr "" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%." -#: solvespace.cpp:860 +#: solvespace.cpp:968 msgid "" "This group does not contain a correctly-formed 2d closed area. It is open, " "not coplanar, or self-intersecting." @@ -1846,7 +1976,7 @@ msgstr "" "This group does not contain a correctly-formed 2d closed area. It is open, " "not coplanar, or self-intersecting." -#: solvespace.cpp:872 +#: solvespace.cpp:980 #, c-format msgid "" "The area of the region sketched in this group is:\n" @@ -1863,7 +1993,7 @@ msgstr "" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%." -#: solvespace.cpp:892 +#: solvespace.cpp:1000 #, c-format msgid "" "The total length of the selected entities is:\n" @@ -1880,36 +2010,36 @@ msgstr "" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%." -#: solvespace.cpp:898 +#: solvespace.cpp:1006 msgid "Bad selection for perimeter; select line segments, arcs, and curves." msgstr "Bad selection for perimeter; select line segments, arcs, and curves." -#: solvespace.cpp:914 +#: solvespace.cpp:1022 msgid "Bad selection for trace; select a single point." msgstr "Bad selection for trace; select a single point." -#: solvespace.cpp:941 +#: solvespace.cpp:1049 #, c-format msgid "Couldn't write to '%s'" msgstr "Couldn't write to '%s'" -#: solvespace.cpp:971 +#: solvespace.cpp:1079 msgid "The mesh is self-intersecting (NOT okay, invalid)." msgstr "The mesh is self-intersecting (NOT okay, invalid)." -#: solvespace.cpp:972 +#: solvespace.cpp:1080 msgid "The mesh is not self-intersecting (okay, valid)." msgstr "The mesh is not self-intersecting (okay, valid)." -#: solvespace.cpp:974 +#: solvespace.cpp:1082 msgid "The mesh has naked edges (NOT okay, invalid)." msgstr "The mesh has naked edges (NOT okay, invalid)." -#: solvespace.cpp:975 +#: solvespace.cpp:1083 msgid "The mesh is watertight (okay, valid)." msgstr "The mesh is watertight (okay, valid)." -#: solvespace.cpp:978 +#: solvespace.cpp:1086 #, c-format msgid "" "\n" @@ -1920,7 +2050,7 @@ msgstr "" "\n" "The model contains %d triangles, from %d surfaces." -#: solvespace.cpp:982 +#: solvespace.cpp:1090 #, c-format msgid "" "%s\n" @@ -1935,7 +2065,7 @@ msgstr "" "\n" "Zero problematic edges, good.%s" -#: solvespace.cpp:985 +#: solvespace.cpp:1093 #, c-format msgid "" "%s\n" @@ -1950,7 +2080,7 @@ msgstr "" "\n" "%d problematic edges, bad.%s" -#: solvespace.cpp:998 +#: solvespace.cpp:1106 #, c-format msgid "" "This is SolveSpace version %s.\n" @@ -1979,7 +2109,7 @@ msgstr "" "\n" "© 2008-%d Jonathan Westhues and other authors.\n" -#: style.cpp:166 +#: style.cpp:185 msgid "" "Can't assign style to an entity that's derived from another entity; try " "assigning a style to this entity's parent." @@ -1987,27 +2117,27 @@ msgstr "" "Can't assign style to an entity that's derived from another entity; try " "assigning a style to this entity's parent." -#: style.cpp:665 +#: style.cpp:735 msgid "Style name cannot be empty" msgstr "Style name cannot be empty" -#: textscreens.cpp:741 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." msgstr "Can't repeat fewer than 1 time." -#: textscreens.cpp:745 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." msgstr "Can't repeat more than 999 times." -#: textscreens.cpp:770 +#: textscreens.cpp:866 msgid "Group name cannot be empty" msgstr "Group name cannot be empty" -#: textscreens.cpp:813 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." msgstr "Opacity must be between zero and one." -#: textscreens.cpp:848 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "Radius cannot be zero or negative." @@ -2162,14 +2292,227 @@ msgctxt "button" msgid "&OK" msgstr "&OK" -#: view.cpp:78 +#: view.cpp:127 msgid "Scale cannot be zero or negative." msgstr "Scale cannot be zero or negative." -#: view.cpp:90 view.cpp:99 +#: view.cpp:139 view.cpp:148 msgid "Bad format: specify x, y, z" msgstr "Bad format: specify x, y, z" +#~ msgid "" +#~ "The tangent arc and line segment must share an endpoint. Constrain them " +#~ "with Constrain -> On Point before constraining tangent." +#~ msgstr "" +#~ "The tangent arc and line segment must share an endpoint. Constrain them " +#~ "with Constrain -> On Point before constraining tangent." + +#~ msgid "" +#~ "The tangent cubic and line segment must share an endpoint. Constrain them " +#~ "with Constrain -> On Point before constraining tangent." +#~ msgstr "" +#~ "The tangent cubic and line segment must share an endpoint. Constrain them " +#~ "with Constrain -> On Point before constraining tangent." + +#~ msgid "" +#~ "The curves must share an endpoint. Constrain them with Constrain -> On " +#~ "Point before constraining tangent." +#~ msgstr "" +#~ "The curves must share an endpoint. Constrain them with Constrain -> On " +#~ "Point before constraining tangent." + +#~ msgid "" +#~ "Bad selection for parallel / tangent constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two faces\n" +#~ " * two or more line segments (parallel)\n" +#~ " * one or more line segments and one or more normals (parallel)\n" +#~ " * two or more normals (parallel)\n" +#~ " * two line segments, arcs, or beziers, that share an endpoint " +#~ "(tangent)\n" +#~ msgstr "" +#~ "Bad selection for parallel / tangent constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two faces\n" +#~ " * two or more line segments (parallel)\n" +#~ " * one or more line segments and one or more normals (parallel)\n" +#~ " * two or more normals (parallel)\n" +#~ " * two line segments, arcs, or beziers, that share an endpoint " +#~ "(tangent)\n" + +#~ msgid "&Workplane" +#~ msgstr "&Workplane" + +#~ msgid "&Image" +#~ msgstr "&Image" + +#~ msgid "Tangent &Arc at Point" +#~ msgstr "Tangent &Arc at Point" + +#~ msgid "" +#~ "Bad selection for on point / curve / plane constraint. This constraint " +#~ "can apply to:\n" +#~ "\n" +#~ " * two points (points coincident)\n" +#~ " * a point and a workplane (point in plane)\n" +#~ " * a point and a line segment (point on line)\n" +#~ " * a point and a circle or arc (point on curve)\n" +#~ " * a point and a plane face (point on face)\n" +#~ msgstr "" +#~ "Bad selection for on point / curve / plane constraint. This constraint " +#~ "can apply to:\n" +#~ "\n" +#~ " * two points (points coincident)\n" +#~ " * a point and a workplane (point in plane)\n" +#~ " * a point and a line segment (point on line)\n" +#~ " * a point and a circle or arc (point on curve)\n" +#~ " * a point and a plane face (point on face)\n" + +#~ msgid "" +#~ "Bad selection for equal length / radius constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (equal length)\n" +#~ " * two line segments and two points (equal point-line distances)\n" +#~ " * a line segment and two points (equal point-line distances)\n" +#~ " * a line segment, and a point and line segment (point-line distance " +#~ "equals length)\n" +#~ " * four line segments or normals (equal angle between A,B and C,D)\n" +#~ " * three line segments or normals (equal angle between A,B and B,C)\n" +#~ " * two circles or arcs (equal radius)\n" +#~ " * a line segment and an arc (line segment length equals arc length)\n" +#~ msgstr "" +#~ "Bad selection for equal length / radius constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (equal length)\n" +#~ " * two line segments and two points (equal point-line distances)\n" +#~ " * a line segment and two points (equal point-line distances)\n" +#~ " * a line segment, and a point and line segment (point-line distance " +#~ "equals length)\n" +#~ " * four line segments or normals (equal angle between A,B and C,D)\n" +#~ " * three line segments or normals (equal angle between A,B and B,C)\n" +#~ " * two circles or arcs (equal radius)\n" +#~ " * a line segment and an arc (line segment length equals arc length)\n" + +#~ msgid "" +#~ "Bad selection for horizontal / vertical constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two points\n" +#~ " * a line segment\n" +#~ msgstr "" +#~ "Bad selection for horizontal / vertical constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two points\n" +#~ " * a line segment\n" + +#~ msgid "" +#~ "Bad selection for angle constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" +#~ msgstr "" +#~ "Bad selection for angle constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" + +#~ msgid "" +#~ "Bad selection for parallel / tangent constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (parallel)\n" +#~ " * a line segment and a normal (parallel)\n" +#~ " * two normals (parallel)\n" +#~ " * two line segments, arcs, or beziers, that share an endpoint " +#~ "(tangent)\n" +#~ msgstr "" +#~ "Bad selection for parallel / tangent constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (parallel)\n" +#~ " * a line segment and a normal (parallel)\n" +#~ " * two normals (parallel)\n" +#~ " * two line segments, arcs, or beziers, that share an endpoint " +#~ "(tangent)\n" + +#~ msgid "" +#~ "Bad selection for perpendicular constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" +#~ msgstr "" +#~ "Bad selection for perpendicular constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" + +#~ msgid "A&ngle" +#~ msgstr "A&ngle" + +#~ msgid "E&qual Length / Radius / Angle" +#~ msgstr "E&qual Length / Radius / Angle" + +#~ msgid "&Mirror" +#~ msgstr "&Mirror" + +#~ msgctxt "group-name" +#~ msgid "mirror" +#~ msgstr "mirror" + +#~ msgid "" +#~ "Bad selection for length ratio constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ msgstr "" +#~ "Bad selection for length ratio constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" + +#~ msgid "" +#~ "Bad selection for length difference constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ msgstr "" +#~ "Bad selection for length difference constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" + +#~ msgid "Length Ra&tio" +#~ msgstr "Length Ra&tio" + +#~ msgid "Length Diff&erence" +#~ msgstr "Length Diff&erence" + +#~ msgid "" +#~ "Bad selection for new sketch in workplane. This group can be created " +#~ "with:\n" +#~ "\n" +#~ " * a point (through the point, orthogonal to coordinate axes)\n" +#~ " * a point and two line segments (through the point, parallel to the " +#~ "lines)\n" +#~ " * a workplane (copy of the workplane)\n" +#~ msgstr "" +#~ "Bad selection for new sketch in workplane. This group can be created " +#~ "with:\n" +#~ "\n" +#~ " * a point (through the point, orthogonal to coordinate axes)\n" +#~ " * a point and two line segments (through the point, parallel to the " +#~ "lines)\n" +#~ " * a workplane (copy of the workplane)\n" + #~ msgctxt "file-type" #~ msgid "Q3D Object file" #~ msgstr "Q3D Object file" diff --git a/res/locales/es_AR.po b/res/locales/es_AR.po new file mode 100644 index 000000000..28cb363ca --- /dev/null +++ b/res/locales/es_AR.po @@ -0,0 +1,2319 @@ +# Spanish/Argentina translations for SolveSpace package. +# Copyright (C) 2017 the SolveSpace authors +# This file is distributed under the same license as the SolveSpace package. +# Maxi , 2021. +# +msgid "" +msgstr "" +"Project-Id-Version: SolveSpace 3.0\n" +"Report-Msgid-Bugs-To: phkahler@gmail.com\n" +"POT-Creation-Date: 2025-01-26 21:04+0200\n" +"PO-Revision-Date: 2021-09-17 \n" +"Last-Translator: Maxi Vasquez \n" +"Language-Team: AndesFreeDesign\n" +"Language: es_AR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.4.2\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: clipboard.cpp:314 +msgid "" +"Cut, paste, and copy work only in a workplane.\n" +"\n" +"Activate one with Sketch -> In Workplane." +msgstr "" +"Cortar, pegar y copiar funciona solo en un plano de trabajo.\n" +"\n" +"Activar uno con Croquis -> En Plano de trabajo." + +#: clipboard.cpp:331 +msgid "Clipboard is empty; nothing to paste." +msgstr "El portapapeles está vacío; nada que pegar." + +#: clipboard.cpp:378 +msgid "Number of copies to paste must be at least one." +msgstr "El número de copias para pegar debe ser al menos una." + +#: clipboard.cpp:394 textscreens.cpp:879 +msgid "Scale cannot be zero." +msgstr "La escala no puede ser cero." + +#: clipboard.cpp:436 +msgid "Select one point to define origin of rotation." +msgstr "Seleccione un punto para definir el origen de la rotación." + +#: clipboard.cpp:448 +msgid "Select two points to define translation vector." +msgstr "Seleccione dos puntos para definir el vector de traslación." + +#: clipboard.cpp:458 +msgid "" +"Transformation is identity. So all copies will be exactly on top of each " +"other." +msgstr "" +"La transformación es identidad. Entonces todas las copias estarán " +"exactamente una encima de la otra." + +#: clipboard.cpp:462 +msgid "Too many items to paste; split this into smaller pastes." +msgstr "Demasiados elementos para pegar; divida esto en partes más pequeñas." + +#: clipboard.cpp:467 +msgid "No workplane active." +msgstr "Ningún plano de trabajo activo." + +#: confscreen.cpp:410 +msgid "Bad format: specify coordinates as x, y, z" +msgstr "Formato incorrecto: especifique las coordenadas como x, y, z" + +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 +msgid "Bad format: specify color as r, g, b" +msgstr "Formato incorrecto: especifique color como r, g, b" + +#: confscreen.cpp:446 +msgid "" +"The perspective factor will have no effect until you enable View -> Use " +"Perspective Projection." +msgstr "" +"El factor de perspectiva no tendrá ningún efecto hasta que habilite Ver -> " +"Usar Proyección Perspectiva." + +#: confscreen.cpp:464 confscreen.cpp:474 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "Especifique entre 0 y %d dígitos después del decimal." + +#: confscreen.cpp:486 +msgid "Export scale must not be zero!" +msgstr "¡La escala de exportación no debe ser cero!" + +#: confscreen.cpp:498 +msgid "Cutter radius offset must not be negative!" +msgstr "¡El desfase del radio de corte no debe ser negativo!" + +#: confscreen.cpp:557 +msgid "Bad value: autosave interval should be positive" +msgstr "Valor incorrecto: el intervalo de autoguardado debe ser positivo" + +#: confscreen.cpp:560 +msgid "Bad format: specify interval in integral minutes" +msgstr "Formato incorrecto: especifique el intervalo en minutos integrales" + +#: constraint.cpp:12 +msgctxt "constr-name" +msgid "pts-coincident" +msgstr "ps-coincidente" + +#: constraint.cpp:13 +msgctxt "constr-name" +msgid "pt-pt-distance" +msgstr "p-p-distancia" + +#: constraint.cpp:14 +msgctxt "constr-name" +msgid "pt-line-distance" +msgstr "p-línea-distancia" + +#: constraint.cpp:15 +msgctxt "constr-name" +msgid "pt-plane-distance" +msgstr "p-plano-distancia" + +#: constraint.cpp:16 +msgctxt "constr-name" +msgid "pt-face-distance" +msgstr "p-cara-distancia" + +#: constraint.cpp:17 +msgctxt "constr-name" +msgid "proj-pt-pt-distance" +msgstr "proy-p-p-distancia" + +#: constraint.cpp:18 +msgctxt "constr-name" +msgid "pt-in-plane" +msgstr "p-en-plano" + +#: constraint.cpp:19 +msgctxt "constr-name" +msgid "pt-on-line" +msgstr "p-sobre-línea" + +#: constraint.cpp:20 +msgctxt "constr-name" +msgid "pt-on-face" +msgstr "p-sobre-cara" + +#: constraint.cpp:21 +msgctxt "constr-name" +msgid "eq-length" +msgstr "igual-longitud" + +#: constraint.cpp:22 +msgctxt "constr-name" +msgid "eq-length-and-pt-ln-dist" +msgstr "igual-longitud-y-p-línea-dist" + +#: constraint.cpp:23 +msgctxt "constr-name" +msgid "eq-pt-line-distances" +msgstr "igual-p-línea-distancias" + +#: constraint.cpp:24 +msgctxt "constr-name" +msgid "length-ratio" +msgstr "longitud-radio" + +#: constraint.cpp:25 +msgctxt "constr-name" +msgid "arc-arc-length-ratio" +msgstr "arco-arco-longitud-relación" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "arc-line-length-ratio" +msgstr "arco-línea-longitud-relación" + +#: constraint.cpp:27 +msgctxt "constr-name" +msgid "length-difference" +msgstr "longitud-diferencia" + +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "arc-arc-len-difference" +msgstr "arco-arco-long-diferencia" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "arc-line-len-difference" +msgstr "arco-línea-long-diferencia" + +#: constraint.cpp:30 +msgctxt "constr-name" +msgid "symmetric" +msgstr "simetría" + +#: constraint.cpp:31 +msgctxt "constr-name" +msgid "symmetric-h" +msgstr "simetría-h" + +#: constraint.cpp:32 +msgctxt "constr-name" +msgid "symmetric-v" +msgstr "simetría-v" + +#: constraint.cpp:33 +msgctxt "constr-name" +msgid "symmetric-line" +msgstr "simetría-línea" + +#: constraint.cpp:34 +msgctxt "constr-name" +msgid "at-midpoint" +msgstr "en-puntoMedio" + +#: constraint.cpp:35 +msgctxt "constr-name" +msgid "horizontal" +msgstr "horizontal" + +#: constraint.cpp:36 +msgctxt "constr-name" +msgid "vertical" +msgstr "vertical" + +#: constraint.cpp:37 +msgctxt "constr-name" +msgid "diameter" +msgstr "diámetro" + +#: constraint.cpp:38 +msgctxt "constr-name" +msgid "pt-on-circle" +msgstr "p-sobre-círculo" + +#: constraint.cpp:39 +msgctxt "constr-name" +msgid "same-orientation" +msgstr "misma-orientación" + +#: constraint.cpp:40 +msgctxt "constr-name" +msgid "angle" +msgstr "ángulo" + +#: constraint.cpp:41 +msgctxt "constr-name" +msgid "parallel" +msgstr "paralela" + +#: constraint.cpp:42 +msgctxt "constr-name" +msgid "arc-line-tangent" +msgstr "arco-línea-tangente" + +#: constraint.cpp:43 +msgctxt "constr-name" +msgid "cubic-line-tangent" +msgstr "cúbica-línea-tangente" + +#: constraint.cpp:44 +msgctxt "constr-name" +msgid "curve-curve-tangent" +msgstr "curva-curva-tangente" + +#: constraint.cpp:45 +msgctxt "constr-name" +msgid "perpendicular" +msgstr "perpendicular" + +#: constraint.cpp:46 +msgctxt "constr-name" +msgid "eq-radius" +msgstr "igual-radio" + +#: constraint.cpp:47 +msgctxt "constr-name" +msgid "eq-angle" +msgstr "igual-ángulo" + +#: constraint.cpp:48 +msgctxt "constr-name" +msgid "eq-line-len-arc-len" +msgstr "igual-línea-long-arco-long" + +#: constraint.cpp:49 +msgctxt "constr-name" +msgid "lock-where-dragged" +msgstr "fijación" + +#: constraint.cpp:50 +msgctxt "constr-name" +msgid "comment" +msgstr "comentario" + +#: constraint.cpp:151 +msgid "" +"The point you selected does not belong to the arc. The arc and line segment " +"do not share an end point.\n" +"\n" +"Select the end point of the arc at which you want it to be tangent to the " +"line." +msgstr "" + +#: constraint.cpp:158 +msgid "" +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be " +"tangent to the line." +msgstr "" +"El arco tangente y la línea deben compartir un punto final. Restringirlos " +"con Restringir -> En el punto antes de restringir la tangente.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be " +"tangent to the line." + +#: constraint.cpp:186 +msgid "" +"The point you selected is not an end point of the cubic spline. The spline " +"and line segment do not share an end point.\n" +"\n" +"Select the end point of the spline at which you want it to be tangent to the " +"line." +msgstr "" + +#: constraint.cpp:193 +msgid "" +"The tangent cubic spline and line segment must share an endpoint. Constrain " +"them with Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it " +"to be tangent to the line." +msgstr "" +"La tangente cúbica y la línea deben compartir un punto final. Restringirlos " +"con Restringir -> En el punto antes de restringir la tangente.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it " +"to be tangent to the line." + +#: constraint.cpp:237 +msgid "" +"The points you selected are not end points of the two curves. The curves do " +"not share an end point.\n" +"\n" +"Select the end points of both curves at which you want them to be tangent to " +"each other." +msgstr "" + +#: constraint.cpp:244 +msgid "" +"The curves must share an endpoint. Constrain them with Constrain -> On Point " +"before constraining tangent.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." +msgstr "" +"Las curvas deben compartir un punto final. Restringirlos con Restringir -> " +"En el punto antes de restringir la tangente.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." + +#: constraint.cpp:303 +msgid "" +"Bad selection for distance / diameter constraint. This constraint can apply " +"to:\n" +"\n" +" * two points (distance between points)\n" +" * a line segment (length)\n" +" * two points and a line segment or normal (projected distance)\n" +" * a workplane and a point (minimum distance)\n" +" * a line segment and a point (minimum distance)\n" +" * a plane face and a point (minimum distance)\n" +" * a circle or an arc (diameter)\n" +msgstr "" +"Selección incorrecta para la restricción de distancia / diámetro. Esta " +"restricción puede aplicarse a:\n" +"\n" +" * dos puntos (distancia entre puntos)\n" +" * un segmento de línea (longitud)\n" +" * dos puntos y un segmento de línea o normal (distancia proyectada)\n" +" * un plano de trabajo y un punto (distancia mínima)\n" +" * un segmento de línea y un punto (distancia mínima)\n" +" * una cara plana y un punto (distancia mínima)\n" +" * un círculo o un arco (diámetro)\n" + +#: constraint.cpp:366 +msgid "" +"Bad selection for on point / curve / plane constraint. This constraint can " +"apply to:\n" +"\n" +" * two or more points (points coincident)\n" +" * a point and a workplane (point in plane)\n" +" * a point and a line segment (point on line)\n" +" * a point and a circle or arc (point on curve)\n" +" * a point and one to three plane faces (point on face(s))\n" +msgstr "" +"Selección incorrecta para la restricción de punto / curva / plano. Esta " +"restricción puede aplicarse a:\n" +"\n" +" * dos o más puntos (puntos coincidentes) \n" +" * un punto y un plano de trabajo (punto en el plano) \n" +" * un punto y un segmento de línea (punto en la línea) \n" +" * un punto y un círculo o arco (punto en la curva) \n" +" * un punto y de una a tres caras planas (punto en la(s) cara(s)) \n" + +#: constraint.cpp:427 +msgid "" +"Bad selection for equal length / radius constraint. This constraint can " +"apply to:\n" +"\n" +" * two or more line segments (equal length)\n" +" * two line segments and two points (equal point-line distances)\n" +" * a line segment and two points (equal point-line distances)\n" +" * a line segment, and a point and line segment (point-line distance " +"equals length)\n" +" * two or more circles or arcs (equal radius)\n" +" * a line segment and an arc (line segment length equals arc length)\n" +msgstr "" +"Selección incorrecta para la restricción de igualdad longitud / radio. Esta " +"restricción puede aplicarse a:\n" +"\n" +" * dos o más segmentos de línea (igual longitud)\n" +" * dos segmentos de línea y dos puntos (distancias de línea-punto " +"iguales)\n" +" * un segmento de línea y dos puntos (distancias punto-línea iguales)\n" +" * un segmento de línea, y un punto y un segmento de línea (distancia " +"punto-línea igual a la longitud)\n" +" * dos o más círculos o arcos (igual radio)\n" +" * un segmento de línea y un arco (la longitud del segmento de línea es " +"igual a la longitud del arco)\n" + +#: constraint.cpp:480 +msgid "" +"Bad selection for length ratio constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" +msgstr "" +"Selección incorrecta por restricción de la relación de longitud. Esta " +"restricción puede aplicarse a:\n" +"\n" +" * dos segmentos de línea\n" +" * dos arcos\n" +" * un arco y un segmento de línea\n" + +#: constraint.cpp:515 +msgid "" +"Bad selection for length difference constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" +msgstr "" +"Selección incorrecta para restricción de diferencia de longitud. Esta " +"restricción puede aplicarse a:\n" +"\n" +" * dos segmentos de línea\n" +" * dos arcos\n" +" * un arco y un segmento de línea\n" + +#: constraint.cpp:550 +msgid "" +"Bad selection for at midpoint constraint. This constraint can apply to:\n" +"\n" +" * a line segment and a point (point at midpoint)\n" +" * a line segment and a workplane (line's midpoint on plane)\n" +msgstr "" +"Selección incorrecta para una restricción de punto medio. Esta restricción " +"se puede aplicar a:\n" +"\n" +" * un segmento de línea y un punto (punto en el punto medio)\n" +" * un segmento de línea y un plano de trabajo (punto medio de la línea en " +"el plano)\n" + +#: constraint.cpp:608 +msgid "" +"Bad selection for symmetric constraint. This constraint can apply to:\n" +"\n" +" * two points or a line segment (symmetric about workplane's coordinate " +"axis)\n" +" * line segment, and two points or a line segment (symmetric about line " +"segment)\n" +" * workplane, and two points or a line segment (symmetric about " +"workplane)\n" +msgstr "" +"Selección incorrecta para restricción de simetría. Esta restricción puede " +"aplicarse a:\n" +"\n" +" * dos puntos o un segmento de línea (simétrico con respecto al eje de " +"coordenadas del plano de trabajo)\n" +" * segmento de línea, y dos puntos o un segmento de línea (simétrico con " +"respecto al segmento de línea)\n" +" * plano de trabajo, y dos puntos o un segmento de recta (simétrico sobre " +"plano de trabajo)\n" + +#: constraint.cpp:623 +msgid "" +"A workplane must be active when constraining symmetric without an explicit " +"symmetry plane." +msgstr "" +"Un plano de trabajo debe estar activo al restringirse simétrico sin un plano " +"de simetría explícito." + +#: constraint.cpp:663 +msgid "" +"Activate a workplane (with Sketch -> In Workplane) before applying a " +"horizontal or vertical constraint." +msgstr "" +"Active un plano de trabajo (con Croquis -> En Plano de trabajo) antes de " +"aplicar una restricción horizontal o vertical." + +#: constraint.cpp:679 +msgid "" +"Bad selection for horizontal / vertical constraint. This constraint can " +"apply to:\n" +"\n" +" * two or more points\n" +" * one or more line segments\n" +msgstr "" +"Selección incorrecta por restricción horizontal / vertical. Esta restricción " +"puede aplicarse a:\n" +"\n" +" * dos o más puntos\n" +" * uno o más segmentos de línea\n" + +#: constraint.cpp:697 +msgid "" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" +"\n" +" * two normals\n" +msgstr "" +"Selección incorrecta para la misma restricción de orientación. Esta " +"restricción puede aplicarse a:\n" +"\n" +" * dos normales\n" + +#: constraint.cpp:748 +msgid "Must select an angle constraint." +msgstr "Debe seleccionar una restricción de ángulo." + +#: constraint.cpp:761 +msgid "Must select a constraint with associated label." +msgstr "Debe seleccionar una restricción con etiqueta asociada." + +#: constraint.cpp:784 +msgid "" +"Bad selection for angle constraint. This constraint can apply to:\n" +"\n" +"Angle between:\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +"\n" +"Equal angles:\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" +msgstr "" +"Selección incorrecta por restricción de ángulo. Esta restricción se puede " +"aplicarse a:\n" +"\n" +"Ángulo entre:\n" +" * dos segmentos de línea\n" +" * un segmento de linea y una normal\n" +" * dos normales\n" +"\n" +"Ángulos iguales:\n" +" * cuatro segmentos de línea o normales (ángulo igual entre A,B y C,D)\n" +" * tres segmentos de línea o normales (ángulo igual entre A,B y B,C)\n" + +#: constraint.cpp:872 +msgid "Curve-curve tangency must apply in workplane." +msgstr "La tangencia curva-curva debe aplicarse en el plano de trabajo." + +#: constraint.cpp:887 +msgid "" +"Bad selection for parallel / tangent constraint. This constraint can apply " +"to:\n" +"\n" +" * two faces\n" +" * two or more line segments (parallel)\n" +" * one or more line segments and one or more normals (parallel)\n" +" * two or more normals (parallel)\n" +" * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +" * two line segments, arcs, or beziers, that do not share an endpoint and " +"the end point(s) of the curve(s) (tangent)\n" +msgstr "" +"Mala selección para restricción de paralelo / tangente. Esta restricción " +"puede aplicarse a:\n" +"\n" +" * dos caras\n" +" * dos o más segmentos de línea (paralelos)\n" +" * uno o más segmentos de línea y una o más normales (paralelas)\n" +" * dos o más normales (paralelas)\n" +" * dos segmentos de línea, arcos, o beziers, que comparten un punto final " +"(tangente)\n" +" * dos segmentos de línea, arcos, o beziers, que no comparten un punto " +"final y el punto(s) final(es) de la(s) curva(s)\n" + +#: constraint.cpp:914 +msgid "" +"Bad selection for perpendicular constraint. This constraint can apply to:\n" +"\n" +" * two faces\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"Selección incorrecta por restricción perpendicular. Esta restricción se " +"puede aplicarse a:\n" +"\n" +" * dos caras\n" +" * dos segmentos de línea\n" +" * un segmento de línea y una normal\n" +" * dos normales\n" + +#: constraint.cpp:931 +msgid "" +"Bad selection for lock point where dragged constraint. This constraint can " +"apply to:\n" +"\n" +" * a point\n" +msgstr "" +"Selección incorrecta para el punto de bloqueo donde se arrastró la " +"restricción. Esta restricción puede aplicarse a:\n" +"\n" +" * un punto\n" + +#: constraint.cpp:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "NUEVO COMENTARIO -- DOBLE-CLIC PARA EDITAR" + +#: constraint.cpp:952 +msgid "click center of comment text" +msgstr "clic en el centro del texto del comentario" + +#: export.cpp:19 +msgid "" +"No solid model present; draw one with extrudes and revolves, or use Export " +"2d View to export bare lines and curves." +msgstr "" +"No hay un modelo sólido presente; dibuje uno con extrusiones y revoluciones, " +"o use Exportar Vista 2d para exportar líneas y curvas simples." + +#: export.cpp:61 +msgid "" +"Bad selection for export section. Please select:\n" +"\n" +" * nothing, with an active workplane (workplane is section plane)\n" +" * a face (section plane through face)\n" +" * a point and two line segments (plane through point and parallel to " +"lines)\n" +msgstr "" +"Selección incorrecta para la sección de exportación. Por favor seleccione:\n" +"\n" +" * nada, con un plano de trabajo activo (el plano de trabajo es un plano " +"de sección)\n" +" * una cara (plano de sección a través de la cara)\n" +" * un punto y dos segmentos de línea (plano que pasa por el punto y " +"paralelo a las líneas)\n" + +#: export.cpp:818 +msgid "Active group mesh is empty; nothing to export." +msgstr "La malla del grupo activo está vacía; nada para exportar." + +#: exportvector.cpp:336 +msgid "freehand lines were replaced with continuous lines" +msgstr "las líneas a mano alzada fueron reemplazadas por líneas continuas" + +#: exportvector.cpp:338 +msgid "zigzag lines were replaced with continuous lines" +msgstr "las líneas en zigzag fueron reemplazadas por líneas continuas" + +#: exportvector.cpp:592 +msgid "" +"Some aspects of the drawing have no DXF equivalent and were not exported:\n" +msgstr "" +"Algunos aspectos del dibujo no tienen equivalente DXF y no se exportaron:\n" + +#: exportvector.cpp:838 +msgid "" +"PDF page size exceeds 200 by 200 inches; many viewers may reject this file." +msgstr "" +"El tamaño de la página PDF supera los 5080mm x 5080mm; muchos usuarios " +"pueden rechazar este archivo." + +#: file.cpp:44 group.cpp:91 +msgctxt "group-name" +msgid "sketch-in-plane" +msgstr "croquis-en-plano" + +#: file.cpp:62 +msgctxt "group-name" +msgid "#references" +msgstr "#referencias" + +#: file.cpp:555 +msgid "The file is empty. It may be corrupt." +msgstr "El archivo esta vacío. Puede estar corrupto." + +#: file.cpp:560 +msgid "" +"Unrecognized data in file. This file may be corrupt, or from a newer version " +"of the program." +msgstr "" +"Datos no reconocidos en el archivo. Este archivo puede estar dañado o ser de " +"una versión más reciente del programa." + +#: file.cpp:876 +msgctxt "title" +msgid "Missing File" +msgstr "Archivo perdido" + +#: file.cpp:877 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "El archivo vinculado “%s” no esta presente." + +#: file.cpp:879 +msgctxt "dialog" +msgid "" +"Do you want to locate it manually?\n" +"\n" +"If you decline, any geometry that depends on the missing file will be " +"permanently removed." +msgstr "" +"¿Querés localizarlo manualmente?\n" +"\n" +"Si lo rechaza, cualquier geometría que dependa del archivo faltante se " +"eliminará permanentemente." + +#: file.cpp:882 +msgctxt "button" +msgid "&Yes" +msgstr "&Si" + +#: file.cpp:884 +msgctxt "button" +msgid "&No" +msgstr "&No" + +#: file.cpp:886 solvespace.cpp:652 +msgctxt "button" +msgid "&Cancel" +msgstr "&Cancelar" + +#: graphicswin.cpp:41 +msgid "&File" +msgstr "&Archivo" + +#: graphicswin.cpp:42 +msgid "&New" +msgstr "&Nuevo" + +#: graphicswin.cpp:43 +msgid "&Open..." +msgstr "&Abrir..." + +#: graphicswin.cpp:44 +msgid "Open &Recent" +msgstr "Abrir &Reciente" + +#: graphicswin.cpp:45 +msgid "&Save" +msgstr "&Guardar" + +#: graphicswin.cpp:46 +msgid "Save &As..." +msgstr "Guardar &Como..." + +#: graphicswin.cpp:48 +msgid "Export &Image..." +msgstr "Exportar &Imagen..." + +#: graphicswin.cpp:49 +msgid "Export 2d &View..." +msgstr "Exportar &Vista 2d..." + +#: graphicswin.cpp:50 +msgid "Export 2d &Section..." +msgstr "Exportar &Corte 2d..." + +#: graphicswin.cpp:51 +msgid "Export 3d &Wireframe..." +msgstr "Exportar &Estructura alámbrica 3d..." + +#: graphicswin.cpp:52 +msgid "Export Triangle &Mesh..." +msgstr "Exportar &Malla Triangular..." + +#: graphicswin.cpp:53 +msgid "Export &Surfaces..." +msgstr "Exportar &Superficies..." + +#: graphicswin.cpp:54 +msgid "Im&port..." +msgstr "Im&portar..." + +#: graphicswin.cpp:57 +msgid "E&xit" +msgstr "S&alir" + +#: graphicswin.cpp:60 +msgid "&Edit" +msgstr "&Editar" + +#: graphicswin.cpp:61 +msgid "&Undo" +msgstr "&Deshacer" + +#: graphicswin.cpp:62 +msgid "&Redo" +msgstr "&Rehacer" + +#: graphicswin.cpp:63 +msgid "Re&generate All" +msgstr "Re&generar Todo" + +#: graphicswin.cpp:65 +msgid "Snap Selection to &Grid" +msgstr "Selección Enganche a &Cuadrícula" + +#: graphicswin.cpp:66 +msgid "Rotate Imported &90°" +msgstr "Rotar Importado a &90°" + +#: graphicswin.cpp:68 +msgid "Cu&t" +msgstr "Co&rtar" + +#: graphicswin.cpp:69 +msgid "&Copy" +msgstr "&Copiar" + +#: graphicswin.cpp:70 +msgid "&Paste" +msgstr "&Pegar" + +#: graphicswin.cpp:71 +msgid "Paste &Transformed..." +msgstr "Pegar &Transformado..." + +#: graphicswin.cpp:72 +msgid "&Delete" +msgstr "&Eliminar" + +#: graphicswin.cpp:74 +msgid "Select &Edge Chain" +msgstr "Seleccionar &Cadena de Aristas" + +#: graphicswin.cpp:75 +msgid "Select &All" +msgstr "Seleccionar &Todo" + +#: graphicswin.cpp:76 +msgid "&Unselect All" +msgstr "&Deseleccionar Todo" + +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "Estilos de &Línea..." + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "&Ver Proyección..." + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "Con&figuración..." + +#: graphicswin.cpp:84 +msgid "&View" +msgstr "&Vista" + +#: graphicswin.cpp:85 +msgid "Zoom &In" +msgstr "Acer&car" + +#: graphicswin.cpp:86 +msgid "Zoom &Out" +msgstr "Ale&jar" + +#: graphicswin.cpp:87 +msgid "Zoom To &Fit" +msgstr "Ajustar a &Pantalla" + +#: graphicswin.cpp:89 +msgid "Align View to &Workplane" +msgstr "Alinear Vista a &Plano de trabajo" + +#: graphicswin.cpp:90 +msgid "Nearest &Ortho View" +msgstr "Vista &Ortogonal más cercana" + +#: graphicswin.cpp:91 +msgid "Nearest &Isometric View" +msgstr "Vista &Isométrica más cercana" + +#: graphicswin.cpp:92 +msgid "&Center View At Point" +msgstr "&Vista Central en Punto" + +#: graphicswin.cpp:94 +msgid "Show Snap &Grid" +msgstr "Mostrar Enganches &Cuadrícula" + +#: graphicswin.cpp:95 +msgid "Darken Inactive Solids" +msgstr "Oscurecer Sólidos Inactivos" + +#: graphicswin.cpp:96 +msgid "Use &Perspective Projection" +msgstr "Usar &Proyección Perspectiva" + +#: graphicswin.cpp:97 +msgid "Show E&xploded View" +msgstr "Mostrar Vista E&xplotada" + +#: graphicswin.cpp:98 +msgid "Dimension &Units" +msgstr "&Unidades de Cota" + +#: graphicswin.cpp:99 +msgid "Dimensions in &Millimeters" +msgstr "Cotas en &Milímetros" + +#: graphicswin.cpp:100 +msgid "Dimensions in M&eters" +msgstr "Cotas en M&etros" + +#: graphicswin.cpp:101 +msgid "Dimensions in &Inches" +msgstr "Cotas en &Pulgadas" + +#: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "Cotas en &Pies y Pulgadas" + +#: graphicswin.cpp:104 +msgid "Show &Toolbar" +msgstr "Mostrar &Barra de herramientas" + +#: graphicswin.cpp:105 +msgid "Show Property Bro&wser" +msgstr "Mostrar Navegador de Pro&piedades" + +#: graphicswin.cpp:107 +msgid "&Full Screen" +msgstr "&Pantalla Completa" + +#: graphicswin.cpp:109 +msgid "&New Group" +msgstr "&Nuevo Grupo" + +#: graphicswin.cpp:110 +msgid "Sketch In &3d" +msgstr "Croquis En &3d" + +#: graphicswin.cpp:111 +msgid "Sketch In New &Workplane" +msgstr "Croquis En Nuevo &Plano de trabajo" + +#: graphicswin.cpp:113 +msgid "Step &Translating" +msgstr "Paso &Traslación" + +#: graphicswin.cpp:114 +msgid "Step &Rotating" +msgstr "Paso &Rotación" + +#: graphicswin.cpp:116 +msgid "E&xtrude" +msgstr "E&xtrusión" + +#: graphicswin.cpp:117 +msgid "&Helix" +msgstr "&Hélice" + +#: graphicswin.cpp:118 +msgid "&Lathe" +msgstr "&Torno" + +#: graphicswin.cpp:119 +msgid "Re&volve" +msgstr "Re&volución" + +#: graphicswin.cpp:121 +msgid "Link / Assemble..." +msgstr "Enlace / Ensamblar..." + +#: graphicswin.cpp:122 +msgid "Link Recent" +msgstr "Enlace Reciente" + +#: graphicswin.cpp:124 +msgid "&Sketch" +msgstr "&Croquis" + +#: graphicswin.cpp:125 +msgid "In &Workplane" +msgstr "En &Plano de trabajo" + +#: graphicswin.cpp:126 +msgid "Anywhere In &3d" +msgstr "En cualquier lugar en &3d" + +#: graphicswin.cpp:128 +msgid "Datum &Point" +msgstr "&Punto de Referencia" + +#: graphicswin.cpp:129 +msgid "Wor&kplane" +msgstr "&Plano de trabajo" + +#: graphicswin.cpp:131 +msgid "Line &Segment" +msgstr "&Segmento de Línea" + +#: graphicswin.cpp:132 +msgid "C&onstruction Line Segment" +msgstr "S&egmento de Línea de Construcción" + +#: graphicswin.cpp:133 +msgid "&Rectangle" +msgstr "&Rectángulo" + +#: graphicswin.cpp:134 +msgid "&Circle" +msgstr "&Círculo" + +#: graphicswin.cpp:135 +msgid "&Arc of a Circle" +msgstr "&Arco de un Círculo" + +#: graphicswin.cpp:136 +msgid "&Bezier Cubic Spline" +msgstr "Spline Cúbico &Bezier" + +#: graphicswin.cpp:138 +msgid "&Text in TrueType Font" +msgstr "&Texto en Fuente TrueType" + +#: graphicswin.cpp:139 +msgid "I&mage" +msgstr "&Imagen" + +#: graphicswin.cpp:141 +msgid "To&ggle Construction" +msgstr "Al&ternar Construcción" + +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" +msgstr "Tangente &Arco en el Punto" + +#: graphicswin.cpp:143 +msgid "Split Curves at &Intersection" +msgstr "Dividir Curvas en &Intersección" + +#: graphicswin.cpp:145 +msgid "&Constrain" +msgstr "&Restricción" + +#: graphicswin.cpp:146 +msgid "&Distance / Diameter" +msgstr "&Distancia / Diámetro" + +#: graphicswin.cpp:147 +msgid "Re&ference Dimension" +msgstr "Co&ta de Referencia" + +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "Á&ngulo / Igual ángulo" + +#: graphicswin.cpp:149 +msgid "Reference An&gle" +msgstr "Ángulo de Re&ferencia" + +#: graphicswin.cpp:150 +msgid "Other S&upplementary Angle" +msgstr "Otro Á&ngulo Suplementario" + +#: graphicswin.cpp:151 +msgid "Toggle R&eference Dim" +msgstr "Alternar Cota de R&eferencia" + +#: graphicswin.cpp:153 +msgid "&Horizontal" +msgstr "&Horizontal" + +#: graphicswin.cpp:154 +msgid "&Vertical" +msgstr "&Vertical" + +#: graphicswin.cpp:156 +msgid "&On Point / Curve / Plane" +msgstr "&Sobre Punto / Curva / Plano" + +#: graphicswin.cpp:157 +msgid "E&qual Length / Radius" +msgstr "I&gual Longitud / Radio" + +#: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "Relación Longi&tud / Arco" + +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Dif&erencia Longitud / Arco" + +#: graphicswin.cpp:160 +msgid "At &Midpoint" +msgstr "En Punto &Medio" + +#: graphicswin.cpp:161 +msgid "S&ymmetric" +msgstr "S&imetría" + +#: graphicswin.cpp:162 +msgid "Para&llel / Tangent" +msgstr "Para&lela / Tangente" + +#: graphicswin.cpp:163 +msgid "&Perpendicular" +msgstr "&Perpendicular" + +#: graphicswin.cpp:164 +msgid "Same Orient&ation" +msgstr "Misma Orient&ación" + +#: graphicswin.cpp:165 +msgid "Lock Point Where &Dragged" +msgstr "Punto de Bloqueo &Donde se Arrastró" + +#: graphicswin.cpp:167 +msgid "Comment" +msgstr "Comentario" + +#: graphicswin.cpp:169 +msgid "&Analyze" +msgstr "&Analizar" + +#: graphicswin.cpp:170 +msgid "Measure &Volume" +msgstr "Medir &Volumen" + +#: graphicswin.cpp:171 +msgid "Measure A&rea" +msgstr "Medir Á&rea" + +#: graphicswin.cpp:172 +msgid "Measure &Perimeter" +msgstr "Medir &Perímetro" + +#: graphicswin.cpp:173 +msgid "Show &Interfering Parts" +msgstr "Mostrar &Piezas que Interfieren" + +#: graphicswin.cpp:174 +msgid "Show &Naked Edges" +msgstr "Mostrar &Aristas Visibles" + +#: graphicswin.cpp:175 +msgid "Show &Center of Mass" +msgstr "Mostrar &Centro de Masa" + +#: graphicswin.cpp:177 +msgid "Show &Underconstrained Points" +msgstr "Mostrar &Puntos Subrestringidos" + +#: graphicswin.cpp:179 +msgid "&Trace Point" +msgstr "&Punto de Rastro" + +#: graphicswin.cpp:180 +msgid "&Stop Tracing..." +msgstr "Detener ra&streo..." + +#: graphicswin.cpp:181 +msgid "Step &Dimension..." +msgstr "Paso de &Cota..." + +#: graphicswin.cpp:183 +msgid "&Help" +msgstr "&Ayuda" + +#: graphicswin.cpp:184 +msgid "&Language" +msgstr "&Idioma" + +#: graphicswin.cpp:185 +msgid "&Website / Manual" +msgstr "&Sitio Web / Manual" + +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "Ir a &GitHub" + +#: graphicswin.cpp:188 +msgid "&About" +msgstr "&Acerca" + +#: graphicswin.cpp:362 +msgid "(no recent files)" +msgstr "(no hay archivos recientes)" + +#: graphicswin.cpp:370 +#, c-format +msgid "File '%s' does not exist." +msgstr "El archivo '%s' no existe." + +#: graphicswin.cpp:779 +msgid "No workplane is active, so the grid will not appear." +msgstr "" +"No hay ningún plano de trabajo activo, por lo que la cuadrícula no aparecerá." + +#: graphicswin.cpp:794 +msgid "" +"The perspective factor is set to zero, so the view will always be a parallel " +"projection.\n" +"\n" +"For a perspective projection, modify the perspective factor in the " +"configuration screen. A value around 0.3 is typical." +msgstr "" +"El factor de perspectiva se establece en cero, por lo que la vista siempre " +"será una proyección paralela.\n" +"\n" +"Para una proyección en perspectiva, modifique el factor de perspectiva en la " +"pantalla de configuración. Un valor alrededor de 0.3 es típico." + +#: graphicswin.cpp:884 +msgid "" +"Select a point; this point will become the center of the view on screen." +msgstr "" +"Seleccione un punto; este punto se convertirá en el centro de la vista en " +"pantalla." + +#: graphicswin.cpp:1193 +msgid "No additional entities share endpoints with the selected entities." +msgstr "" +"Ninguna entidad adicional comparte puntos finales con las entidades " +"seleccionadas." + +#: graphicswin.cpp:1211 +msgid "" +"To use this command, select a point or other entity from an linked part, or " +"make a link group the active group." +msgstr "" +"Para usar este comando, seleccione un punto u otra entidad de una pieza " +"vinculada, o convertir un grupo de enlaces en el grupo activo." + +#: graphicswin.cpp:1234 +msgid "" +"No workplane is active. Activate a workplane (with Sketch -> In Workplane) " +"to define the plane for the snap grid." +msgstr "" +"No hay ningún plano de trabajo activo. Activar un plano de trabajo (con " +"Croquis -> En plano de trabajo) para definir el plano para el enganche a " +"cuadrícula." + +#: graphicswin.cpp:1241 +msgid "" +"Can't snap these items to grid; select points, text comments, or constraints " +"with a label. To snap a line, select its endpoints." +msgstr "" +"No se pueden enganchar estos elementos a la cuadrícula; seleccionar puntos, " +"comentarios de texto o restricciones con una etiqueta. Para enganchar una " +"línea, seleccione sus puntos finales." + +#: graphicswin.cpp:1326 +msgid "No workplane selected. Activating default workplane for this group." +msgstr "" +"No se seleccionó ningún plano de trabajo. Activando el plano de trabajo " +"predeterminado para este grupo." + +#: graphicswin.cpp:1329 +msgid "" +"No workplane is selected, and the active group does not have a default " +"workplane. Try selecting a workplane, or activating a sketch-in-new-" +"workplane group." +msgstr "" +"No se selecciona ningún plano de trabajo y el grupo activo no tiene un plano " +"de trabajo. Intente seleccionar un plano de trabajo o activar un croquis-en-" +"nuevo-grupo de plano de trabajo." + +#: graphicswin.cpp:1350 +msgid "" +"Bad selection for tangent arc at point. Select a single point, or select " +"nothing to set up arc parameters." +msgstr "" +"Selección incorrecta de arco tangente en el punto. Seleccione un solo punto " +"o no seleccione nada para configurar los parámetros del arco." + +#: graphicswin.cpp:1361 +msgid "click point on arc (draws anti-clockwise)" +msgstr "clic en el punto en el arco (dibuja en sentido antihorario)" + +#: graphicswin.cpp:1362 +msgid "click to place datum point" +msgstr "clic para colocar el punto de referencia" + +#: graphicswin.cpp:1363 +msgid "click first point of line segment" +msgstr "clic en el primer punto del segmento de línea" + +#: graphicswin.cpp:1365 +msgid "click first point of construction line segment" +msgstr "clic en el primer punto del segmento de línea de construcción" + +#: graphicswin.cpp:1366 +msgid "click first point of cubic segment" +msgstr "clic en el primer punto del segmento cúbico" + +#: graphicswin.cpp:1367 +msgid "click center of circle" +msgstr "clic en el centro del círculo" + +#: graphicswin.cpp:1368 +msgid "click origin of workplane" +msgstr "clic en origen del plano de trabajo" + +#: graphicswin.cpp:1369 +msgid "click one corner of rectangle" +msgstr "clic en una esquina del rectángulo" + +#: graphicswin.cpp:1370 +msgid "click top left of text" +msgstr "clic en la parte superior izquierda del texto" + +#: graphicswin.cpp:1376 +msgid "click top left of image" +msgstr "clic en la parte superior izquierda de la imagen" + +#: graphicswin.cpp:1402 +msgid "" +"No entities are selected. Select entities before trying to toggle their " +"construction state." +msgstr "" +"No se seleccionaron entidades. Seleccione entidades antes de intentar " +"alternar su estado de construcción." + +#: group.cpp:86 +msgctxt "group-name" +msgid "sketch-in-3d" +msgstr "croquis-en-3d" + +#: group.cpp:154 +msgid "" +"Bad selection for new sketch in workplane. This group can be created with:\n" +"\n" +" * a point (through the point, orthogonal to coordinate axes)\n" +" * a point and two line segments (through the point, parallel to the " +"lines)\n" +" * a point and a normal (through the point, orthogonal to the normal)\n" +" * a workplane (copy of the workplane)\n" +msgstr "" +"Selección incorrecta para un nuevo croquis en el plano de trabajo. Este " +"grupo se puede crear con:\n" +"\n" +" * un punto (a través del punto, ortogonal a los ejes de coordenadas)\n" +" * un punto y dos segmentos de línea (a través del punto, paralelo a " +"líneas)\n" +" * un punto y una normal (a través del punto, ortogonal a la normal)\n" +" * un plano de trabajo (copia de un plano de trabajo)\n" + +#: group.cpp:170 +msgid "" +"Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " +"will be extruded normal to the workplane." +msgstr "" +"Active un plano de trabajo (Croquis -> En plano de trabajo) antes de " +"extruir. El croquis se extruirá normal al plano de trabajo." + +#: group.cpp:179 +msgctxt "group-name" +msgid "extrude" +msgstr "extrusión" + +#: group.cpp:184 +msgid "Lathe operation can only be applied to planar sketches." +msgstr "La operación de torno solo se puede aplicar a croquis planos." + +#: group.cpp:195 +msgid "" +"Bad selection for new lathe group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"Selección incorrecta para el nuevo grupo de torno. Este grupo se puede crear " +"con:\n" +"\n" +" * un punto y un segmento de línea o normal (revolucionado alrededor de " +"un eje paralelo a la línea / normal, a través del punto)\n" +" * un segmento de línea (revolucionado sobre un segmento de línea)\n" + +#: group.cpp:205 +msgctxt "group-name" +msgid "lathe" +msgstr "torno" + +#: group.cpp:210 +msgid "Revolve operation can only be applied to planar sketches." +msgstr "La operación de revolución solo se puede aplicar a croquis planos." + +#: group.cpp:221 +msgid "" +"Bad selection for new revolve group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"Selección incorrecta para el nuevo grupo de revolución. Este grupo se puede " +"crear con:\n" +"\n" +" * un punto y un segmento de línea o normal (revolucionado alrededor de " +"un eje paralelo a la línea / normal, a través del punto)\n" +" * un segmento de línea (revolucionado sobre un segmento de línea)\n" + +#: group.cpp:233 +msgctxt "group-name" +msgid "revolve" +msgstr "revolución" + +#: group.cpp:238 +msgid "Helix operation can only be applied to planar sketches." +msgstr "La operación de hélice solo se puede aplicar a croquis planos." + +#: group.cpp:249 +msgid "" +"Bad selection for new helix group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"Selección incorrecta para el nuevo grupo de hélice. Este grupo se puede " +"crear con:\n" +"\n" +" * un punto y un segmento de línea o normal (girado alrededor de un eje " +"paralelo a la línea / normal, a través del punto)\n" +" * un segmento de línea (girado sobre un segmento de línea)\n" + +#: group.cpp:261 +msgctxt "group-name" +msgid "helix" +msgstr "hélice" + +#: group.cpp:274 +msgid "" +"Bad selection for new rotation. This group can be created with:\n" +"\n" +" * a point, while locked in workplane (rotate in plane, about that " +"point)\n" +" * a point and a line or a normal (rotate about an axis through the " +"point, and parallel to line / normal)\n" +msgstr "" +"Selección incorrecta para nueva rotación. Este grupo se puede crear con:\n" +"\n" +" * un punto, mientras está bloqueado en el plano de trabajo (girar en el " +"plano, sobre ese punto)\n" +" * un punto y una línea o una normal (rotar alrededor de un eje a través " +"de un punto y paralela a la línea / normal)\n" + +#: group.cpp:287 +msgctxt "group-name" +msgid "rotate" +msgstr "rotación" + +#: group.cpp:298 +msgctxt "group-name" +msgid "translate" +msgstr "traslación" + +#: group.cpp:422 +msgid "(unnamed)" +msgstr "(sin nombre)" + +#: groupmesh.cpp:710 +msgid "not closed contour, or not all same style!" +msgstr "¡Contorno no cerrado, o no todos del mismo estilo!" + +#: groupmesh.cpp:723 +msgid "points not all coplanar!" +msgstr "¡No todos los puntos son coplanares!" + +#: groupmesh.cpp:725 +msgid "contour is self-intersecting!" +msgstr "¡El contorno se intersecta a sí mismo!" + +#: groupmesh.cpp:727 +msgid "zero-length edge!" +msgstr "¡arista de longitud cero!" + +#: importmesh.cpp:136 +msgid "Text-formated STL files are not currently supported" +msgstr "Los archivos STL con formato de texto no son compatibles actualmente" + +#: modify.cpp:252 +msgid "Must be sketching in workplane to create tangent arc." +msgstr "" +"Debe estar dibujando en el plano de trabajo para crear un arco tangente." + +#: modify.cpp:299 +msgid "" +"To create a tangent arc, select a point where two non-construction lines or " +"circles in this group and workplane join." +msgstr "" +"Para crear un arco tangente, seleccione un punto donde dos líneas que no " +"sean de construcción o los círculos de este grupo y el plano de trabajo se " +"unen." + +#: modify.cpp:386 +msgid "" +"Couldn't round this corner. Try a smaller radius, or try creating the " +"desired geometry by hand with tangency constraints." +msgstr "" +"No pude redondear esta esquina. Pruebe con un radio más pequeño o intente " +"crear la geometría deseada a mano con restricciones de tangencia." + +#: modify.cpp:595 +msgid "Couldn't split this entity; lines, circles, or cubics only." +msgstr "No se pudo dividir esta entidad; solo líneas, círculos o cúbicos." + +#: modify.cpp:622 +msgid "Must be sketching in workplane to split." +msgstr "Debe estar dibujando en el plano de trabajo para dividir." + +#: modify.cpp:629 +msgid "" +"Select two entities that intersect each other (e.g. two lines/circles/arcs " +"or a line/circle/arc and a point)." +msgstr "" +"Seleccione dos entidades que se crucen entre sí (por ejemplo, dos líneas/" +"círculos/arcos o una línea/círculo/arco y un punto)." + +#: modify.cpp:734 +msgid "Can't split; no intersection found." +msgstr "No se puede dividir; no se encontró ninguna intersección." + +#: mouse.cpp:558 +msgid "Assign to Style" +msgstr "Asignar a Estilo" + +#: mouse.cpp:574 +msgid "No Style" +msgstr "Sin Estilo" + +#: mouse.cpp:577 +msgid "Newly Created Custom Style..." +msgstr "Estilo Personalizado Recién Creado..." + +#: mouse.cpp:584 +msgid "Group Info" +msgstr "Información de Grupo" + +#: mouse.cpp:604 +msgid "Style Info" +msgstr "Información de Estilo" + +#: mouse.cpp:624 +msgid "Select Edge Chain" +msgstr "Seleccionar Cadena de Arista" + +#: mouse.cpp:630 +msgid "Toggle Reference Dimension" +msgstr "Alternar Cota de Referencia" + +#: mouse.cpp:636 +msgid "Other Supplementary Angle" +msgstr "Otro Ángulo Suplementario" + +#: mouse.cpp:641 +msgid "Snap to Grid" +msgstr "Enganchar a la cuadrícula" + +#: mouse.cpp:650 +msgid "Remove Spline Point" +msgstr "Remover Punto de Spline" + +#: mouse.cpp:685 +msgid "Add Spline Point" +msgstr "Agregar Punto de Spline" + +#: mouse.cpp:689 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "" +"No se puede agregar un punto de spline: se alcanzó el número máximo de " +"puntos." + +#: mouse.cpp:714 +msgid "Toggle Construction" +msgstr "Alternar Construcción" + +#: mouse.cpp:730 +msgid "Delete Point-Coincident Constraint" +msgstr "Eliminar Restricción de Punto-Coincidente" + +#: mouse.cpp:748 +msgid "Cut" +msgstr "Cortar" + +#: mouse.cpp:750 +msgid "Copy" +msgstr "Copiar" + +#: mouse.cpp:754 +msgid "Select All" +msgstr "Seleccionar Todo" + +#: mouse.cpp:759 +msgid "Paste" +msgstr "Pegar" + +#: mouse.cpp:761 +msgid "Paste Transformed..." +msgstr "Pegar Transformado..." + +#: mouse.cpp:766 +msgid "Delete" +msgstr "Eliminar" + +#: mouse.cpp:769 +msgid "Unselect All" +msgstr "Deseleccionar Todo" + +#: mouse.cpp:776 +msgid "Unselect Hovered" +msgstr "Deselección Flotante" + +#: mouse.cpp:785 +msgid "Zoom to Fit" +msgstr "Ajustar a Pantalla" + +#: mouse.cpp:987 mouse.cpp:1276 +msgid "click next point of line, or press Esc" +msgstr "clic en el siguiente punto de la línea o presione Esc" + +#: mouse.cpp:993 +msgid "" +"Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"No se puede dibujar un rectángulo en 3d; primero, active un plano de trabajo " +"con Croquis -> En Plano de trabajo." + +#: mouse.cpp:1027 +msgid "click to place other corner of rectangle" +msgstr "clic para colocar la otra esquina del rectángulo" + +#: mouse.cpp:1048 +msgid "click to set radius" +msgstr "clic para establecer el radio" + +#: mouse.cpp:1053 +msgid "" +"Can't draw arc in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"No se puede dibujar un arco en 3d; primero, active un plano de trabajo con " +"Croquis -> En Plano de trabajo." + +#: mouse.cpp:1072 +msgid "click to place point" +msgstr "clic para colocar el punto" + +#: mouse.cpp:1088 +msgid "click next point of cubic, or press Esc" +msgstr "clic en el siguiente punto del cúbico, o presione Esc" + +#: mouse.cpp:1093 +msgid "" +"Sketching in a workplane already; sketch in 3d before creating new workplane." +msgstr "" +"Croquizando en un plano de trabajo ya; croquis en 3d antes de crear un nuevo " +"plano de trabajo." + +#: mouse.cpp:1109 +msgid "" +"Can't draw text in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"No se puede dibujar texto en 3D; primero, active un plano de trabajo con " +"Croquis -> En Plano de trabajo." + +#: mouse.cpp:1126 +msgid "click to place bottom right of text" +msgstr "clic para colocar la parte inferior derecha del texto" + +#: mouse.cpp:1132 +msgid "" +"Can't draw image in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"No se puede dibujar una imagen en 3D; primero, active un plano de trabajo " +"con Croquis -> En Plano de Trabajo." + +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "Modelos SolveSpace" + +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "TODO" + +#: platform/gui.cpp:91 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "Placa circuito IDF" + +#: platform/gui.cpp:92 +msgctxt "file-type" +msgid "STL triangle mesh" +msgstr "Malla de triángulo STL" + +#: platform/gui.cpp:96 +msgctxt "file-type" +msgid "PNG image" +msgstr "Imagen PNG" + +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "STL mesh" +msgstr "Malla STL" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Malla Wavefront OBJ" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "Malla compatible-Three.js, con visor" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "Malla compatible-Three.js, solo malla" + +#: platform/gui.cpp:104 +msgctxt "file-type" +msgid "VRML text file" +msgstr "Archivo texto VRML" + +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "STEP file" +msgstr "Archivo STEP" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "PDF file" +msgstr "Archivo PDF" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "PostScript Encapsulado" + +#: platform/gui.cpp:114 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "Gráficos Vectoriales Escalables SVG" + +#: platform/gui.cpp:116 platform/gui.cpp:123 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "Archivo DXF (AutoCAD 2007)" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "HPGL file" +msgstr "Archivo HPGL" + +#: platform/gui.cpp:118 +msgctxt "file-type" +msgid "G Code" +msgstr "G Code" + +#: platform/gui.cpp:127 +msgctxt "file-type" +msgid "AutoCAD DXF and DWG files" +msgstr "Archivos AutoCAD DXF y DWG" + +#: platform/gui.cpp:131 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "Valores separados por comas" + +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 +msgid "untitled" +msgstr "sin título" + +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 +msgctxt "title" +msgid "Save File" +msgstr "Guardar Archivo" + +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 +msgctxt "title" +msgid "Open File" +msgstr "Abrir Archivo" + +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 +msgctxt "button" +msgid "_Cancel" +msgstr "_Cancelar" + +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 +msgctxt "button" +msgid "_Save" +msgstr "_Guardar" + +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 +msgctxt "button" +msgid "_Open" +msgstr "_Abrir" + +#: solvespace.cpp:175 +msgctxt "title" +msgid "Autosave Available" +msgstr "Autoguardado Disponible" + +#: solvespace.cpp:176 +msgctxt "dialog" +msgid "An autosave file is available for this sketch." +msgstr "Un archivo de autoguardado está disponible para este croquis." + +#: solvespace.cpp:177 +msgctxt "dialog" +msgid "Do you want to load the autosave file instead?" +msgstr "¿Desea cargar el archivo de autoguardado en su lugar?" + +#: solvespace.cpp:178 +msgctxt "button" +msgid "&Load autosave" +msgstr "&Cargar autoguardado" + +#: solvespace.cpp:180 +msgctxt "button" +msgid "Do&n't Load" +msgstr "&No Cargar" + +#: solvespace.cpp:640 +msgctxt "title" +msgid "Modified File" +msgstr "Archivo Modificado" + +#: solvespace.cpp:642 +#, c-format +msgctxt "dialog" +msgid "Do you want to save the changes you made to the sketch “%s”?" +msgstr "¿Desea guardar los cambios que realizó en el croquis “%s”?" + +#: solvespace.cpp:645 +msgctxt "dialog" +msgid "Do you want to save the changes you made to the new sketch?" +msgstr "¿Desea guardar los cambios que realizó en el nuevo croquis?" + +#: solvespace.cpp:648 +msgctxt "dialog" +msgid "Your changes will be lost if you don't save them." +msgstr "Sus cambios se perderán si no los guarda." + +#: solvespace.cpp:649 +msgctxt "button" +msgid "&Save" +msgstr "&Guardar" + +#: solvespace.cpp:651 +msgctxt "button" +msgid "Do&n't Save" +msgstr "&No Guardar" + +#: solvespace.cpp:672 +msgctxt "title" +msgid "(new sketch)" +msgstr "(nuevo croquis)" + +#: solvespace.cpp:683 +msgctxt "title" +msgid "Property Browser" +msgstr "Explorador de Propiedades" + +#: solvespace.cpp:746 +msgid "" +"Constraints are currently shown, and will be exported in the toolpath. This " +"is probably not what you want; hide them by clicking the link at the top of " +"the text window." +msgstr "" +"Las restricciones se muestran actualmente y se exportarán en la trayectoria. " +"Probablemente esto no sea lo que querés; ocultalos haciendo clic en el " +"enlace en la parte superior de la ventana de texto." + +#: solvespace.cpp:834 +#, c-format +msgid "" +"Can't identify file type from file extension of filename '%s'; try .dxf or ." +"dwg." +msgstr "" +"No se puede identificar el tipo de archivo a partir de la extensión del " +"nombre del archivo '%s'; intente .dxf o .dwg." + +#: solvespace.cpp:886 +msgid "Constraint must have a label, and must not be a reference dimension." +msgstr "" +"La restricción debe tener una etiqueta y no debe ser una cota de referencia." + +#: solvespace.cpp:890 +msgid "Bad selection for step dimension; select a constraint." +msgstr "" +"Selección incorrecta para la cota del paso; seleccione una restricción." + +#: solvespace.cpp:914 +msgid "The assembly does not interfere, good." +msgstr "El ensamble no interfiere, bien." + +#: solvespace.cpp:930 +#, c-format +msgid "" +"The volume of the solid model is:\n" +"\n" +" %s" +msgstr "" +"El volumen del modelo sólido es:\n" +"\n" +" %s" + +#: solvespace.cpp:939 +#, c-format +msgid "" +"\n" +"The volume of current group mesh is:\n" +"\n" +" %s" +msgstr "" +"\n" +"El volumen de la malla del grupo actual es:\n" +"\n" +" %s" + +#: solvespace.cpp:944 +msgid "" +"\n" +"\n" +"Curved surfaces have been approximated as triangles.\n" +"This introduces error, typically of around 1%." +msgstr "" +"\n" +"\n" +"Las superficies curvas se han aproximado como triángulos.\n" +"Esto introduce un error, normalmente alrededor del 1%." + +#: solvespace.cpp:959 +#, c-format +msgid "" +"The surface area of the selected faces is:\n" +"\n" +" %s\n" +"\n" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." +msgstr "" +"El área de la superficie de las caras seleccionadas es:\n" +"\n" +" %s\n" +"\n" +"Las curvas se han aproximado como lineales por partes.\n" +"Esto introduce un error, normalmente alrededor del 1%%." + +#: solvespace.cpp:968 +msgid "" +"This group does not contain a correctly-formed 2d closed area. It is open, " +"not coplanar, or self-intersecting." +msgstr "" +"Este grupo no contiene un área cerrada 2d correctamente formada. Está " +"abierta, no coplanares, o auto-intersectantes." + +#: solvespace.cpp:980 +#, c-format +msgid "" +"The area of the region sketched in this group is:\n" +"\n" +" %s\n" +"\n" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." +msgstr "" +"El área de la región croquizada en este grupo es:\n" +"\n" +" %s\n" +"\n" +"Las curvas se han aproximado como lineales por partes.\n" +"Esto introduce un error, normalmente alrededor del 1%%." + +#: solvespace.cpp:1000 +#, c-format +msgid "" +"The total length of the selected entities is:\n" +"\n" +" %s\n" +"\n" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." +msgstr "" +"La longitud total de las entidades seleccionadas es:\n" +"\n" +" %s\n" +"\n" +"Las curvas se han aproximado como lineales por partes.\n" +"Esto introduce un error, normalmente alrededor del 1%%." + +#: solvespace.cpp:1006 +msgid "Bad selection for perimeter; select line segments, arcs, and curves." +msgstr "" +"Selección incorrecta de perímetro; seleccione segmentos de línea, arcos y " +"curvas." + +#: solvespace.cpp:1022 +msgid "Bad selection for trace; select a single point." +msgstr "Selección incorrecta de rastreo; seleccione un solo punto." + +#: solvespace.cpp:1049 +#, c-format +msgid "Couldn't write to '%s'" +msgstr "No pude escribir a '%s'" + +#: solvespace.cpp:1079 +msgid "The mesh is self-intersecting (NOT okay, invalid)." +msgstr "La malla se intersecta a si misma (NO está bien, no es válida)." + +#: solvespace.cpp:1080 +msgid "The mesh is not self-intersecting (okay, valid)." +msgstr "La malla no se intersecta (está bien, es válida)." + +#: solvespace.cpp:1082 +msgid "The mesh has naked edges (NOT okay, invalid)." +msgstr "La malla tiene bordes desnudos (NO está bien, no es válida)." + +#: solvespace.cpp:1083 +msgid "The mesh is watertight (okay, valid)." +msgstr "La malla es estanca (está bien, es válida)." + +#: solvespace.cpp:1086 +#, c-format +msgid "" +"\n" +"\n" +"The model contains %d triangles, from %d surfaces." +msgstr "" +"\n" +"\n" +"El modelo contiene %d triángulos, desde %d superficies." + +#: solvespace.cpp:1090 +#, c-format +msgid "" +"%s\n" +"\n" +"%s\n" +"\n" +"Zero problematic edges, good.%s" +msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"Cero aristas problemáticas, bien.%s" + +#: solvespace.cpp:1093 +#, c-format +msgid "" +"%s\n" +"\n" +"%s\n" +"\n" +"%d problematic edges, bad.%s" +msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"%d aristas problemáticas, mal.%s" + +#: solvespace.cpp:1106 +#, c-format +msgid "" +"This is SolveSpace version %s.\n" +"\n" +"For more information, see http://solvespace.com/\n" +"\n" +"SolveSpace is free software: you are free to modify\n" +"and/or redistribute it under the terms of the GNU\n" +"General Public License (GPL) version 3 or later.\n" +"\n" +"There is NO WARRANTY, to the extent permitted by\n" +"law. For details, visit http://gnu.org/licenses/\n" +"\n" +"© 2008-%d Jonathan Westhues and other authors.\n" +msgstr "" +"Esto es SolveSpace versión %s.\n" +"\n" +"Para más información, ver http://solvespace.com/\n" +"\n" +"SolveSpace es software libre: sos libre de modificarlo\n" +"y/o redistribuirlo bajo los términos de la GNU\n" +"Licencia Pública General (GPL) versión 3 o posterior.\n" +"\n" +"NO HAY GARANTÍA, en la medida permitida por\n" +"ley. Para detalles, visitá http://gnu.org/licenses/\n" +"\n" +"© 2008-%d Jonathan Westhues y otros autores.\n" + +#: style.cpp:185 +msgid "" +"Can't assign style to an entity that's derived from another entity; try " +"assigning a style to this entity's parent." +msgstr "" +"No se puede asignar estilo a una entidad derivada de otra entidad;; intenta " +"asignar un estilo al padre de esta entidad." + +#: style.cpp:735 +msgid "Style name cannot be empty" +msgstr "El nombre del estilo no puede estar vacío" + +#: textscreens.cpp:837 +msgid "Can't repeat fewer than 1 time." +msgstr "No se puede repetir menos de 1 vez." + +#: textscreens.cpp:841 +msgid "Can't repeat more than 999 times." +msgstr "No se puede repetir más de 999 veces." + +#: textscreens.cpp:866 +msgid "Group name cannot be empty" +msgstr "El nombre del grupo no puede estar vacío" + +#: textscreens.cpp:918 +msgid "Opacity must be between zero and one." +msgstr "La opacidad debe estar entre cero y uno." + +#: textscreens.cpp:953 +msgid "Radius cannot be zero or negative." +msgstr "El radio no puede ser cero o negativo." + +#: toolbar.cpp:18 +msgid "Sketch line segment" +msgstr "Croquizar segmento de línea" + +#: toolbar.cpp:20 +msgid "Sketch rectangle" +msgstr "Croquizar rectángulo" + +#: toolbar.cpp:22 +msgid "Sketch circle" +msgstr "Croquizar círculo" + +#: toolbar.cpp:24 +msgid "Sketch arc of a circle" +msgstr "Croquizar arco de un círculo" + +#: toolbar.cpp:26 +msgid "Sketch curves from text in a TrueType font" +msgstr "Croquizar curvas desde texto en fuente TrueType" + +#: toolbar.cpp:28 +msgid "Sketch image from a file" +msgstr "Croquizar imagen desde un archivo" + +#: toolbar.cpp:30 +msgid "Create tangent arc at selected point" +msgstr "Crear arco tangente en el punto seleccionado" + +#: toolbar.cpp:32 +msgid "Sketch cubic Bezier spline" +msgstr "Croquizar spline de Bezier cúbico" + +#: toolbar.cpp:34 +msgid "Sketch datum point" +msgstr "Croquizar punto de referencia" + +#: toolbar.cpp:36 +msgid "Toggle construction" +msgstr "Alternar construcción" + +#: toolbar.cpp:38 +msgid "Split lines / curves where they intersect" +msgstr "Dividir líneas / curvas donde se intersectan" + +#: toolbar.cpp:42 +msgid "Constrain distance / diameter / length" +msgstr "Restringir distancia / diámetro / longitud" + +#: toolbar.cpp:44 +msgid "Constrain angle" +msgstr "Restringir ángulo" + +#: toolbar.cpp:46 +msgid "Constrain to be horizontal" +msgstr "Restringir para ser horizontal" + +#: toolbar.cpp:48 +msgid "Constrain to be vertical" +msgstr "Restringir para ser vertical" + +#: toolbar.cpp:50 +msgid "Constrain to be parallel or tangent" +msgstr "Restringir para ser paralela o tangente" + +#: toolbar.cpp:52 +msgid "Constrain to be perpendicular" +msgstr "Restringir para ser perpendicular" + +#: toolbar.cpp:54 +msgid "Constrain point on line / curve / plane / point" +msgstr "Restringir punto sobre línea / curva / plano / punto" + +#: toolbar.cpp:56 +msgid "Constrain symmetric" +msgstr "Restringir simetría" + +#: toolbar.cpp:58 +msgid "Constrain equal length / radius / angle" +msgstr "Restringir igual longitud / radio / ángulo" + +#: toolbar.cpp:60 +msgid "Constrain normals in same orientation" +msgstr "Restringir normales en misma orientación" + +#: toolbar.cpp:62 +msgid "Other supplementary angle" +msgstr "Otro ángulo suplementario" + +#: toolbar.cpp:64 +msgid "Toggle reference dimension" +msgstr "Alternar cota de referencia" + +#: toolbar.cpp:68 +msgid "New group extruding active sketch" +msgstr "Nuevo croquis activo de grupo de extrusión" + +#: toolbar.cpp:70 +msgid "New group rotating active sketch" +msgstr "Nuevo croquis activo de grupo de rotación" + +#: toolbar.cpp:72 +msgid "New group helix from active sketch" +msgstr "Nuevo croquis activo de grupo de hélice" + +#: toolbar.cpp:74 +msgid "New group revolve active sketch" +msgstr "Nuevo croquis activo de grupo de revolución" + +#: toolbar.cpp:76 +msgid "New group step and repeat rotating" +msgstr "Nuevo paso de grupo y rotación repetida" + +#: toolbar.cpp:78 +msgid "New group step and repeat translating" +msgstr "Nuevo paso de grupo y traslación repetida" + +#: toolbar.cpp:80 +msgid "New group in new workplane (thru given entities)" +msgstr "Nuevo grupo en nuevo plano de trabajo (a través de entidades dadas)" + +#: toolbar.cpp:82 +msgid "New group in 3d" +msgstr "Nuevo grupo en 3d" + +#: toolbar.cpp:84 +msgid "New group linking / assembling file" +msgstr "Nuevo enlace de grupo / archivo de ensamble" + +#: toolbar.cpp:88 +msgid "Nearest isometric view" +msgstr "Vista isométrica más cercana" + +#: toolbar.cpp:90 +msgid "Align view to active workplane" +msgstr "Alinear vista al plano de trabajo activo" + +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "Error" + +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "Mensaje" + +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "&Aceptar" + +#: view.cpp:127 +msgid "Scale cannot be zero or negative." +msgstr "La escala no debe ser cero o negativa." + +#: view.cpp:139 view.cpp:148 +msgid "Bad format: specify x, y, z" +msgstr "Formato incorrecto: especifica x, y, z" diff --git a/res/locales/fr_FR.po b/res/locales/fr_FR.po index 50c302012..4724dc01e 100644 --- a/res/locales/fr_FR.po +++ b/res/locales/fr_FR.po @@ -5,49 +5,49 @@ msgid "" msgstr "" "Project-Id-Version: SolveSpace 3.0\n" -"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" -"POT-Creation-Date: 2021-02-01 15:45+0200\n" -"PO-Revision-Date: 2018-07-14 06:12+0000\n" +"Report-Msgid-Bugs-To: phkahler@gmail.com\n" +"POT-Creation-Date: 2025-01-26 21:04+0200\n" +"PO-Revision-Date: 2025-01-26 21:48+0100\n" "Last-Translator: whitequark \n" "Language-Team: none\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Zanata 4.4.5\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Poedit 3.2.2\n" -#: clipboard.cpp:310 +#: clipboard.cpp:314 msgid "" "Cut, paste, and copy work only in a workplane.\n" "\n" "Activate one with Sketch -> In Workplane." msgstr "" -"Couper, coller et copier uniquement dans un plan de travail.\n" +"Couper, coller et copier fonctionnent uniquement dans un plan de travail.\n" "\n" -"Activez un plan avec \"Dessin -> Dans plan de travail\"." +"Activez un plan avec « Dessin -> Dans le plan de travail »." -#: clipboard.cpp:327 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." -msgstr "Presse papier vide; rien à coller." +msgstr "Presse papier vide ; rien à coller." -#: clipboard.cpp:374 +#: clipboard.cpp:378 msgid "Number of copies to paste must be at least one." msgstr "Le nombre de copies à coller doit être d'au moins un." -#: clipboard.cpp:390 textscreens.cpp:783 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "L'échelle ne peut pas être zéro." -#: clipboard.cpp:432 +#: clipboard.cpp:436 msgid "Select one point to define origin of rotation." msgstr "Sélectionnez un point pour définir l'origine de la rotation." -#: clipboard.cpp:444 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." msgstr "Sélectionnez deux points pour définir le vecteur de translation." -#: clipboard.cpp:454 +#: clipboard.cpp:458 msgid "" "Transformation is identity. So all copies will be exactly on top of each " "other." @@ -55,52 +55,52 @@ msgstr "" "Transformation identique. Donc, toutes les copies seront exactement les unes " "au-dessus des autres." -#: clipboard.cpp:458 +#: clipboard.cpp:462 msgid "Too many items to paste; split this into smaller pastes." -msgstr "Trop d'éléments à coller; Divisez-les en plus petits groupes." +msgstr "Trop d'éléments à coller ; divisez-les en plus petits groupes." -#: clipboard.cpp:463 +#: clipboard.cpp:467 msgid "No workplane active." msgstr "Pas d'espace de travail actif." -#: confscreen.cpp:418 +#: confscreen.cpp:410 msgid "Bad format: specify coordinates as x, y, z" -msgstr "Mauvais format: spécifiez les coordonnées comme x, y, z" +msgstr "Mauvais format : spécifiez les coordonnées comme x, y, z" -#: confscreen.cpp:428 style.cpp:659 textscreens.cpp:805 +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 msgid "Bad format: specify color as r, g, b" -msgstr "Mauvais format; spécifiez la couleur comme r, v, b" +msgstr "Mauvais format ; spécifiez la couleur comme r, v, b" -#: confscreen.cpp:454 +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." msgstr "" "Le facteur de perspective n'aura aucun effet tant que vous n'aurez pas " -"activé \"Affichage -> Utiliser la projection de perspective\"." +"activé « Affichage -> Utiliser la Vue en perspective »." -#: confscreen.cpp:467 confscreen.cpp:477 +#: confscreen.cpp:464 confscreen.cpp:474 #, c-format msgid "Specify between 0 and %d digits after the decimal." -msgstr "" +msgstr "Spécifier entre 0 et %d chiffres après la virgule." -#: confscreen.cpp:489 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" -msgstr "L'échelle d'export ne doit pas être zéro!" +msgstr "L'échelle d'export ne doit pas être zéro !" -#: confscreen.cpp:501 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" -msgstr "Le décalage du rayon de coupe ne doit pas être négatif!" +msgstr "Le décalage du rayon de coupe ne doit pas être négatif !" -#: confscreen.cpp:555 +#: confscreen.cpp:557 msgid "Bad value: autosave interval should be positive" msgstr "" -"Mauvaise valeur: l'intervalle d'enregistrement automatique devrait être " +"Mauvaise valeur : l'intervalle d'enregistrement automatique devrait être " "positif" -#: confscreen.cpp:558 +#: confscreen.cpp:560 msgid "Bad format: specify interval in integral minutes" -msgstr "Mauvais format: spécifiez un nombre entier de minutes" +msgstr "Mauvais format : spécifiez un nombre entier de minutes" #: constraint.cpp:12 msgctxt "constr-name" @@ -169,141 +169,221 @@ msgstr "longueur-ratio" #: constraint.cpp:25 msgctxt "constr-name" +msgid "arc-arc-length-ratio" +msgstr "arc-arc-longueur-ratio" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "arc-line-length-ratio" +msgstr "arc-ligne-longueur-ratio" + +#: constraint.cpp:27 +msgctxt "constr-name" msgid "length-difference" msgstr "longueur-difference" -#: constraint.cpp:26 +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "arc-arc-len-difference" +msgstr "arc-arc-longueur-différence" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "arc-line-len-difference" +msgstr "arc-ligne-longueur-différence" + +#: constraint.cpp:30 msgctxt "constr-name" msgid "symmetric" msgstr "symétrique" -#: constraint.cpp:27 +#: constraint.cpp:31 msgctxt "constr-name" msgid "symmetric-h" msgstr "symétrique-h" -#: constraint.cpp:28 +#: constraint.cpp:32 msgctxt "constr-name" msgid "symmetric-v" msgstr "symétrique-v" -#: constraint.cpp:29 +#: constraint.cpp:33 msgctxt "constr-name" msgid "symmetric-line" msgstr "symétrique-ligne" -#: constraint.cpp:30 +#: constraint.cpp:34 msgctxt "constr-name" msgid "at-midpoint" msgstr "au-point-milieu" -#: constraint.cpp:31 +#: constraint.cpp:35 msgctxt "constr-name" msgid "horizontal" msgstr "horizontal" -#: constraint.cpp:32 +#: constraint.cpp:36 msgctxt "constr-name" msgid "vertical" msgstr "vertical" -#: constraint.cpp:33 +#: constraint.cpp:37 msgctxt "constr-name" msgid "diameter" msgstr "diamètre" -#: constraint.cpp:34 +#: constraint.cpp:38 msgctxt "constr-name" msgid "pt-on-circle" msgstr "pt-sur-cercle" -#: constraint.cpp:35 +#: constraint.cpp:39 msgctxt "constr-name" msgid "same-orientation" msgstr "même-orientation" -#: constraint.cpp:36 +#: constraint.cpp:40 msgctxt "constr-name" msgid "angle" msgstr "angle" -#: constraint.cpp:37 +#: constraint.cpp:41 msgctxt "constr-name" msgid "parallel" msgstr "parallèle" -#: constraint.cpp:38 +#: constraint.cpp:42 msgctxt "constr-name" msgid "arc-line-tangent" msgstr "arc-ligne-tangente" -#: constraint.cpp:39 +#: constraint.cpp:43 msgctxt "constr-name" msgid "cubic-line-tangent" msgstr "cubique-ligne-tangente" -#: constraint.cpp:40 +#: constraint.cpp:44 msgctxt "constr-name" msgid "curve-curve-tangent" msgstr "courbe-courbe-tangente" -#: constraint.cpp:41 +#: constraint.cpp:45 msgctxt "constr-name" msgid "perpendicular" msgstr "perpendiculaire" -#: constraint.cpp:42 +#: constraint.cpp:46 msgctxt "constr-name" msgid "eq-radius" msgstr "eg-rayon" -#: constraint.cpp:43 +#: constraint.cpp:47 msgctxt "constr-name" msgid "eq-angle" msgstr "eg-angle" -#: constraint.cpp:44 +#: constraint.cpp:48 msgctxt "constr-name" msgid "eq-line-len-arc-len" msgstr "eg-ligne-long-arc-long" -#: constraint.cpp:45 +#: constraint.cpp:49 msgctxt "constr-name" msgid "lock-where-dragged" msgstr "verrouillé-où-déplacé" -#: constraint.cpp:46 +#: constraint.cpp:50 msgctxt "constr-name" msgid "comment" msgstr "commentaire" -#: constraint.cpp:140 +#: constraint.cpp:151 msgid "" -"The tangent arc and line segment must share an endpoint. Constrain them with " -"Constrain -> On Point before constraining tangent." +"The point you selected does not belong to the arc. The arc and line segment " +"do not share an end point.\n" +"\n" +"Select the end point of the arc at which you want it to be tangent to the " +"line." msgstr "" -"L'arc tangent et le segment de ligne doivent partager un point final. " -"Contraignez-les avec \"Contrainte -> Sur point avant de contraindre la " -"tangente\"." +"Le point sélectionné n'appartient pas à l'arc. L'arc et le segment de ligne " +"n'ont pas d'extrémité en commun.\n" +"\n" +"Sélectionnez une extrémité de l'arc que vous voulez être tangent à la ligne." #: constraint.cpp:158 msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them " -"with Constrain -> On Point before constraining tangent." +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be " +"tangent to the line." msgstr "" -"La tangente cubique et le segment de ligne doivent partager un point final. " -"Contraignez-les avec \"Contrainte -> Sur point avant de contraindre la " -"tangente\"." +"L'arc tangent et le segment de ligne doivent partager une extrémité. " +"Contraignez-les avec « Contraintes -> Sur Point » avant de contraindre la " +"tangente.\n" +"\n" +"Alternativement, sélectionnez l'extrémité de l'arc que vous voulez " +"tangent à la ligne." -#: constraint.cpp:183 +#: constraint.cpp:186 +msgid "" +"The point you selected is not an end point of the cubic spline. The spline " +"and line segment do not share an end point.\n" +"\n" +"Select the end point of the spline at which you want it to be tangent to the " +"line." +msgstr "" +"Le point sélectionné n'est pas une extrémité de la spline cubique. La spline " +"et le segment de ligne n'ont pas d'extrémité en commun.\n" +"\n" +"Sélectionnez l'extrémité de la spline que vous voulez être tangente à la " +"ligne." + +#: constraint.cpp:193 +msgid "" +"The tangent cubic spline and line segment must share an endpoint. Constrain " +"them with Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it " +"to be tangent to the line." +msgstr "" +"La tangente cubique et le segment de ligne doivent partager une extrémité. " +"Contraignez-les avec « Contraintes -> Sur Point » avant de contraindre la " +"tangente.\n" +"\n" +"Alternativement, sélectionnez l'extrémité de la spline cubique où vous la " +"voulez tangent à la ligne." + +#: constraint.cpp:237 +msgid "" +"The points you selected are not end points of the two curves. The curves do " +"not share an end point.\n" +"\n" +"Select the end points of both curves at which you want them to be tangent to " +"each other." +msgstr "" +"Les points sélectionnés ne sont pas les extrémités des des courbes. Les " +"courbes n'ont pas d'extrémité en commun.\n" +"\n" +"Sélectionnez les extrémités des deux courbes que vous voulez tangentes." + +#: constraint.cpp:244 msgid "" "The curves must share an endpoint. Constrain them with Constrain -> On Point " -"before constraining tangent." +"before constraining tangent.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." msgstr "" -"Les courbes doivent partager un point final. Contraignez-les avec " -"\"Contrainte -> Sur point avant de contraindre la tangente\"." +"Les courbes doivent partager une extrémité. Contraignez-les avec " +"« Contraintes -> Sur Point » avant de contraindre la tangente.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." +"Alternativement, sélectionnez les extrémités des deux courbes là où vous " +"les voulez tangentes." -#: constraint.cpp:231 +#: constraint.cpp:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply " "to:\n" @@ -317,91 +397,93 @@ msgid "" " * a circle or an arc (diameter)\n" msgstr "" "Mauvaise sélection pour la contrainte distance / diamètre. Cette contrainte " -"peut s'appliquer à:\n" +"peut s'appliquer à :\n" "\n" -"    * Deux points (distance entre points)\n" -"    * Un segment de ligne (longueur)\n" -"    * Deux points et un segment de ligne ou normal (distance projetée)\n" -"    * Un plan de travail et un point (distance minimale)\n" -"    * Un segment de ligne et un point (distance minimale)\n" -"    * Une face plane et un point (distance minimale)\n" -"    * Un cercle ou un arc (diamètre)\n" +" – Deux points (distance entre points)\n" +" – Un segment de ligne (longueur)\n" +" – Deux points et un segment de ligne ou normal (distance projetée)\n" +" – Un plan de travail et un point (distance minimale)\n" +" – Un segment de ligne et un point (distance minimale)\n" +" – Une face plane et un point (distance minimale)\n" +" – Un cercle ou un arc (diamètre)\n" -#: constraint.cpp:284 +#: constraint.cpp:366 msgid "" "Bad selection for on point / curve / plane constraint. This constraint can " "apply to:\n" "\n" -" * two points (points coincident)\n" +" * two or more points (points coincident)\n" " * a point and a workplane (point in plane)\n" " * a point and a line segment (point on line)\n" " * a point and a circle or arc (point on curve)\n" -" * a point and a plane face (point on face)\n" +" * a point and one to three plane faces (point on face(s))\n" msgstr "" "Mauvaise sélection pour la contrainte point / courbe / plan. Cette " -"contrainte peut s'appliquer à:\n" +"contrainte peut s'appliquer à :\n" "\n" -"    * Deux points (points coïncidents)\n" -"    * Un point et un plan de travail (point dans le plan)\n" -"    * Un point et un segment de ligne (point en ligne)\n" -"    * Un point et un cercle ou un arc (point sur courbe)\n" -"    * Un point et une face plane (point sur une face)\n" +" – Au moins deux points (points coïncidents)\n" +" – Un point et un plan de travail (point dans le plan)\n" +" – Un point et un segment de ligne (point en ligne)\n" +" – Un point et un cercle ou un arc (point sur courbe)\n" +" – Un point, et entre une et trois faces planes (point sur face(s))\n" -#: constraint.cpp:346 +#: constraint.cpp:427 msgid "" "Bad selection for equal length / radius constraint. This constraint can " "apply to:\n" "\n" -" * two line segments (equal length)\n" +" * two or more line segments (equal length)\n" " * two line segments and two points (equal point-line distances)\n" " * a line segment and two points (equal point-line distances)\n" " * a line segment, and a point and line segment (point-line distance " "equals length)\n" -" * four line segments or normals (equal angle between A,B and C,D)\n" -" * three line segments or normals (equal angle between A,B and B,C)\n" -" * two circles or arcs (equal radius)\n" +" * two or more circles or arcs (equal radius)\n" " * a line segment and an arc (line segment length equals arc length)\n" msgstr "" "Mauvaise sélection pour une contrainte de longueur / rayon égale. Cette " -"contrainte peut s'appliquer à:\n" +"contrainte peut s'appliquer à :\n" "\n" -"    * Deux segments de ligne (longueur égale)\n" -"    * Deux segments de ligne et deux points (distances point-ligne égales)\n" -"    * Un segment de ligne et deux points (distances point-ligne égales)\n" -"    * Un segment de ligne ou un segment de ligne et point (distance point-" +" – Au moins deux segments de ligne(s) (longueur égale)\n" +" – Deux segments de ligne et deux points (distances point-ligne égales)\n" +" – Un segment de ligne et deux points (distances point-ligne égales)\n" +" – Un segment de ligne ou un segment de ligne et point (distance point-" "ligne de longueur égale)\n" -"    * Quatre segments de ligne ou des normales (angle entre A, B et C, D " -"égaux)\n" -"    * Trois segments de ligne ou des normales (angle entre A, B et B, C " -"égaux)\n" -"    * Deux cercles ou arcs (rayon égaux)\n" -"    * Un segment de ligne et un arc (la longueur de segment de ligne est " +" – Au moins deux cercles ou arcs (rayon égaux)\n" +" – Un segment de ligne et un arc (la longueur de segment de ligne est " "égale à la longueur d'arc)\n" -#: constraint.cpp:385 +#: constraint.cpp:480 msgid "" "Bad selection for length ratio constraint. This constraint can apply to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" -"Mauvaise sélection pour la contrainte du rapport de longueur. Cette " -"contrainte peut s'appliquer à:\n" +"Mauvaise sélection pour une contrainte de ratio de longueur. Cette " +"contrainte peut s'appliquer à :\n" "\n" -"    * Deux segments de ligne\n" +" – Deux segments de ligne\n" +" – Deux arcs\n" +" – Un arc et un segment de ligne\n" -#: constraint.cpp:402 +#: constraint.cpp:515 msgid "" "Bad selection for length difference constraint. This constraint can apply " "to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" -"Mauvaise sélection pour la contrainte de différence de longueur. Cette " -"contrainte peut s'appliquer à:\n" +"Mauvaise sélection pour une contrainte de différence de longueur. Cette " +"contrainte peut s'appliquer à :\n" "\n" -"    * Deux segments de ligne\n" +" – Deux segments de ligne\n" +" – Deux arcs\n" +" – Un arc et un segment de ligne\n" -#: constraint.cpp:428 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" @@ -409,12 +491,12 @@ msgid "" " * a line segment and a workplane (line's midpoint on plane)\n" msgstr "" "Mauvaise sélection pour une contrainte de point médian. Cette contrainte " -"peut s'appliquer à:\n" +"peut s'appliquer à :\n" "\n" -"    * Un segment de ligne et un point (point au milieu)\n" -"    * Un segment de ligne et un plan de travail (point médian dans le plan)\n" +" – Un segment de ligne et un point (point au milieu)\n" +" – Un segment de ligne et un plan de travail (point médian dans le plan)\n" -#: constraint.cpp:486 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -426,16 +508,16 @@ msgid "" "workplane)\n" msgstr "" "Mauvaise sélection pour la contrainte symétrique. Cette contrainte peut " -"s'appliquer à:\n" +"s'appliquer à :\n" "\n" -"    * Deux points ou un segment de ligne (symétrique à l'axe des coordonnées " +" – Deux points ou un segment de ligne (symétrique à l'axe des coordonnées " "du plan de travail)\n" -"    * Segment de ligne, et deux points ou un segment de ligne (symétrique " +" – Segment de ligne, et deux points ou un segment de ligne (symétrique " "sur le segment de ligne)\n" -"    * Plan de travail, et deux points ou un segment de ligne (symétrique au " +" – Plan de travail, et deux points ou un segment de ligne (symétrique au " "plan de travail)\n" -#: constraint.cpp:500 +#: constraint.cpp:623 msgid "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." @@ -443,29 +525,29 @@ msgstr "" "Un plan de travail doit être actif lors d'une contrainte de symétrie sans " "plan de symétrie explicite." -#: constraint.cpp:530 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." msgstr "" -"Activez un plan de travail (avec Dessin -> Dans plan de travail) avant " +"Activez un plan de travail (avec Dessin -> Dans le plan de travail) avant " "d'appliquer une contrainte horizontale ou verticale." -#: constraint.cpp:543 +#: constraint.cpp:679 msgid "" "Bad selection for horizontal / vertical constraint. This constraint can " "apply to:\n" "\n" -" * two points\n" -" * a line segment\n" +" * two or more points\n" +" * one or more line segments\n" msgstr "" "Mauvaise sélection pour la contrainte horizontale / verticale. Cette " -"contrainte peut s'appliquer à:\n" +"contrainte peut s'appliquer à :\n" "\n" -"    * deux points\n" -"    * Un segment de ligne\n" +" – Au moins deux points\n" +" – Au moins un segment de ligne(s)\n" -#: constraint.cpp:564 +#: constraint.cpp:697 msgid "" "Bad selection for same orientation constraint. This constraint can apply " "to:\n" @@ -473,72 +555,92 @@ msgid "" " * two normals\n" msgstr "" "Mauvaise sélection pour la même contrainte d'orientation. Cette contrainte " -"peut s'appliquer à:\n" +"peut s'appliquer à :\n" "\n" -" * Deux normales\n" +" – Deux normales\n" -#: constraint.cpp:614 +#: constraint.cpp:748 msgid "Must select an angle constraint." msgstr "Vous devez sélectionner une contrainte d'angle." -#: constraint.cpp:627 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." msgstr "Vous devez sélectionner une contrainte avec une étiquette associée." -#: constraint.cpp:638 +#: constraint.cpp:784 msgid "" "Bad selection for angle constraint. This constraint can apply to:\n" "\n" +"Angle between:\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" +"\n" +"Equal angles:\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" msgstr "" "Mauvaise sélection pour une contrainte d'angle. Cette contrainte peut " -"s'appliquer à:\n" +"s'appliquer à :\n" +"\n" +"Angle entre :\n" +" – Deux segments de ligne\n" +" – Un segment de ligne et une normale\n" +" – Deux normales\n" "\n" -"    * Deux segments de ligne\n" -"    * Un segment de ligne et une normale\n" -"    * Deux normales\n" +"Angles égaux :\n" +" – Quatre segments de ligne ou des normales (angle entre A, B et C, D " +"égaux)\n" +" – Trois segments de ligne ou des normales (angle entre A, B et B, C " +"égaux)\n" -#: constraint.cpp:701 +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." -msgstr "Courbe-Courbe tangence doit s'appliquer dans le plan de travail." +msgstr "La tangence courbe-courbe doit s'appliquer dans le plan de travail." -#: constraint.cpp:711 +#: constraint.cpp:887 msgid "" "Bad selection for parallel / tangent constraint. This constraint can apply " "to:\n" "\n" -" * two line segments (parallel)\n" -" * a line segment and a normal (parallel)\n" -" * two normals (parallel)\n" +" * two faces\n" +" * two or more line segments (parallel)\n" +" * one or more line segments and one or more normals (parallel)\n" +" * two or more normals (parallel)\n" " * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +" * two line segments, arcs, or beziers, that do not share an endpoint and " +"the end point(s) of the curve(s) (tangent)\n" msgstr "" "Mauvaise sélection pour la contrainte parallèle / tangente. Cette contrainte " -"peut s'appliquer à:\n" +"peut s'appliquer à :\n" "\n" -"    * Deux segments de ligne (parallèles)\n" -"    * Un segment de ligne et un parallèle (parallèle)\n" -"    * Deux normales (parallèles)\n" -"    * Deux segments de ligne, des arcs ou des Béziers, qui partagent un " -"point final (tangent)\n" +" – Deux faces\n" +" – Au moins deux segments de ligne(s) (parallèle)\n" +" – Au moins un segment de ligne et au moins une normale (parallèle)\n" +" – Au moins deux normales (parallèle)\n" +" – Deux segments de ligne, des arcs ou des Béziers, qui partagent une " +"extrémité (tangente)\n" +" * Deux segments de lignes, arcs, ou béziers, qui n'ont pas d'extrémité " +"en commun et la/les extrémité(s) des courbe(s) (tangente)\n" -#: constraint.cpp:729 +#: constraint.cpp:914 msgid "" "Bad selection for perpendicular constraint. This constraint can apply to:\n" "\n" +" * two faces\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" msgstr "" "Mauvaise sélection pour une contrainte perpendiculaire. Cette contrainte " -"peut s'appliquer à:\n" +"peut s'appliquer à :\n" "\n" -"    * Deux segments de ligne\n" -"    * Un segment de ligne et une normale\n" -"    * Deux normales\n" +" – Deux faces\n" +" – Deux segments de ligne\n" +" – Un segment de ligne et une normale\n" +" – Deux normales\n" -#: constraint.cpp:744 +#: constraint.cpp:931 msgid "" "Bad selection for lock point where dragged constraint. This constraint can " "apply to:\n" @@ -546,11 +648,15 @@ msgid "" " * a point\n" msgstr "" "Mauvaise sélection pour le point de verrouillage où la contrainte déplacé. " -"Cette contrainte peut s'appliquer à:\n" +"Cette contrainte peut s'appliquer à :\n" "\n" -"    * un point\n" +" – un point\n" + +#: constraint.cpp:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "NOUVEAU COMMENTAIRE — DOUBLE-CLIQUEZ POUR EDITER" -#: constraint.cpp:755 +#: constraint.cpp:952 msgid "click center of comment text" msgstr "cliquez le centre du texte de commentaire" @@ -559,8 +665,8 @@ msgid "" "No solid model present; draw one with extrudes and revolves, or use Export " "2d View to export bare lines and curves." msgstr "" -"Aucun modèle solide présent; Dessinez-en un avec une extrusion et " -"révolution, ou utilisez \"Exporter vue 2d\" pour exporter les lignes et les " +"Aucun modèle solide présent ; dessinez-en un avec une extrusion et " +"révolution, ou utilisez « Exporter vue 2d » pour exporter les lignes et les " "courbes dépouillées." #: export.cpp:61 @@ -572,39 +678,39 @@ msgid "" " * a point and two line segments (plane through point and parallel to " "lines)\n" msgstr "" -"Mauvaise sélection pour la section export. Sélectionnez:\n" +"Mauvaise sélection pour la section export. Sélectionnez :\n" "\n" -"    * Rien, avec un plan de travail actif (plan de travail est un plan de " +" – Rien, avec un plan de travail actif (plan de travail est un plan de " "section)\n" -"    * Une face (plan de coupe au-travers d'une face)\n" -"    * Un point et deux segments de ligne (plan au-travers d'un point et " +" – Une face (plan de coupe au-travers d'une face)\n" +" – Un point et deux segments de ligne (plan au-travers d'un point et " "parallèle aux lignes)\n" -#: export.cpp:822 +#: export.cpp:818 msgid "Active group mesh is empty; nothing to export." -msgstr "Le maillage du groupe actif est vide; Rien à exporter." +msgstr "Le maillage du groupe actif est vide ; Rien à exporter." -#: exportvector.cpp:337 +#: exportvector.cpp:336 msgid "freehand lines were replaced with continuous lines" msgstr "les lignes à main levée ont été remplacées par des lignes continues" -#: exportvector.cpp:339 +#: exportvector.cpp:338 msgid "zigzag lines were replaced with continuous lines" msgstr "les lignes en zigzag ont été remplacées par des lignes continues" -#: exportvector.cpp:593 +#: exportvector.cpp:592 msgid "" "Some aspects of the drawing have no DXF equivalent and were not exported:\n" msgstr "" "Certains aspects du dessin n'ont pas d'équivalent DXF et n'ont pas été " -"exportés:\n" +"exportés :\n" -#: exportvector.cpp:839 +#: exportvector.cpp:838 msgid "" "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." msgstr "" -"La taille de la page PDF dépasse 200 par 200 pouces; De nombreux lecteurs " -"peuvent rejeter ce fichier." +"La taille de la page PDF dépasse 200 par 200 pouces ; de nombreux lecteurs " +"risquent rejeter ce fichier." #: file.cpp:44 group.cpp:91 msgctxt "group-name" @@ -616,30 +722,30 @@ msgctxt "group-name" msgid "#references" msgstr "#références" -#: file.cpp:552 +#: file.cpp:555 msgid "The file is empty. It may be corrupt." -msgstr "" +msgstr "Le fichier est vide. Il est possible qu'il soit corrompu." -#: file.cpp:557 +#: file.cpp:560 msgid "" "Unrecognized data in file. This file may be corrupt, or from a newer version " "of the program." msgstr "" -"Données non reconnues dans le fichier. Ce fichier peut être corrompu ou " -"depuis une version plus récente du programme." +"Données non reconnues dans le fichier. Ce fichier peut être corrompu, ou " +"venir d'une version plus récente du programme." -#: file.cpp:867 +#: file.cpp:876 msgctxt "title" msgid "Missing File" msgstr "Fichier manquant" -#: file.cpp:868 +#: file.cpp:877 #, c-format msgctxt "dialog" msgid "The linked file “%s” is not present." -msgstr "" +msgstr "Le fichier lié « %s » n'est pas présent." -#: file.cpp:870 +#: file.cpp:879 msgctxt "dialog" msgid "" "Do you want to locate it manually?\n" @@ -647,21 +753,25 @@ msgid "" "If you decline, any geometry that depends on the missing file will be " "permanently removed." msgstr "" +"Voulez-vous le chercher manuellement ?\n" +"\n" +"Sinon, toute géométrie qui dépend du fichier manquant sera définitivement " +"supprimée." -#: file.cpp:873 +#: file.cpp:882 msgctxt "button" msgid "&Yes" -msgstr "" +msgstr "&Oui" -#: file.cpp:875 +#: file.cpp:884 msgctxt "button" msgid "&No" -msgstr "" +msgstr "&Non" -#: file.cpp:877 solvespace.cpp:569 +#: file.cpp:886 solvespace.cpp:652 msgctxt "button" msgid "&Cancel" -msgstr "" +msgstr "&Annuler" #: graphicswin.cpp:41 msgid "&File" @@ -741,7 +851,7 @@ msgstr "Accrocher la sélection à la &Grille" #: graphicswin.cpp:66 msgid "Rotate Imported &90°" -msgstr "Rotation importation &90°" +msgstr "Tourner l'import de &90°" #: graphicswin.cpp:68 msgid "Cu&t" @@ -777,7 +887,7 @@ msgstr "&Désélectionner Tout" #: graphicswin.cpp:78 msgid "&Line Styles..." -msgstr "&Ligne styles..." +msgstr "&Styles de Ligne..." #: graphicswin.cpp:79 msgid "&View Projection..." @@ -825,302 +935,314 @@ msgstr "Afficher la &grille d'accrochage" #: graphicswin.cpp:95 msgid "Darken Inactive Solids" -msgstr "" +msgstr "Noircir Solides Inactifs" #: graphicswin.cpp:96 msgid "Use &Perspective Projection" -msgstr "Utiliser la vue en &Perspective" +msgstr "Utiliser la Vue en &Perspective" #: graphicswin.cpp:97 +msgid "Show E&xploded View" +msgstr "Afficher Vue Éclatée" + +#: graphicswin.cpp:98 msgid "Dimension &Units" msgstr "&Unités de dimensions" -#: graphicswin.cpp:98 +#: graphicswin.cpp:99 msgid "Dimensions in &Millimeters" msgstr "Dimensions en &Millimètres" -#: graphicswin.cpp:99 +#: graphicswin.cpp:100 msgid "Dimensions in M&eters" msgstr "Dimensions en &Mètres" -#: graphicswin.cpp:100 +#: graphicswin.cpp:101 msgid "Dimensions in &Inches" msgstr "Dimensions en &Pouces" #: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "Dimensions en &Pieds et Pouces" + +#: graphicswin.cpp:104 msgid "Show &Toolbar" msgstr "Affichage &Barre d'outils" -#: graphicswin.cpp:103 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" msgstr "Affichage du &Navigateur de Propriété" -#: graphicswin.cpp:105 +#: graphicswin.cpp:107 msgid "&Full Screen" msgstr "&Plein Ecran" -#: graphicswin.cpp:107 +#: graphicswin.cpp:109 msgid "&New Group" msgstr "&Nouveau Groupe" -#: graphicswin.cpp:108 +#: graphicswin.cpp:110 msgid "Sketch In &3d" msgstr "Dessin en &3d" -#: graphicswin.cpp:109 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" msgstr "Dessin dans un nouveau &Plan de travail" -#: graphicswin.cpp:111 +#: graphicswin.cpp:113 msgid "Step &Translating" -msgstr "Espacement &Linéaire" +msgstr "Répéter par &Translation" -#: graphicswin.cpp:112 +#: graphicswin.cpp:114 msgid "Step &Rotating" -msgstr "Espacement &Circulaire" +msgstr "Répéter par &Rotation" -#: graphicswin.cpp:114 +#: graphicswin.cpp:116 msgid "E&xtrude" msgstr "E&xtruder" -#: graphicswin.cpp:115 +#: graphicswin.cpp:117 msgid "&Helix" -msgstr "&Helix" +msgstr "&Hélice" -#: graphicswin.cpp:116 +#: graphicswin.cpp:118 msgid "&Lathe" -msgstr "&Lathe" +msgstr "&Tour (révolution complète)" -#: graphicswin.cpp:117 +#: graphicswin.cpp:119 msgid "Re&volve" msgstr "Ré&volution" -#: graphicswin.cpp:119 +#: graphicswin.cpp:121 msgid "Link / Assemble..." -msgstr "Lié / Assembler..." +msgstr "Lier / Assembler..." -#: graphicswin.cpp:120 +#: graphicswin.cpp:122 msgid "Link Recent" -msgstr "Lié Récent" +msgstr "Lier Récent" -#: graphicswin.cpp:122 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "&Dessin" -#: graphicswin.cpp:123 +#: graphicswin.cpp:125 msgid "In &Workplane" msgstr "Dans le &Plan de travail" -#: graphicswin.cpp:124 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" msgstr "N'importe où dans la &3d" -#: graphicswin.cpp:126 +#: graphicswin.cpp:128 msgid "Datum &Point" msgstr "&Point" -#: graphicswin.cpp:127 -msgid "&Workplane" +#: graphicswin.cpp:129 +msgid "Wor&kplane" msgstr "&Plan de travail" -#: graphicswin.cpp:129 +#: graphicswin.cpp:131 msgid "Line &Segment" msgstr "Ligne - &Polyligne" -#: graphicswin.cpp:130 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" msgstr "Ligne de C&onstruction" -#: graphicswin.cpp:131 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "&Rectangle" -#: graphicswin.cpp:132 +#: graphicswin.cpp:134 msgid "&Circle" msgstr "&Cercle" -#: graphicswin.cpp:133 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" msgstr "&Arc de Cercle" -#: graphicswin.cpp:134 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" -msgstr "Spline Cubique de &Beziers" +msgstr "Spline Cubique de &Bézier" -#: graphicswin.cpp:136 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" msgstr "&Texte en Police TrueType" -#: graphicswin.cpp:137 -msgid "&Image" -msgstr "&Image" - #: graphicswin.cpp:139 +msgid "I&mage" +msgstr "I&mage" + +#: graphicswin.cpp:141 msgid "To&ggle Construction" -msgstr "&Basculer en mode \"Construction\"" +msgstr "&Basculer en mode « Construction »" -#: graphicswin.cpp:140 -msgid "Tangent &Arc at Point" -msgstr "&Arc Tangent au Point" +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" +msgstr "Rendre l'Arc Ta&ngent au Point" -#: graphicswin.cpp:141 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" msgstr "Diviser les Courbes à l'&Intersection" -#: graphicswin.cpp:143 +#: graphicswin.cpp:145 msgid "&Constrain" -msgstr "&Constraintes" +msgstr "&Contraintes" -#: graphicswin.cpp:144 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "&Distance / Diamètre" -#: graphicswin.cpp:145 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" msgstr "Dimension Maîtresse / Indicative" -#: graphicswin.cpp:146 -msgid "A&ngle" -msgstr "A&ngle" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "A&ngle / Égalité Angle" -#: graphicswin.cpp:147 +#: graphicswin.cpp:149 msgid "Reference An&gle" msgstr "An&gle Maître / Indicatif" -#: graphicswin.cpp:148 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" msgstr "Autre angle S&upplémentaire" -#: graphicswin.cpp:149 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" msgstr "Basculer cote Maîtresse / cote Indicative" -#: graphicswin.cpp:151 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "&Horizontal" -#: graphicswin.cpp:152 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "&Vertical" -#: graphicswin.cpp:154 +#: graphicswin.cpp:156 msgid "&On Point / Curve / Plane" msgstr "&Sur Point / Courbe / Plan" -#: graphicswin.cpp:155 -msgid "E&qual Length / Radius / Angle" -msgstr "&Egale Longueur / Rayon / Angle" - -#: graphicswin.cpp:156 -msgid "Length Ra&tio" -msgstr "R&apport de Longueur" - #: graphicswin.cpp:157 -msgid "Length Diff&erence" -msgstr "D&ifférence de Longueur" +msgid "E&qual Length / Radius" +msgstr "É&galité Longueur / Rayon" #: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "Ratio Longueur / Arc" + +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Différence Longueur / Arc" + +#: graphicswin.cpp:160 msgid "At &Midpoint" msgstr "Au &Milieu" -#: graphicswin.cpp:159 +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "&Symétrique" -#: graphicswin.cpp:160 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "Para&llèle / Tangent" -#: graphicswin.cpp:161 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "&Perpendiculaire" -#: graphicswin.cpp:162 +#: graphicswin.cpp:164 msgid "Same Orient&ation" msgstr "Même Orient&ation" -#: graphicswin.cpp:163 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "Accrocher le point à l'&Emplacement" -#: graphicswin.cpp:165 +#: graphicswin.cpp:167 msgid "Comment" msgstr "Commentaire" -#: graphicswin.cpp:167 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "&Analyse" -#: graphicswin.cpp:168 +#: graphicswin.cpp:170 msgid "Measure &Volume" -msgstr "Mesure &Volume" +msgstr "Mesurer &Volume" -#: graphicswin.cpp:169 +#: graphicswin.cpp:171 msgid "Measure A&rea" -msgstr "Mesure &Aire" +msgstr "Mesurer &Aire" -#: graphicswin.cpp:170 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" -msgstr "Mesure &Périmètre" +msgstr "Mesurer &Périmètre" -#: graphicswin.cpp:171 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" msgstr "Montrer les Pièces &Interférentes" -#: graphicswin.cpp:172 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" msgstr "Montrer les Arêtes &Nues" -#: graphicswin.cpp:173 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" msgstr "Montrer le &Centre de Gravité" -#: graphicswin.cpp:175 +#: graphicswin.cpp:177 msgid "Show &Underconstrained Points" msgstr "Montrer les &sous-contraintes Points" -#: graphicswin.cpp:177 +#: graphicswin.cpp:179 msgid "&Trace Point" msgstr "&Tracer Point" -#: graphicswin.cpp:178 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." -msgstr "&Arrêt Tracé..." +msgstr "&Arrêter Tracé..." -#: graphicswin.cpp:179 +#: graphicswin.cpp:181 msgid "Step &Dimension..." -msgstr "Espacement &Dimension..." +msgstr "&Dimension pas-à-pas..." -#: graphicswin.cpp:181 +#: graphicswin.cpp:183 msgid "&Help" msgstr "&Aide" -#: graphicswin.cpp:182 +#: graphicswin.cpp:184 msgid "&Language" msgstr "&Langue" -#: graphicswin.cpp:183 +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "&Site web / Manuel" -#: graphicswin.cpp:185 +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "Voir le commit sur &GitHub" + +#: graphicswin.cpp:188 msgid "&About" msgstr "&A propos" -#: graphicswin.cpp:355 +#: graphicswin.cpp:362 msgid "(no recent files)" msgstr "(pas de fichier récent)" -#: graphicswin.cpp:363 +#: graphicswin.cpp:370 #, c-format msgid "File '%s' does not exist." -msgstr "" +msgstr "Le fichier « %s » n'existe pas." -#: graphicswin.cpp:725 +#: graphicswin.cpp:779 msgid "No workplane is active, so the grid will not appear." -msgstr "Pas de plan de travail actif, donc la grille ne va pas apparaître." +msgstr "Pas de plan de travail actif, la grille ne va donc pas apparaître." -#: graphicswin.cpp:740 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel " "projection.\n" @@ -1134,19 +1256,19 @@ msgstr "" "Pour une projection en perspective, modifiez le facteur de perspective dans " "l'écran de configuration. Une valeur d'environ 0,3 est typique." -#: graphicswin.cpp:819 +#: graphicswin.cpp:884 msgid "" "Select a point; this point will become the center of the view on screen." msgstr "" "Sélectionnez un point. Ce point deviendra le centre de la vue à l'écran." -#: graphicswin.cpp:1114 +#: graphicswin.cpp:1193 msgid "No additional entities share endpoints with the selected entities." msgstr "" "Aucune entité supplémentaire ne partage des points d'extrémité avec les " "entités sélectionnées." -#: graphicswin.cpp:1132 +#: graphicswin.cpp:1211 msgid "" "To use this command, select a point or other entity from an linked part, or " "make a link group the active group." @@ -1154,30 +1276,30 @@ msgstr "" "Pour utiliser cette commande, sélectionnez un point ou une autre entité à " "partir d'une pièce liée ou créez un groupe de liens dans le groupe actif." -#: graphicswin.cpp:1155 +#: graphicswin.cpp:1234 msgid "" "No workplane is active. Activate a workplane (with Sketch -> In Workplane) " "to define the plane for the snap grid." msgstr "" "Aucun plan de travail n'est actif. Activez un plan de travail (avec Dessin -" -"> Dans plan de travail) pour définir le plan pour la grille d'accrochage." +"> Dans le plan de travail) pour définir le plan pour la grille d'accrochage." -#: graphicswin.cpp:1162 +#: graphicswin.cpp:1241 msgid "" "Can't snap these items to grid; select points, text comments, or constraints " "with a label. To snap a line, select its endpoints." msgstr "" "Impossible d'accrocher ces éléments à la grille. Sélectionnez des points, " "des textes de commentaires ou des contraintes avec une étiquette. Pour " -"accrocher une ligne, sélectionnez ses points d'extrémité." +"accrocher une ligne, sélectionnez ses extrémités." -#: graphicswin.cpp:1247 +#: graphicswin.cpp:1326 msgid "No workplane selected. Activating default workplane for this group." msgstr "" "Aucun plan de travail sélectionné. Activation du plan de travail par défaut " "pour ce groupe." -#: graphicswin.cpp:1250 +#: graphicswin.cpp:1329 msgid "" "No workplane is selected, and the active group does not have a default " "workplane. Try selecting a workplane, or activating a sketch-in-new-" @@ -1185,9 +1307,9 @@ msgid "" msgstr "" "Aucun plan de travail n'est sélectionné et le groupe actif n'a pas de plan " "de travail par défaut. Essayez de sélectionner un plan de travail ou " -"d'activer un groupe de \"Dessin dans nouveau plan travail\"." +"d'activer un groupe de « Dessin dans nouveau plan travail »." -#: graphicswin.cpp:1271 +#: graphicswin.cpp:1350 msgid "" "Bad selection for tangent arc at point. Select a single point, or select " "nothing to set up arc parameters." @@ -1195,49 +1317,47 @@ msgstr "" "Mauvaise sélection pour l'arc tangent au point. Sélectionnez un seul point, " "ou ne sélectionnez rien pour configurer les paramètres de l'arc." -#: graphicswin.cpp:1282 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" -msgstr "" -"cliquez un point sur l'arc (dessine dans le sens inverse des aiguilles d'une " -"montre)" +msgstr "cliquez un point sur l'arc (dessine dans le sens anti-horaire)" -#: graphicswin.cpp:1283 +#: graphicswin.cpp:1362 msgid "click to place datum point" msgstr "cliquez pour placer un point" -#: graphicswin.cpp:1284 +#: graphicswin.cpp:1363 msgid "click first point of line segment" msgstr "cliquez le premier point du segment de ligne" -#: graphicswin.cpp:1286 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" msgstr "cliquez le premier point de la ligne de construction" -#: graphicswin.cpp:1287 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" msgstr "cliquez le premier point du segment cubique" -#: graphicswin.cpp:1288 +#: graphicswin.cpp:1367 msgid "click center of circle" msgstr "cliquez pour placer le centre du cercle" -#: graphicswin.cpp:1289 +#: graphicswin.cpp:1368 msgid "click origin of workplane" msgstr "cliquez pour placer l'origine du plan de travail" -#: graphicswin.cpp:1290 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" msgstr "cliquez un coin du rectangle" -#: graphicswin.cpp:1291 +#: graphicswin.cpp:1370 msgid "click top left of text" msgstr "cliquez le haut à gauche du texte" -#: graphicswin.cpp:1297 +#: graphicswin.cpp:1376 msgid "click top left of image" msgstr "cliquez le haut à gauche de l'image" -#: graphicswin.cpp:1309 +#: graphicswin.cpp:1402 msgid "" "No entities are selected. Select entities before trying to toggle their " "construction state." @@ -1250,41 +1370,43 @@ msgctxt "group-name" msgid "sketch-in-3d" msgstr "dessin-en-3d" -#: group.cpp:142 +#: group.cpp:154 msgid "" "Bad selection for new sketch in workplane. This group can be created with:\n" "\n" " * a point (through the point, orthogonal to coordinate axes)\n" " * a point and two line segments (through the point, parallel to the " "lines)\n" +" * a point and a normal (through the point, orthogonal to the normal)\n" " * a workplane (copy of the workplane)\n" msgstr "" -"Mauvaise sélection pour un nouveau dessin dans le plan de travail. Ce groupe " -"peut être créé avec:\n" +"Mauvaise sélection pour un nouveau dessin. Ce groupe peut être créé avec :\n" "\n" -"    * Un point (par le point, orthogonal aux axes de coordonnées)\n" -"    * Un point et deux segments de ligne (par le point, parallèle aux " -"lignes)\n" -"    * Un plan de travail (copie du plan de travail)\n" +" – Un point (passant par le point, orthogonal aux axes de coordonnées)\n" +" – Un point et deux segments de lignes (passant par le point, parallèle " +"aux deux lignes)\n" +" – Un point et une normale (passant par le point, orthogonal à la " +"normale)\n" +" – Un plan de travail (copie du plan de travail)\n" -#: group.cpp:154 +#: group.cpp:170 msgid "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " "will be extruded normal to the workplane." msgstr "" -"Activez un plan de travail (Dessin -> Dans plan de travail) avant " +"Activez un plan de travail (Dessin -> Dans le plan de travail) avant " "l'extrusion. Le croquis sera extrudé normalement au plan de travail." -#: group.cpp:163 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" msgstr "extruder" -#: group.cpp:168 +#: group.cpp:184 msgid "Lathe operation can only be applied to planar sketches." -msgstr "" +msgstr "L'opération tour ne peut être appliquée qu'à des dessins plans." -#: group.cpp:179 +#: group.cpp:195 msgid "" "Bad selection for new lathe group. This group can be created with:\n" "\n" @@ -1293,22 +1415,22 @@ msgid "" " * a line segment (revolved about line segment)\n" msgstr "" "Mauvaise sélection pour un nouveau groupe de révolution. Ce groupe peut être " -"créé avec:\n" +"créé avec :\n" "\n" -"    * Un point et un segment de ligne ou normal (révolution autour d'un axe " +" – Un point et un segment de ligne ou normal (révolution autour d'un axe " "parallèle à la ligne / point normal, par le point)\n" -"    * Un segment de ligne (révolution sur le segment de ligne)\n" +" – Un segment de ligne (révolution sur le segment de ligne)\n" -#: group.cpp:189 +#: group.cpp:205 msgctxt "group-name" msgid "lathe" -msgstr "révolution" +msgstr "tour" -#: group.cpp:194 +#: group.cpp:210 msgid "Revolve operation can only be applied to planar sketches." -msgstr "" +msgstr "L'opération révolution ne peut être appliquée qu'à des dessins plans." -#: group.cpp:205 +#: group.cpp:221 msgid "" "Bad selection for new revolve group. This group can be created with:\n" "\n" @@ -1316,17 +1438,23 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" +"Mauvaise sélection pour un nouveau groupe de révolution. Ce groupe peut être " +"créé avec :\n" +"\n" +" – Un point et un segment de ligne ou normal (révolution autour d'un axe " +"parallèle à la ligne / point normal, par le point)\n" +" – Un segment de ligne (révolution sur le segment de ligne)\n" -#: group.cpp:217 +#: group.cpp:233 msgctxt "group-name" msgid "revolve" -msgstr "" +msgstr "révolution" -#: group.cpp:222 +#: group.cpp:238 msgid "Helix operation can only be applied to planar sketches." -msgstr "" +msgstr "L'opération hélice ne peut être appliquée qu'à des dessins plans." -#: group.cpp:233 +#: group.cpp:249 msgid "" "Bad selection for new helix group. This group can be created with:\n" "\n" @@ -1334,13 +1462,19 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" +"Mauvaise sélection pour un nouveau groupe d'hélice. Ce groupe peut être créé " +"avec :\n" +"\n" +" – Un point et un segment de ligne ou normal (révolution autour d'un axe " +"parallèle à la ligne / point normal, par le point)\n" +" – Un segment de ligne (révolution sur le segment de ligne)\n" -#: group.cpp:245 +#: group.cpp:261 msgctxt "group-name" msgid "helix" -msgstr "" +msgstr "hélice" -#: group.cpp:258 +#: group.cpp:274 msgid "" "Bad selection for new rotation. This group can be created with:\n" "\n" @@ -1350,56 +1484,60 @@ msgid "" "point, and parallel to line / normal)\n" msgstr "" "Mauvaise sélection pour une nouvelle rotation. Ce groupe peut être créé " -"avec:\n" +"avec :\n" "\n" -"    * Un point, lorsqu'il est verrouillé dans un plan de travail (rotation " +" – Un point, lorsqu'il est verrouillé dans un plan de travail (rotation " "dans le plan, autour de ce point)\n" -"    * Un point et une ligne ou une normale (tourner autour d'un axe par le " +" – Un point et une ligne ou une normale (tourner autour d'un axe par le " "point et parallèle à la ligne / normale)\n" -#: group.cpp:271 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" msgstr "rotation" -#: group.cpp:282 +#: group.cpp:298 msgctxt "group-name" msgid "translate" msgstr "translation" -#: group.cpp:400 +#: group.cpp:422 msgid "(unnamed)" msgstr "(sans nom)" -#: groupmesh.cpp:709 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" -msgstr "contour non fermé ou tout n'est pas du même style!" +msgstr "contour non fermé ou tout n'est pas du même style !" -#: groupmesh.cpp:722 +#: groupmesh.cpp:723 msgid "points not all coplanar!" -msgstr "les points ne sont pas tous coplanaires!" +msgstr "les points ne sont pas tous coplanaires !" -#: groupmesh.cpp:724 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" -msgstr "le contour s'entrecroise!" +msgstr "le contour s'entrecroise !" -#: groupmesh.cpp:726 +#: groupmesh.cpp:727 msgid "zero-length edge!" -msgstr "arête de longueur nulle!" +msgstr "arête de longueur nulle !" + +#: importmesh.cpp:136 +msgid "Text-formated STL files are not currently supported" +msgstr "Les fichiers STL textuels ne sont pas actuellement pas supportés" -#: modify.cpp:254 +#: modify.cpp:252 msgid "Must be sketching in workplane to create tangent arc." msgstr "Vous devez dessiner dans un plan pour créer un arc tangent." -#: modify.cpp:301 +#: modify.cpp:299 msgid "" "To create a tangent arc, select a point where two non-construction lines or " "circles in this group and workplane join." msgstr "" -"Pour créer un arc tangent, sélectionnez un point où deux lignes (pas de " -"construction) ou cercles de ce groupe et de ce plan se joignent." +"Pour créer un arc tangent, sélectionnez un point où deux lignes ou cercles " +"(pas de construction) de ce groupe et de ce plan se rejoignent." -#: modify.cpp:388 +#: modify.cpp:386 msgid "" "Couldn't round this corner. Try a smaller radius, or try creating the " "desired geometry by hand with tangency constraints." @@ -1407,131 +1545,133 @@ msgstr "" "Impossible d'arrondir ce coin. Essayez un rayon plus petit, ou essayez de " "créer la géométrie souhaitée à la main avec des contraintes tangentielles." -#: modify.cpp:597 +#: modify.cpp:595 msgid "Couldn't split this entity; lines, circles, or cubics only." msgstr "" -"Impossible de diviser cette entité; Lignes, cercles ou cubiques uniquement." +"Impossible de diviser cette entité ; Lignes, cercles ou cubiques uniquement." -#: modify.cpp:624 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." msgstr "Vous devez dessiner dans un plan de travail pour diviser." -#: modify.cpp:631 +#: modify.cpp:629 msgid "" "Select two entities that intersect each other (e.g. two lines/circles/arcs " "or a line/circle/arc and a point)." msgstr "" +"Sélectionnez deux entités qui s'intersectent (par exemple deux lignes/" +"cercles/arcs, ou une ligne/cercle/arc et un point)." -#: modify.cpp:736 +#: modify.cpp:734 msgid "Can't split; no intersection found." -msgstr "Impossible de diviser; pas d'intersection trouvée." +msgstr "Impossible de diviser ; pas d'intersection trouvée." -#: mouse.cpp:559 +#: mouse.cpp:558 msgid "Assign to Style" msgstr "Appliquer au style" -#: mouse.cpp:575 +#: mouse.cpp:574 msgid "No Style" msgstr "Pas de style" -#: mouse.cpp:578 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." msgstr "Style personnalisé nouvellement créé ..." -#: mouse.cpp:585 +#: mouse.cpp:584 msgid "Group Info" msgstr "Info Groupe" -#: mouse.cpp:605 +#: mouse.cpp:604 msgid "Style Info" msgstr "Info Style" -#: mouse.cpp:625 +#: mouse.cpp:624 msgid "Select Edge Chain" msgstr "Sélection Chaîne d'arêtes" -#: mouse.cpp:631 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" msgstr "Basculer cote maîtresse / cote indicative" -#: mouse.cpp:637 +#: mouse.cpp:636 msgid "Other Supplementary Angle" msgstr "Autre angle supplémentaire" -#: mouse.cpp:642 +#: mouse.cpp:641 msgid "Snap to Grid" msgstr "Accrocher à la grille" -#: mouse.cpp:651 +#: mouse.cpp:650 msgid "Remove Spline Point" msgstr "Effacer le point de la Spline" -#: mouse.cpp:686 +#: mouse.cpp:685 msgid "Add Spline Point" msgstr "Ajouter un point à la Spline" -#: mouse.cpp:690 +#: mouse.cpp:689 msgid "Cannot add spline point: maximum number of points reached." msgstr "" -"Impossible d'ajouter le point spline: nombre maximum de points atteints." +"Impossible d'ajouter le point spline : nombre maximum de points atteints." -#: mouse.cpp:715 +#: mouse.cpp:714 msgid "Toggle Construction" -msgstr "Basculer en mode \"construction\"." +msgstr "Basculer en mode « construction »" #: mouse.cpp:730 msgid "Delete Point-Coincident Constraint" msgstr "Effacer la contraint Point-Coïncident" -#: mouse.cpp:749 +#: mouse.cpp:748 msgid "Cut" msgstr "Couper" -#: mouse.cpp:751 +#: mouse.cpp:750 msgid "Copy" msgstr "Copier" -#: mouse.cpp:755 +#: mouse.cpp:754 msgid "Select All" msgstr "Sélectionner tout" -#: mouse.cpp:760 +#: mouse.cpp:759 msgid "Paste" msgstr "Coller" -#: mouse.cpp:762 +#: mouse.cpp:761 msgid "Paste Transformed..." msgstr "Coller transformé..." -#: mouse.cpp:767 +#: mouse.cpp:766 msgid "Delete" msgstr "Effacer" -#: mouse.cpp:770 +#: mouse.cpp:769 msgid "Unselect All" msgstr "Désélectionner tout" -#: mouse.cpp:777 +#: mouse.cpp:776 msgid "Unselect Hovered" msgstr "Désélectionner survolé" -#: mouse.cpp:786 +#: mouse.cpp:785 msgid "Zoom to Fit" msgstr "Zoom pour ajuster" -#: mouse.cpp:988 mouse.cpp:1275 +#: mouse.cpp:987 mouse.cpp:1276 msgid "click next point of line, or press Esc" msgstr "cliquez pou le prochain point de ligne or appuyez sur Esc" -#: mouse.cpp:994 +#: mouse.cpp:993 msgid "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" -"Impossible de dessiner un rectangle en 3d; D'abord, activez un plan de " -"travail avec \"Dessin -> Dans plan de travail\"." +"Impossible de dessiner un rectangle en 3d ; D'abord, activez un plan de " +"travail avec « Dessin -> Dans le plan de travail »." -#: mouse.cpp:1028 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" msgstr "cliquez pour placer un autre coin de rectangle" @@ -1544,8 +1684,8 @@ msgid "" "Can't draw arc in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" -"Ne peut pas dessiner l'arc en 3d; D'abord, activez un plan de travail avec " -"\"Dessin -> Dans plan de travail\"." +"Ne peut pas dessiner l'arc en 3d ; D'abord, activez un plan de travail avec " +"« Dessin -> Dans le plan de travail »." #: mouse.cpp:1072 msgid "click to place point" @@ -1559,7 +1699,7 @@ msgstr "cliquez le prochain point cubique ou appuyez sur Esc" msgid "" "Sketching in a workplane already; sketch in 3d before creating new workplane." msgstr "" -"Vous dessinez déjà dans un plan de travail; Sélectionner \"Dessiner en 3d\" " +"Vous dessinez déjà dans un plan de travail ; Sélectionner « Dessiner en 3d » " "avant de créer un nouveau plan de travail." #: mouse.cpp:1109 @@ -1567,242 +1707,260 @@ msgid "" "Can't draw text in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" -"Impossible de dessiner du texte en 3d; D'abord, activer un plan de travail " -"avec \"Dessin -> Dans plan de travail\"." +"Impossible de dessiner du texte en 3d ; D'abord, activer un plan de travail " +"avec « Dessin -> Dans le plan de travail »." #: mouse.cpp:1126 msgid "click to place bottom right of text" -msgstr "" +msgstr "cliquer pour placer le coin inférieur droit du texte" #: mouse.cpp:1132 msgid "" "Can't draw image in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" -"Impossible de dessiner l'image en 3d; D'abord, activez un plan de travail " -"avec \"Dessin -> Dans plan de travail\"." - -#: mouse.cpp:1159 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" -msgstr "NOUVEAU COMMENTAIRE - DOUBLE-CLIQUE POUR EDITER" +"Impossible de dessiner l'image en 3d ; D'abord, activez un plan de travail " +"avec « Dessin -> Dans le plan de travail »." -#: platform/gui.cpp:85 platform/gui.cpp:89 solvespace.cpp:511 +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 msgctxt "file-type" msgid "SolveSpace models" -msgstr "" +msgstr "modèles SolveSpace" + +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "TOUT" -#: platform/gui.cpp:90 +#: platform/gui.cpp:91 msgctxt "file-type" msgid "IDF circuit board" -msgstr "" +msgstr "circuit imprimé IDF" + +#: platform/gui.cpp:92 +msgctxt "file-type" +msgid "STL triangle mesh" +msgstr "maillage de triangles STL" -#: platform/gui.cpp:94 +#: platform/gui.cpp:96 msgctxt "file-type" msgid "PNG image" -msgstr "" +msgstr "image PNG" -#: platform/gui.cpp:98 +#: platform/gui.cpp:100 msgctxt "file-type" msgid "STL mesh" -msgstr "" +msgstr "maillage STL" -#: platform/gui.cpp:99 +#: platform/gui.cpp:101 msgctxt "file-type" msgid "Wavefront OBJ mesh" -msgstr "" +msgstr "Mesh Wavefront OBJ" -#: platform/gui.cpp:100 +#: platform/gui.cpp:102 msgctxt "file-type" msgid "Three.js-compatible mesh, with viewer" -msgstr "" +msgstr "Mesh compatible avec Three.js, avec visionneuse" -#: platform/gui.cpp:101 +#: platform/gui.cpp:103 msgctxt "file-type" msgid "Three.js-compatible mesh, mesh only" -msgstr "" +msgstr "Mesh compatible avec Three.js, mesh seulement" -#: platform/gui.cpp:102 +#: platform/gui.cpp:104 msgctxt "file-type" msgid "VRML text file" -msgstr "" +msgstr "Fichier texte VRML" -#: platform/gui.cpp:106 platform/gui.cpp:113 platform/gui.cpp:120 +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 msgctxt "file-type" msgid "STEP file" -msgstr "" +msgstr "fichier STEP" -#: platform/gui.cpp:110 +#: platform/gui.cpp:112 msgctxt "file-type" msgid "PDF file" -msgstr "" +msgstr "fichier PDF" -#: platform/gui.cpp:111 +#: platform/gui.cpp:113 msgctxt "file-type" msgid "Encapsulated PostScript" -msgstr "" +msgstr "PostScript encapsulé" -#: platform/gui.cpp:112 +#: platform/gui.cpp:114 msgctxt "file-type" msgid "Scalable Vector Graphics" -msgstr "" +msgstr "image vectorielle SVG" -#: platform/gui.cpp:114 platform/gui.cpp:121 +#: platform/gui.cpp:116 platform/gui.cpp:123 msgctxt "file-type" msgid "DXF file (AutoCAD 2007)" -msgstr "" +msgstr "fichier DXF (AutoCAD 2007)" -#: platform/gui.cpp:115 +#: platform/gui.cpp:117 msgctxt "file-type" msgid "HPGL file" -msgstr "" +msgstr "fichier HPGL" -#: platform/gui.cpp:116 +#: platform/gui.cpp:118 msgctxt "file-type" msgid "G Code" -msgstr "" +msgstr "G Code" -#: platform/gui.cpp:125 +#: platform/gui.cpp:127 msgctxt "file-type" msgid "AutoCAD DXF and DWG files" -msgstr "" +msgstr "fichiers AutoCAD DXF et DWG" -#: platform/gui.cpp:129 +#: platform/gui.cpp:131 msgctxt "file-type" msgid "Comma-separated values" -msgstr "" +msgstr "CSV (Comma-separated values)" -#: platform/guigtk.cpp:1324 platform/guimac.mm:1363 platform/guiwin.cpp:1639 +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" msgstr "sans nom" -#: platform/guigtk.cpp:1335 platform/guigtk.cpp:1368 platform/guimac.mm:1321 -#: platform/guiwin.cpp:1582 +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 msgctxt "title" msgid "Save File" msgstr "Sauver fichier" -#: platform/guigtk.cpp:1336 platform/guigtk.cpp:1369 platform/guimac.mm:1304 -#: platform/guiwin.cpp:1584 +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 msgctxt "title" msgid "Open File" msgstr "Ouvrir Fichier" -#: platform/guigtk.cpp:1339 platform/guigtk.cpp:1375 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" msgid "_Cancel" msgstr "_Annuler" -#: platform/guigtk.cpp:1340 platform/guigtk.cpp:1373 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" msgid "_Save" msgstr "_Sauver" -#: platform/guigtk.cpp:1341 platform/guigtk.cpp:1374 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" msgid "_Open" -msgstr "" +msgstr "_Ouvrir" -#: solvespace.cpp:169 +#: solvespace.cpp:175 msgctxt "title" msgid "Autosave Available" msgstr "Sauvegarde automatique existante" -#: solvespace.cpp:170 +#: solvespace.cpp:176 msgctxt "dialog" msgid "An autosave file is available for this sketch." -msgstr "" +msgstr "Un fichier de sauvegarde automatique est disponible pour ce dessin." -#: solvespace.cpp:171 +#: solvespace.cpp:177 msgctxt "dialog" msgid "Do you want to load the autosave file instead?" -msgstr "" +msgstr "Voulez-vous charger la sauvegarde automatique à la place ?" -#: solvespace.cpp:172 +#: solvespace.cpp:178 msgctxt "button" msgid "&Load autosave" -msgstr "" +msgstr "&Charger la sauvegarde automatique" -#: solvespace.cpp:174 +#: solvespace.cpp:180 msgctxt "button" msgid "Do&n't Load" -msgstr "" +msgstr "Ne &pas la charger" -#: solvespace.cpp:557 +#: solvespace.cpp:640 msgctxt "title" msgid "Modified File" msgstr "Fichier modifié" -#: solvespace.cpp:559 +#: solvespace.cpp:642 #, c-format msgctxt "dialog" msgid "Do you want to save the changes you made to the sketch “%s”?" msgstr "" +"Voulez-vous sauver les changements que vous avez fait au dessin « %s » ?" -#: solvespace.cpp:562 +#: solvespace.cpp:645 msgctxt "dialog" msgid "Do you want to save the changes you made to the new sketch?" msgstr "" +"Voulez-vous sauver les changements que vous avez fait au nouveau dessin ?" -#: solvespace.cpp:565 +#: solvespace.cpp:648 msgctxt "dialog" msgid "Your changes will be lost if you don't save them." -msgstr "" +msgstr "Vos changements seront perdus si vous ne les sauvez pas." -#: solvespace.cpp:566 +#: solvespace.cpp:649 msgctxt "button" msgid "&Save" -msgstr "" +msgstr "&Sauver" -#: solvespace.cpp:568 +#: solvespace.cpp:651 msgctxt "button" msgid "Do&n't Save" -msgstr "" +msgstr "&Ne pas sauver" -#: solvespace.cpp:589 +#: solvespace.cpp:672 msgctxt "title" msgid "(new sketch)" msgstr "(nouveau dessin)" -#: solvespace.cpp:596 +#: solvespace.cpp:683 msgctxt "title" msgid "Property Browser" msgstr "Navigateur de propriété" -#: solvespace.cpp:658 +#: solvespace.cpp:746 msgid "" "Constraints are currently shown, and will be exported in the toolpath. This " "is probably not what you want; hide them by clicking the link at the top of " "the text window." msgstr "" +"Les contraintes sont actuellement affichées, et seront exportées dans le " +"toolpath. Ce n'est probablement pas ce que vous voulez, cachez-le en cliquer " +"le lien en haut de la fenêtre de texte." -#: solvespace.cpp:730 +#: solvespace.cpp:834 #, c-format msgid "" "Can't identify file type from file extension of filename '%s'; try .dxf or ." "dwg." msgstr "" +"Impossible d'identifier le type de fichier à partir de l'extension '%s' ; " +"essayez .dxf ou .dwg." -#: solvespace.cpp:778 +#: solvespace.cpp:886 msgid "Constraint must have a label, and must not be a reference dimension." msgstr "" +"La contrainte doit avoir un nom, et ne doit pas référencer une dimension." -#: solvespace.cpp:782 +#: solvespace.cpp:890 msgid "Bad selection for step dimension; select a constraint." msgstr "" +"Mauvaise sélection pour le pas-à-pas dimension ; sélectionnea une contrainte." -#: solvespace.cpp:806 +#: solvespace.cpp:914 msgid "The assembly does not interfere, good." -msgstr "" +msgstr "Cet assemblage n'interfère pas, bien." -#: solvespace.cpp:822 +#: solvespace.cpp:930 #, c-format msgid "" "The volume of the solid model is:\n" "\n" " %s" msgstr "" +"Le volume du modèle de solide est :\n" +"\n" +" %s" -#: solvespace.cpp:831 +#: solvespace.cpp:939 #, c-format msgid "" "\n" @@ -1810,16 +1968,24 @@ msgid "" "\n" " %s" msgstr "" +"\n" +"Le volume de ce groupe mesh est :\n" +"\n" +" %s" -#: solvespace.cpp:836 +#: solvespace.cpp:944 msgid "" "\n" "\n" "Curved surfaces have been approximated as triangles.\n" "This introduces error, typically of around 1%." msgstr "" +"\n" +"\n" +"Les surfaces courbes ont été approximée par des triangles.\n" +"Cela introduit une erreur, d'environ 1% en général." -#: solvespace.cpp:851 +#: solvespace.cpp:959 #, c-format msgid "" "The surface area of the selected faces is:\n" @@ -1829,14 +1995,22 @@ msgid "" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%." msgstr "" +"La surface des faces sélectionnées est :\n" +"\n" +" %s\n" +"\n" +"Les courbes ont été approximée comme étant linéaires par morceau.\n" +"Cela introduit une erreur, d'environ 1%% en général." -#: solvespace.cpp:860 +#: solvespace.cpp:968 msgid "" "This group does not contain a correctly-formed 2d closed area. It is open, " "not coplanar, or self-intersecting." msgstr "" +"Ce groupe ne contient pas de surface 3d fermée correcte. Elle est ouverte, " +"non coplanaire, ou s'intersecte." -#: solvespace.cpp:872 +#: solvespace.cpp:980 #, c-format msgid "" "The area of the region sketched in this group is:\n" @@ -1846,8 +2020,14 @@ msgid "" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%." msgstr "" +"La surface de la région dessinée dans ce groupe est :\n" +"\n" +" %s\n" +"\n" +"Les courbes ont été approximée comme linéaires par morceau.\n" +"Cela introduit une erreur, d'environ 1%% en général." -#: solvespace.cpp:892 +#: solvespace.cpp:1000 #, c-format msgid "" "The total length of the selected entities is:\n" @@ -1857,45 +2037,57 @@ msgid "" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%." msgstr "" +"La longueur totale des entités sélectionnées est :\n" +"\n" +" %s\n" +"\n" +"\n" +"Les courbes ont été approximée comme linéaires par morceau.\n" +"Cela introduit une erreur, d'environ 1%% en général." -#: solvespace.cpp:898 +#: solvespace.cpp:1006 msgid "Bad selection for perimeter; select line segments, arcs, and curves." msgstr "" +"Mauvaise sélection pour un périmètre ; sélectionnez des segments de ligne, " +"des arcs, et des coubres." -#: solvespace.cpp:914 +#: solvespace.cpp:1022 msgid "Bad selection for trace; select a single point." -msgstr "" +msgstr "Mauvaise sélection pour le tracé ; sélectionnez un unique point." -#: solvespace.cpp:941 +#: solvespace.cpp:1049 #, c-format msgid "Couldn't write to '%s'" -msgstr "" +msgstr "Impossible d'écrire « %s »" -#: solvespace.cpp:971 +#: solvespace.cpp:1079 msgid "The mesh is self-intersecting (NOT okay, invalid)." -msgstr "" +msgstr "Le mesh s'intersecte (NON valide)." -#: solvespace.cpp:972 +#: solvespace.cpp:1080 msgid "The mesh is not self-intersecting (okay, valid)." -msgstr "" +msgstr "Le mesh ne s'intersecte pas (valide)." -#: solvespace.cpp:974 +#: solvespace.cpp:1082 msgid "The mesh has naked edges (NOT okay, invalid)." -msgstr "" +msgstr "Le mesh a des arêtes nues (NON valide)." -#: solvespace.cpp:975 +#: solvespace.cpp:1083 msgid "The mesh is watertight (okay, valid)." -msgstr "" +msgstr "Le mesh est étanche (valide)." -#: solvespace.cpp:978 +#: solvespace.cpp:1086 #, c-format msgid "" "\n" "\n" "The model contains %d triangles, from %d surfaces." msgstr "" +"\n" +"\n" +"Le modèle contient %d triangles, répartis sur %d surfaces." -#: solvespace.cpp:982 +#: solvespace.cpp:1090 #, c-format msgid "" "%s\n" @@ -1904,8 +2096,13 @@ msgid "" "\n" "Zero problematic edges, good.%s" msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"Pas d'arête problématique, bien.%s" -#: solvespace.cpp:985 +#: solvespace.cpp:1093 #, c-format msgid "" "%s\n" @@ -1914,8 +2111,13 @@ msgid "" "\n" "%d problematic edges, bad.%s" msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"%d arêtes problématiques, pas bien.%s" -#: solvespace.cpp:998 +#: solvespace.cpp:1106 #, c-format msgid "" "This is SolveSpace version %s.\n" @@ -1931,36 +2133,48 @@ msgid "" "\n" "© 2008-%d Jonathan Westhues and other authors.\n" msgstr "" +"Ceci est SolveSpace version %s.\n" +"\n" +"Pour plus d'information, visitez http://solvespace.com/\n" +"\n" +"SolveSpace est un logiciel libre : vous êtes libre de le\n" +"modifier et/ou le redistribuer selon les termes de la GNU\n" +"General Public License (GPL) version 3 ou supérieure.\n" +"\n" +"Il n'y a AUCUNE GARANTIE, dans les limites légales.\n" +"Pour plus de détails, visitez http://gnu.org/licenses/\n" +"\n" +"© 2008-%d Jonathan Westhues et autres auteurs.\n" -#: style.cpp:166 +#: style.cpp:185 msgid "" "Can't assign style to an entity that's derived from another entity; try " "assigning a style to this entity's parent." msgstr "" -"Impossible d'attribuer le style à une entité dérivée d'une autre entité; " +"Impossible d'attribuer le style à une entité dérivée d'une autre entité ; " "Essayez d'attribuer un style au parent de cette entité." -#: style.cpp:665 +#: style.cpp:735 msgid "Style name cannot be empty" msgstr "Le nom d'un style ne peut pas être vide" -#: textscreens.cpp:741 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." msgstr "Je ne peux pas répéter moins de 1 fois." -#: textscreens.cpp:745 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." msgstr "Je ne peux pas répéter plus de 999 fois." -#: textscreens.cpp:770 +#: textscreens.cpp:866 msgid "Group name cannot be empty" msgstr "Un nom de groupe ne peut pas être vide" -#: textscreens.cpp:813 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." msgstr "L'opacité doit être entre 0 et 1." -#: textscreens.cpp:848 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "Le rayon ne peut pas être zéro ou négatif." @@ -1994,7 +2208,7 @@ msgstr "Créer un arc tangent au point sélectionné" #: toolbar.cpp:32 msgid "Sketch cubic Bezier spline" -msgstr "Dessin d'une spline cubique de Bezier" +msgstr "Dessin d'une spline cubique de Bézier" #: toolbar.cpp:34 msgid "Sketch datum point" @@ -2002,7 +2216,7 @@ msgstr "Dessin d'un point" #: toolbar.cpp:36 msgid "Toggle construction" -msgstr "Basculer en mode \"construction\"" +msgstr "Basculer en mode « construction »" #: toolbar.cpp:38 msgid "Split lines / curves where they intersect" @@ -2062,15 +2276,15 @@ msgstr "Nouveau groupe d'extrusion du dessin actif" #: toolbar.cpp:70 msgid "New group rotating active sketch" -msgstr "Nouveau groupe de révolution du dessin actif" +msgstr "Nouveau groupe de révolution du dessin actif (tour complet)" #: toolbar.cpp:72 msgid "New group helix from active sketch" -msgstr "" +msgstr "Nouveau groupe hélice du dessin actif" #: toolbar.cpp:74 msgid "New group revolve active sketch" -msgstr "" +msgstr "Nouveau groupe de révolution partielle du dessin actif" #: toolbar.cpp:76 msgid "New group step and repeat rotating" @@ -2114,15 +2328,176 @@ msgstr "Message" #: util.cpp:170 msgctxt "button" msgid "&OK" -msgstr "" +msgstr "&Ok" -#: view.cpp:78 +#: view.cpp:127 msgid "Scale cannot be zero or negative." msgstr "L'échelle ne peut pas être zéro ou négative." -#: view.cpp:90 view.cpp:99 +#: view.cpp:139 view.cpp:148 msgid "Bad format: specify x, y, z" -msgstr "Mauvais format: Spécifiez x, y, z" +msgstr "Mauvais format : Spécifiez x, y, z" + +#~ msgid "" +#~ "Bad selection for on point / curve / plane constraint. This constraint " +#~ "can apply to:\n" +#~ "\n" +#~ " * two points (points coincident)\n" +#~ " * a point and a workplane (point in plane)\n" +#~ " * a point and a line segment (point on line)\n" +#~ " * a point and a circle or arc (point on curve)\n" +#~ " * a point and a plane face (point on face)\n" +#~ msgstr "" +#~ "Mauvaise sélection pour la contrainte point / courbe / plan. Cette " +#~ "contrainte peut s'appliquer à :\n" +#~ "\n" +#~ " – Deux points (points coïncidents)\n" +#~ " – Un point et un plan de travail (point dans le plan)\n" +#~ " – Un point et un segment de ligne (point en ligne)\n" +#~ " – Un point et un cercle ou un arc (point sur courbe)\n" +#~ " – Un point et une face plane (point sur une face)\n" + +#~ msgid "" +#~ "Bad selection for equal length / radius constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (equal length)\n" +#~ " * two line segments and two points (equal point-line distances)\n" +#~ " * a line segment and two points (equal point-line distances)\n" +#~ " * a line segment, and a point and line segment (point-line distance " +#~ "equals length)\n" +#~ " * four line segments or normals (equal angle between A,B and C,D)\n" +#~ " * three line segments or normals (equal angle between A,B and B,C)\n" +#~ " * two circles or arcs (equal radius)\n" +#~ " * a line segment and an arc (line segment length equals arc length)\n" +#~ msgstr "" +#~ "Mauvaise sélection pour une contrainte de longueur / rayon égale. Cette " +#~ "contrainte peut s'appliquer à :\n" +#~ "\n" +#~ " – Deux segments de ligne (longueur égale)\n" +#~ " – Deux segments de ligne et deux points (distances point-ligne " +#~ "égales)\n" +#~ " – Un segment de ligne et deux points (distances point-ligne égales)\n" +#~ " – Un segment de ligne ou un segment de ligne et point (distance point-" +#~ "ligne de longueur égale)\n" +#~ " – Quatre segments de ligne ou des normales (angle entre A, B et C, D " +#~ "égaux)\n" +#~ " – Trois segments de ligne ou des normales (angle entre A, B et B, C " +#~ "égaux)\n" +#~ " – Deux cercles ou arcs (rayon égaux)\n" +#~ " – Un segment de ligne et un arc (la longueur de segment de ligne est " +#~ "égale à la longueur d'arc)\n" + +#~ msgid "" +#~ "Bad selection for horizontal / vertical constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two points\n" +#~ " * a line segment\n" +#~ msgstr "" +#~ "Mauvaise sélection pour la contrainte horizontale / verticale. Cette " +#~ "contrainte peut s'appliquer à :\n" +#~ "\n" +#~ " – deux points\n" +#~ " – Un segment de ligne\n" + +#~ msgid "" +#~ "Bad selection for angle constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" +#~ msgstr "" +#~ "Mauvaise sélection pour une contrainte d'angle. Cette contrainte peut " +#~ "s'appliquer à :\n" +#~ "\n" +#~ " – Deux segments de ligne\n" +#~ " – Un segment de ligne et une normale\n" +#~ " – Deux normales\n" + +#~ msgid "" +#~ "Bad selection for parallel / tangent constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (parallel)\n" +#~ " * a line segment and a normal (parallel)\n" +#~ " * two normals (parallel)\n" +#~ " * two line segments, arcs, or beziers, that share an endpoint " +#~ "(tangent)\n" +#~ msgstr "" +#~ "Mauvaise sélection pour la contrainte parallèle / tangente. Cette " +#~ "contrainte peut s'appliquer à :\n" +#~ "\n" +#~ " – Deux segments de ligne (parallèles)\n" +#~ " – Un segment de ligne et un parallèle (parallèle)\n" +#~ " – Deux normales (parallèles)\n" +#~ " – Deux segments de ligne, des arcs ou des Béziers, qui partagent une " +#~ "extrémité (tangente)\n" + +#~ msgid "" +#~ "Bad selection for perpendicular constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" +#~ msgstr "" +#~ "Mauvaise sélection pour une contrainte perpendiculaire. Cette contrainte " +#~ "peut s'appliquer à :\n" +#~ "\n" +#~ " – Deux segments de ligne\n" +#~ " – Un segment de ligne et une normale\n" +#~ " – Deux normales\n" + +#~ msgid "A&ngle" +#~ msgstr "A&ngle" + +#~ msgid "E&qual Length / Radius / Angle" +#~ msgstr "É&galité Longueur / Rayon / Angle" + +#~ msgid "" +#~ "Bad selection for length ratio constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ msgstr "" +#~ "Mauvaise sélection pour la contrainte du rapport de longueur. Cette " +#~ "contrainte peut s'appliquer à :\n" +#~ "\n" +#~ "    * Deux segments de ligne\n" + +#~ msgid "" +#~ "Bad selection for length difference constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ msgstr "" +#~ "Mauvaise sélection pour la contrainte de différence de longueur. Cette " +#~ "contrainte peut s'appliquer à :\n" +#~ "\n" +#~ "    * Deux segments de ligne\n" + +#~ msgid "Length Ra&tio" +#~ msgstr "R&apport de Longueur" + +#~ msgid "Length Diff&erence" +#~ msgstr "D&ifférence de Longueur" + +#~ msgid "" +#~ "Bad selection for new sketch in workplane. This group can be created " +#~ "with:\n" +#~ "\n" +#~ " * a point (through the point, orthogonal to coordinate axes)\n" +#~ " * a point and two line segments (through the point, parallel to the " +#~ "lines)\n" +#~ " * a workplane (copy of the workplane)\n" +#~ msgstr "" +#~ "Mauvaise sélection pour un nouveau dessin dans le plan de travail. Ce " +#~ "groupe peut être créé avec :\n" +#~ "\n" +#~ "    * Un point (par le point, orthogonal aux axes de coordonnées)\n" +#~ "    * Un point et deux segments de ligne (par le point, parallèle aux " +#~ "lignes)\n" +#~ "    * Un plan de travail (copie du plan de travail)\n" #~ msgid "Specify between 0 and 8 digits after the decimal." #~ msgstr "Spécifiez entre 0 et 8 chiffres après la virgule." @@ -2136,7 +2511,7 @@ msgstr "Mauvais format: Spécifiez x, y, z" #~ msgid "Do you want to save the changes you made to the new sketch?" #~ msgstr "" #~ "Voulez-vous enregistrer les modifications que vous avez apportées au " -#~ "nouveau dessin?" +#~ "nouveau dessin ?" #~ msgid "Your changes will be lost if you don't save them." #~ msgstr "Vos modifications seront perdues si vous ne les enregistrez pas." @@ -2154,7 +2529,7 @@ msgstr "Mauvais format: Spécifiez x, y, z" #~ msgstr "Ne pas sauver" #~ msgid "Do you want to load the autosave file instead?" -#~ msgstr "Voulez-vous charger le fichier de sauvegarde à la place?" +#~ msgstr "Voulez-vous charger le fichier de sauvegarde à la place ?" #~ msgctxt "button" #~ msgid "Load" @@ -2169,8 +2544,8 @@ msgstr "Mauvais format: Spécifiez x, y, z" #~ "If you select “No”, any geometry that depends on the missing file will be " #~ "removed." #~ msgstr "" -#~ "Voulez-vous le localiser manuellement?\n" -#~ "Si vous sélectionnez \"Non\", toute géométrie qui dépend du fichier " +#~ "Voulez-vous le localiser manuellement ?\n" +#~ "Si vous sélectionnez « Non », toute géométrie qui dépend du fichier " #~ "manquant sera supprimée." #~ msgctxt "button" @@ -2198,7 +2573,7 @@ msgstr "Mauvais format: Spécifiez x, y, z" #~ msgstr "" #~ "Le fichier a changé depuis sa dernière sauvegarde.\n" #~ "\n" -#~ "Voulez-vous enregistrer les modifications?" +#~ "Voulez-vous enregistrer les modifications ?" #~ msgctxt "button" #~ msgid "Do_n't Save" diff --git a/res/locales/ja_JP.po b/res/locales/ja_JP.po new file mode 100644 index 000000000..211c43c52 --- /dev/null +++ b/res/locales/ja_JP.po @@ -0,0 +1,2384 @@ +# Japanese translations for SolveSpace package. +# Copyright (C) 2022 the Solvespace authors +# This file is distributed under the same license as the SolveSpace package. +# verylowfreq, 2022. +# +msgid "" +msgstr "" +"Project-Id-Version: SolveSpace 3.0\n" +"Report-Msgid-Bugs-To: phkahler@gmail.com\n" +"POT-Creation-Date: 2025-01-26 21:04+0200\n" +"PO-Revision-Date: 2025-02-01 10:31+0900\n" +"Last-Translator: https://github.com/verylowfreq\n" +"Language-Team: \n" +"Language: ja_JP\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.5\n" + +#: clipboard.cpp:314 +msgid "" +"Cut, paste, and copy work only in a workplane.\n" +"\n" +"Activate one with Sketch -> In Workplane." +msgstr "" +"切り取り・貼り付け・コピーは作業平面でのみ利用できます。\n" +"\n" +"作業平面は「スケッチ -> 作業平面上でスケッチする」から指定してください。" + +#: clipboard.cpp:331 +msgid "Clipboard is empty; nothing to paste." +msgstr "クリップボードは空です。貼り付けるものはありません。" + +#: clipboard.cpp:378 +msgid "Number of copies to paste must be at least one." +msgstr "貼り付ける個数は1以上でなければなりません。" + +#: clipboard.cpp:394 textscreens.cpp:879 +msgid "Scale cannot be zero." +msgstr "縮尺は0にできません。" + +#: clipboard.cpp:436 +msgid "Select one point to define origin of rotation." +msgstr "回転の原点を決めるために、点を1つ選択してください。" + +#: clipboard.cpp:448 +msgid "Select two points to define translation vector." +msgstr "移動方向を決めるために、点を2つ選択してください。" + +#: clipboard.cpp:458 +msgid "" +"Transformation is identity. So all copies will be exactly on top of each " +"other." +msgstr "移動量が 0 です。すべての複製は同一位置にあります。" + +#: clipboard.cpp:462 +msgid "Too many items to paste; split this into smaller pastes." +msgstr "複製の数が多すぎます。分割してください。" + +#: clipboard.cpp:467 +msgid "No workplane active." +msgstr "作業平面がアクティブでありません。" + +#: confscreen.cpp:410 +msgid "Bad format: specify coordinates as x, y, z" +msgstr "書式が誤っています。座標を \"x, y, z\" で指定してください。" + +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 +msgid "Bad format: specify color as r, g, b" +msgstr "書式が誤っています。色を \"r, g, b\" で指定してください。" + +#: confscreen.cpp:446 +msgid "" +"The perspective factor will have no effect until you enable View -> Use " +"Perspective Projection." +msgstr "" +"透視投影の拡大率は、「表示 -> 透視投影で表示」を有効にするまで反映されませ" +"ん。" + +#: confscreen.cpp:464 confscreen.cpp:474 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "小数点以下の桁数は、0 から %d の範囲で指定してください。" + +#: confscreen.cpp:486 +msgid "Export scale must not be zero!" +msgstr "エクスポートの比率には0を指定できません。" + +#: confscreen.cpp:498 +msgid "Cutter radius offset must not be negative!" +msgstr "ドリル径は負の値にできません。" + +#: confscreen.cpp:557 +msgid "Bad value: autosave interval should be positive" +msgstr "値が誤っています。オートセーブの間隔は正の値を指定してください。" + +#: confscreen.cpp:560 +msgid "Bad format: specify interval in integral minutes" +msgstr "書式が誤っています。間隔は分単位で整数を指定してください。" + +#: constraint.cpp:12 +msgctxt "constr-name" +msgid "pts-coincident" +msgstr "" + +#: constraint.cpp:13 +msgctxt "constr-name" +msgid "pt-pt-distance" +msgstr "" + +#: constraint.cpp:14 +msgctxt "constr-name" +msgid "pt-line-distance" +msgstr "" + +#: constraint.cpp:15 +msgctxt "constr-name" +msgid "pt-plane-distance" +msgstr "" + +#: constraint.cpp:16 +msgctxt "constr-name" +msgid "pt-face-distance" +msgstr "" + +#: constraint.cpp:17 +msgctxt "constr-name" +msgid "proj-pt-pt-distance" +msgstr "" + +#: constraint.cpp:18 +msgctxt "constr-name" +msgid "pt-in-plane" +msgstr "" + +#: constraint.cpp:19 +msgctxt "constr-name" +msgid "pt-on-line" +msgstr "" + +#: constraint.cpp:20 +msgctxt "constr-name" +msgid "pt-on-face" +msgstr "" + +#: constraint.cpp:21 +msgctxt "constr-name" +msgid "eq-length" +msgstr "" + +#: constraint.cpp:22 +msgctxt "constr-name" +msgid "eq-length-and-pt-ln-dist" +msgstr "" + +#: constraint.cpp:23 +msgctxt "constr-name" +msgid "eq-pt-line-distances" +msgstr "" + +#: constraint.cpp:24 +msgctxt "constr-name" +msgid "length-ratio" +msgstr "" + +#: constraint.cpp:25 +msgctxt "constr-name" +msgid "arc-arc-length-ratio" +msgstr "" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "arc-line-length-ratio" +msgstr "" + +#: constraint.cpp:27 +msgctxt "constr-name" +msgid "length-difference" +msgstr "" + +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "arc-arc-len-difference" +msgstr "" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "arc-line-len-difference" +msgstr "" + +#: constraint.cpp:30 +msgctxt "constr-name" +msgid "symmetric" +msgstr "" + +#: constraint.cpp:31 +msgctxt "constr-name" +msgid "symmetric-h" +msgstr "" + +#: constraint.cpp:32 +msgctxt "constr-name" +msgid "symmetric-v" +msgstr "" + +#: constraint.cpp:33 +msgctxt "constr-name" +msgid "symmetric-line" +msgstr "" + +#: constraint.cpp:34 +msgctxt "constr-name" +msgid "at-midpoint" +msgstr "" + +#: constraint.cpp:35 +msgctxt "constr-name" +msgid "horizontal" +msgstr "" + +#: constraint.cpp:36 +msgctxt "constr-name" +msgid "vertical" +msgstr "" + +#: constraint.cpp:37 +msgctxt "constr-name" +msgid "diameter" +msgstr "" + +#: constraint.cpp:38 +msgctxt "constr-name" +msgid "pt-on-circle" +msgstr "" + +#: constraint.cpp:39 +msgctxt "constr-name" +msgid "same-orientation" +msgstr "" + +#: constraint.cpp:40 +msgctxt "constr-name" +msgid "angle" +msgstr "" + +#: constraint.cpp:41 +msgctxt "constr-name" +msgid "parallel" +msgstr "" + +#: constraint.cpp:42 +msgctxt "constr-name" +msgid "arc-line-tangent" +msgstr "" + +#: constraint.cpp:43 +msgctxt "constr-name" +msgid "cubic-line-tangent" +msgstr "" + +#: constraint.cpp:44 +msgctxt "constr-name" +msgid "curve-curve-tangent" +msgstr "" + +#: constraint.cpp:45 +msgctxt "constr-name" +msgid "perpendicular" +msgstr "" + +#: constraint.cpp:46 +msgctxt "constr-name" +msgid "eq-radius" +msgstr "" + +#: constraint.cpp:47 +msgctxt "constr-name" +msgid "eq-angle" +msgstr "" + +#: constraint.cpp:48 +msgctxt "constr-name" +msgid "eq-line-len-arc-len" +msgstr "" + +#: constraint.cpp:49 +msgctxt "constr-name" +msgid "lock-where-dragged" +msgstr "" + +#: constraint.cpp:50 +msgctxt "constr-name" +msgid "comment" +msgstr "" + +#: constraint.cpp:151 +msgid "" +"The point you selected does not belong to the arc. The arc and line segment " +"do not share an end point.\n" +"\n" +"Select the end point of the arc at which you want it to be tangent to the " +"line." +msgstr "" +"選択された点は円弧上にありません。円弧と直線が同じ端点を共有していません。\n" +"\n" +"線分に正接させたい円弧の端点を選択してください。" + +#: constraint.cpp:158 +msgid "" +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be " +"tangent to the line." +msgstr "" +"正接する円弧と線分は端点を共有している必要があります。正接拘束をする前に「拘" +"束 -> 一致」で拘束してください。\n" +"\n" +"もしくは、線分に正接させたい円弧の端点を選択してください。" + +#: constraint.cpp:186 +msgid "" +"The point you selected is not an end point of the cubic spline. The spline " +"and line segment do not share an end point.\n" +"\n" +"Select the end point of the spline at which you want it to be tangent to the " +"line." +msgstr "" +"選択された点は三次スプラインの端点ではありません。スプラインと線分が同じ端点" +"を共有していません。\n" +"\n" +"線分に正接させたいスプラインの端点を選択してください。" + +#: constraint.cpp:193 +msgid "" +"The tangent cubic spline and line segment must share an endpoint. Constrain " +"them with Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it " +"to be tangent to the line." +msgstr "" +"ベジェ曲線と線分は端点を共有している必要があります。正接拘束をする前に「拘束 " +"-> 一致」で拘束してください。\n" +"\n" +"もしくは、線分に正接させたいベジェ曲線の端点を選択してください。" + +#: constraint.cpp:237 +msgid "" +"The points you selected are not end points of the two curves. The curves do " +"not share an end point.\n" +"\n" +"Select the end points of both curves at which you want them to be tangent to " +"each other." +msgstr "" +"選択された点は2つのカーブの端点ではありません。カーブが同じ端点を共有していま" +"せん。\n" +"\n" +"互いに正接させたいカーブの共通の端点を選択してください。" + +#: constraint.cpp:244 +msgid "" +"The curves must share an endpoint. Constrain them with Constrain -> On Point " +"before constraining tangent.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." +msgstr "" +"曲線同士は端点を共有している必要があります。正接拘束をする前に「拘束 -> 一" +"致」で拘束してください。\n" +"\n" +"もしくは、正接させたい曲線の共通の端点を選択してください。" + +#: constraint.cpp:303 +msgid "" +"Bad selection for distance / diameter constraint. This constraint can apply " +"to:\n" +"\n" +" * two points (distance between points)\n" +" * a line segment (length)\n" +" * two points and a line segment or normal (projected distance)\n" +" * a workplane and a point (minimum distance)\n" +" * a line segment and a point (minimum distance)\n" +" * a plane face and a point (minimum distance)\n" +" * a circle or an arc (diameter)\n" +msgstr "" +"距離・直径拘束ができませんでした。次の要素に適用できます:\n" +"\n" +" * 2 つの点 (点同士の距離)\n" +" * 1 つの線分 (長さ)\n" +" * 2 つの点と、1 つの線分もしくは法線 (投影された距離)\n" +" * 1 つの作業平面と 1 つの点 (最小の距離)\n" +" * 1 つの線分と 1 つの点 (最小の距離)\n" +" * 1 つの平面フェイスと1つの点 (最小の距離)\n" +" * 1 つの円もしくは円弧 (直径)\n" +"\n" + +#: constraint.cpp:366 +msgid "" +"Bad selection for on point / curve / plane constraint. This constraint can " +"apply to:\n" +"\n" +" * two or more points (points coincident)\n" +" * a point and a workplane (point in plane)\n" +" * a point and a line segment (point on line)\n" +" * a point and a circle or arc (point on curve)\n" +" * a point and one to three plane faces (point on face(s))\n" +msgstr "" +"一致拘束ができませんでした。次の要素に適用できます。\n" +"\n" +" * 2 つの点 (点を同一位置に)\n" +" * 1 つの点と 1 つの作業平面 (点を平面上に)\n" +" * 1 つの点と 1 つの線分 (点を直線状に)\n" +" * 1 つの点と、1つの円もしくは円弧 (点を曲線上に)\n" +" * 1 つの点と、1 から3つの平面フェイス (点をフェイス上に)\n" + +#: constraint.cpp:427 +msgid "" +"Bad selection for equal length / radius constraint. This constraint can " +"apply to:\n" +"\n" +" * two or more line segments (equal length)\n" +" * two line segments and two points (equal point-line distances)\n" +" * a line segment and two points (equal point-line distances)\n" +" * a line segment, and a point and line segment (point-line distance " +"equals length)\n" +" * two or more circles or arcs (equal radius)\n" +" * a line segment and an arc (line segment length equals arc length)\n" +msgstr "" +"長さ・半径の同値拘束ができませんでした。次の要素に適用できます:\n" +"\n" +" * 2 つ以上の線分 (長さを同じに)\n" +" * 2 つの線分と 2 つの点 (それぞれ直線と点の距離を同じに)\n" +" * 1 つの線分と 2 つの点 (直線とそれぞれの点の距離を同じに)\n" +" * 1 つの線分と、1 つの点と 1 つの線分 (線分の長さと、直線と点の距離を同じ" +"に)\n" +" * 2 つ以上の円もしくは円弧 (半径を同じに)\n" +" * 1 つの線分と 1 つの円弧 (線分の長さと円弧の長さを同じに)\n" + +#: constraint.cpp:480 +msgid "" +"Bad selection for length ratio constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" +msgstr "" +"長さの比率の拘束ができませんでした。次の要素に適用できます:\n" +"\n" +" * 2 つの線分\n" +" * 2 つの円弧\n" +" * 円弧と線分\n" + +#: constraint.cpp:515 +msgid "" +"Bad selection for length difference constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" +msgstr "" +"長さの差の拘束ができませんでした。次の要素に適用できます:\n" +"\n" +" * 2 つの線分\n" +" * 2 つの円弧\n" +" * 円弧と線分\n" + +#: constraint.cpp:550 +msgid "" +"Bad selection for at midpoint constraint. This constraint can apply to:\n" +"\n" +" * a line segment and a point (point at midpoint)\n" +" * a line segment and a workplane (line's midpoint on plane)\n" +msgstr "" +"中点拘束できませんでした。次の要素に適用できます:\n" +"\n" +" * 線分と点 (点を直線の中点に置きます)\n" +" * 線分と作業平面 (直線の中点を作業平面上に置きます)\n" + +#: constraint.cpp:608 +msgid "" +"Bad selection for symmetric constraint. This constraint can apply to:\n" +"\n" +" * two points or a line segment (symmetric about workplane's coordinate " +"axis)\n" +" * line segment, and two points or a line segment (symmetric about line " +"segment)\n" +" * workplane, and two points or a line segment (symmetric about " +"workplane)\n" +msgstr "" +"対象拘束ができませんでした。次の要素の適用できます:\n" +"\n" +" * 2 つの点、もしくは 1 つの線分 (作業平面の座標軸について対称をとります)\n" +" * 線分と、2 つの点もしくは 1 つの線分 (線分について対称をとります)\n" +" * 作業平面と、2 つの点もしくは 1 つの線分 (作業平面について対称をとりま" +"す)\n" + +#: constraint.cpp:623 +msgid "" +"A workplane must be active when constraining symmetric without an explicit " +"symmetry plane." +msgstr "" +"明示的に対称平面を指定しない場合は、対象拘束をするには作業平面をアクティブに" +"してください。" + +#: constraint.cpp:663 +msgid "" +"Activate a workplane (with Sketch -> In Workplane) before applying a " +"horizontal or vertical constraint." +msgstr "" +"垂直拘束や水平拘束をする前に、「スケッチ -> 作業平面上でスケッチする」で作業" +"平面をアクティブにしてください。" + +#: constraint.cpp:679 +msgid "" +"Bad selection for horizontal / vertical constraint. This constraint can " +"apply to:\n" +"\n" +" * two or more points\n" +" * one or more line segments\n" +msgstr "" +"垂直・水平拘束ができませんでした。次の要素に適用できます:\n" +"\n" +" * 2つ以上の点\n" +" * 1つ以上の線分\n" + +#: constraint.cpp:697 +msgid "" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" +"\n" +" * two normals\n" +msgstr "" +"同じ方向の拘束ができませんでした。次の要素に適用できます。\n" +"\n" +" * 2 つの法線\n" + +#: constraint.cpp:748 +msgid "Must select an angle constraint." +msgstr "角度拘束を選択してください。" + +#: constraint.cpp:761 +msgid "Must select a constraint with associated label." +msgstr "値を持つ拘束を選択してください。" + +#: constraint.cpp:784 +msgid "" +"Bad selection for angle constraint. This constraint can apply to:\n" +"\n" +"Angle between:\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +"\n" +"Equal angles:\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" +msgstr "" +"角度拘束ができませんでした。\n" +"\n" +"次の要素間の角度に適用できます:\n" +" * 2つの線分\n" +" * 1つの線分と1つの法線\n" +" * 2つの法線\n" +"\n" +"次の要素を同じ角度に拘束できます:\n" +" * 4つの線分もしくは法線 (AとB、CとDの角度を同じにする)\n" +" * 3つの線分もしくは法線 (AとB、BとCの角度を同じにする)\n" + +#: constraint.cpp:872 +msgid "Curve-curve tangency must apply in workplane." +msgstr "曲線同士の正接は作業平面上で適用できます。" + +#: constraint.cpp:887 +msgid "" +"Bad selection for parallel / tangent constraint. This constraint can apply " +"to:\n" +"\n" +" * two faces\n" +" * two or more line segments (parallel)\n" +" * one or more line segments and one or more normals (parallel)\n" +" * two or more normals (parallel)\n" +" * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +" * two line segments, arcs, or beziers, that do not share an endpoint and " +"the end point(s) of the curve(s) (tangent)\n" +msgstr "" +"平行・正接拘束ができませんでした。次の要素に適用できます:\n" +"\n" +" * 2つのフェイス\n" +" * 2つ以上の線分 (平行に)\n" +" * 1つ以上の線分と1つ以上の法線 (平行に)\n" +" * 2つ以上の法線 (平行に)\n" +" * 端点の1つが一致する、2つの線分か円弧かベジェ曲線 (正接に)\n" +" * 正接する曲線と端点を共有しない、2つの線分か円弧かベジェ曲線\n" + +#: constraint.cpp:914 +msgid "" +"Bad selection for perpendicular constraint. This constraint can apply to:\n" +"\n" +" * two faces\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"垂線拘束ができませんでした。次の要素に適用できます:\n" +"\n" +" * 2つのフェイス\n" +" * 2つの線分\n" +" * 1つの線分と1つの法線\n" +" * 2つの法線\n" + +#: constraint.cpp:931 +msgid "" +"Bad selection for lock point where dragged constraint. This constraint can " +"apply to:\n" +"\n" +" * a point\n" +msgstr "" +"点の位置の拘束 (点を他のオブジェクトに連動して動かさない) ができませんでし" +"た。次の要素に適用できます:\n" +"\n" +" * 1 つの点\n" + +#: constraint.cpp:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "(コメント - ダブルクリックで編集)" + +#: constraint.cpp:952 +msgid "click center of comment text" +msgstr "コメントテキストの中心をクリックで指定" + +#: export.cpp:19 +msgid "" +"No solid model present; draw one with extrudes and revolves, or use Export " +"2d View to export bare lines and curves." +msgstr "" +"ソリッドモデルがありません。押し出しや周回、回転を利用するか、平面への投影視" +"点のエクスポートで直線や曲線をそのまま出力します。" + +#: export.cpp:61 +msgid "" +"Bad selection for export section. Please select:\n" +"\n" +" * nothing, with an active workplane (workplane is section plane)\n" +" * a face (section plane through face)\n" +" * a point and two line segments (plane through point and parallel to " +"lines)\n" +msgstr "" +"2D断面の出力ができませんでした。次のように選択してください:\n" +"\n" +" * なにも選択せず、作業平面がアクティブである (作業平面を断面とします)\n" +" * フェイス (フェイスを通る断面を用います)\n" +" * 1 つの点と 2 つの線分 (点を通り直線と平行する断面を用います)\n" + +#: export.cpp:818 +msgid "Active group mesh is empty; nothing to export." +msgstr "" +"アクティブなグループのメッシュは空です。エクスポートは行なわれませんでした。" + +#: exportvector.cpp:336 +msgid "freehand lines were replaced with continuous lines" +msgstr "フリーハンドの線は実線に置き換えられました。" + +#: exportvector.cpp:338 +msgid "zigzag lines were replaced with continuous lines" +msgstr "ジグザグ線は実線に置き換えられました。" + +#: exportvector.cpp:592 +msgid "" +"Some aspects of the drawing have no DXF equivalent and were not exported:\n" +msgstr "" +"図面のうち一部の側面は、対応するものがDXFにないため、それらは出力されませんで" +"した:\n" + +#: exportvector.cpp:838 +msgid "" +"PDF page size exceeds 200 by 200 inches; many viewers may reject this file." +msgstr "" +"PDFのページ寸法が 200x200 インチを超えています。多くのPDFリーダーでは正常に読" +"み込めません。" + +#: file.cpp:44 group.cpp:91 +msgctxt "group-name" +msgid "sketch-in-plane" +msgstr "" + +#: file.cpp:62 +msgctxt "group-name" +msgid "#references" +msgstr "" + +#: file.cpp:555 +msgid "The file is empty. It may be corrupt." +msgstr "ファイルが空です。ファイルが破損している可能性があります。" + +#: file.cpp:560 +msgid "" +"Unrecognized data in file. This file may be corrupt, or from a newer version " +"of the program." +msgstr "" +"ファイルに処理できないデータが含まれています。ファイルが破損しているか、より" +"新しいバージョンで作成されたファイルの可能性があります。" + +#: file.cpp:876 +msgctxt "title" +msgid "Missing File" +msgstr "不明ファイル" + +#: file.cpp:877 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "リンクされたファイル \"%s\" が見つかりません。" + +#: file.cpp:879 +msgctxt "dialog" +msgid "" +"Do you want to locate it manually?\n" +"\n" +"If you decline, any geometry that depends on the missing file will be " +"permanently removed." +msgstr "" +"手動で配置しますか?\n" +"\n" +"キャンセルした場合、不明なファイルに依存しているすべての寸法は削除されます。" + +#: file.cpp:882 +msgctxt "button" +msgid "&Yes" +msgstr "はい (&Y)" + +#: file.cpp:884 +msgctxt "button" +msgid "&No" +msgstr "いいえ (&N)" + +#: file.cpp:886 solvespace.cpp:652 +msgctxt "button" +msgid "&Cancel" +msgstr "キャンセル (&C)" + +#: graphicswin.cpp:41 +msgid "&File" +msgstr "ファイル (&F)" + +#: graphicswin.cpp:42 +msgid "&New" +msgstr "新規作成 (&N)" + +#: graphicswin.cpp:43 +msgid "&Open..." +msgstr "開く... (&O)" + +#: graphicswin.cpp:44 +msgid "Open &Recent" +msgstr "最近使用したファイル (&R)" + +#: graphicswin.cpp:45 +msgid "&Save" +msgstr "保存 (&S)" + +#: graphicswin.cpp:46 +msgid "Save &As..." +msgstr "名前を付けて保存... (&A)" + +#: graphicswin.cpp:48 +msgid "Export &Image..." +msgstr "画像をエクスポート... (&I)" + +#: graphicswin.cpp:49 +msgid "Export 2d &View..." +msgstr "平面への投影視点をエクスポート... (&V)" + +#: graphicswin.cpp:50 +msgid "Export 2d &Section..." +msgstr "2D断面をエクスポート... (&S)" + +#: graphicswin.cpp:51 +msgid "Export 3d &Wireframe..." +msgstr "三次元ワイヤーフレームをエクスポート... (&W)" + +#: graphicswin.cpp:52 +msgid "Export Triangle &Mesh..." +msgstr "三角メッシュをエクスポート... (&M)" + +#: graphicswin.cpp:53 +msgid "Export &Surfaces..." +msgstr "サーフェスをエクスポート... (&S)" + +#: graphicswin.cpp:54 +msgid "Im&port..." +msgstr "インポート... (&I)" + +#: graphicswin.cpp:57 +msgid "E&xit" +msgstr "終了 (&E)" + +#: graphicswin.cpp:60 +msgid "&Edit" +msgstr "編集 (&E)" + +#: graphicswin.cpp:61 +msgid "&Undo" +msgstr "取り消し (&U)" + +#: graphicswin.cpp:62 +msgid "&Redo" +msgstr "やり直し (&R)" + +#: graphicswin.cpp:63 +msgid "Re&generate All" +msgstr "すべて再生成 (&g)" + +#: graphicswin.cpp:65 +msgid "Snap Selection to &Grid" +msgstr "選択したオブジェクトをグリッドに合わせる (&G)" + +#: graphicswin.cpp:66 +msgid "Rotate Imported &90°" +msgstr "リンクした部品を90度回転する (&9)" + +#: graphicswin.cpp:68 +msgid "Cu&t" +msgstr "切り取り (&t)" + +#: graphicswin.cpp:69 +msgid "&Copy" +msgstr "コピー (&C)" + +#: graphicswin.cpp:70 +msgid "&Paste" +msgstr "貼り付け (&P)" + +#: graphicswin.cpp:71 +msgid "Paste &Transformed..." +msgstr "複製しながら貼り付け... (&T)" + +#: graphicswin.cpp:72 +msgid "&Delete" +msgstr "削除 (&D)" + +#: graphicswin.cpp:74 +msgid "Select &Edge Chain" +msgstr "接続しているエッジを選択" + +#: graphicswin.cpp:75 +msgid "Select &All" +msgstr "すべてを選択 (&A)" + +#: graphicswin.cpp:76 +msgid "&Unselect All" +msgstr "選択を解除 (&U)" + +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "線のスタイル... (&L)" + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "カメラの設定... (&V)" + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "設定 (&f)" + +#: graphicswin.cpp:84 +msgid "&View" +msgstr "表示 (&V)" + +#: graphicswin.cpp:85 +msgid "Zoom &In" +msgstr "拡大 (&I)" + +#: graphicswin.cpp:86 +msgid "Zoom &Out" +msgstr "縮小 (&O)" + +#: graphicswin.cpp:87 +msgid "Zoom To &Fit" +msgstr "選択オブジェクトの最適表示" + +#: graphicswin.cpp:89 +msgid "Align View to &Workplane" +msgstr "作業平面に合わせる (&W)" + +#: graphicswin.cpp:90 +msgid "Nearest &Ortho View" +msgstr "近傍の正面図視点へ移動 (&O)" + +#: graphicswin.cpp:91 +msgid "Nearest &Isometric View" +msgstr "近傍の等角図視点へ移動 (&I)" + +#: graphicswin.cpp:92 +msgid "&Center View At Point" +msgstr "点を表示の中心にする" + +#: graphicswin.cpp:94 +msgid "Show Snap &Grid" +msgstr "グリッドを表示 (&G)" + +#: graphicswin.cpp:95 +msgid "Darken Inactive Solids" +msgstr "アクティブでないソリッドを暗くする" + +#: graphicswin.cpp:96 +msgid "Use &Perspective Projection" +msgstr "透視投影で表示 (&P)" + +#: graphicswin.cpp:97 +msgid "Show E&xploded View" +msgstr "分解立体図を表示 (&x)" + +#: graphicswin.cpp:98 +msgid "Dimension &Units" +msgstr "表示単位の選択 (&U)" + +#: graphicswin.cpp:99 +msgid "Dimensions in &Millimeters" +msgstr "ミリメートル (&M)" + +#: graphicswin.cpp:100 +msgid "Dimensions in M&eters" +msgstr "メートル (&e)" + +#: graphicswin.cpp:101 +msgid "Dimensions in &Inches" +msgstr "インチ (&I)" + +#: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "フィート・インチ (&F)" + +#: graphicswin.cpp:104 +msgid "Show &Toolbar" +msgstr "ツールバーを表示 (&T)" + +#: graphicswin.cpp:105 +msgid "Show Property Bro&wser" +msgstr "プロパティブラウザを表示 (&w)" + +#: graphicswin.cpp:107 +msgid "&Full Screen" +msgstr "全画面表示 (&F)" + +#: graphicswin.cpp:109 +msgid "&New Group" +msgstr "グループ (&N)" + +#: graphicswin.cpp:110 +msgid "Sketch In &3d" +msgstr "三次元空間内でスケッチを始める (&3)" + +#: graphicswin.cpp:111 +msgid "Sketch In New &Workplane" +msgstr "新しい作業平面上でスケッチを始める (&W)" + +#: graphicswin.cpp:113 +msgid "Step &Translating" +msgstr "直線上にコピー (&T)" + +#: graphicswin.cpp:114 +msgid "Step &Rotating" +msgstr "円上にコピー (&R)" + +#: graphicswin.cpp:116 +msgid "E&xtrude" +msgstr "押し出し (&x)" + +#: graphicswin.cpp:117 +msgid "&Helix" +msgstr "周回 (&H)" + +#: graphicswin.cpp:118 +msgid "&Lathe" +msgstr "螺旋 (&L)" + +#: graphicswin.cpp:119 +msgid "Re&volve" +msgstr "回転 (&v)" + +#: graphicswin.cpp:121 +msgid "Link / Assemble..." +msgstr "リンク・アセンブル..." + +#: graphicswin.cpp:122 +msgid "Link Recent" +msgstr "最近使用したファイルとリンク" + +#: graphicswin.cpp:124 +msgid "&Sketch" +msgstr "スケッチ (&S)" + +#: graphicswin.cpp:125 +msgid "In &Workplane" +msgstr "作業平面上でスケッチする (&W)" + +#: graphicswin.cpp:126 +msgid "Anywhere In &3d" +msgstr "三次元空間内でスケッチする (&3)" + +#: graphicswin.cpp:128 +msgid "Datum &Point" +msgstr "点 (&P)" + +#: graphicswin.cpp:129 +msgid "Wor&kplane" +msgstr "作業平面 (&k)" + +#: graphicswin.cpp:131 +msgid "Line &Segment" +msgstr "線分 (&S)" + +#: graphicswin.cpp:132 +msgid "C&onstruction Line Segment" +msgstr "補助線の線分 (&o)" + +#: graphicswin.cpp:133 +msgid "&Rectangle" +msgstr "四角形 (&R)" + +#: graphicswin.cpp:134 +msgid "&Circle" +msgstr "円 (&C)" + +#: graphicswin.cpp:135 +msgid "&Arc of a Circle" +msgstr "円弧 (&A)" + +#: graphicswin.cpp:136 +msgid "&Bezier Cubic Spline" +msgstr "ベジェ曲線 (&B)" + +#: graphicswin.cpp:138 +msgid "&Text in TrueType Font" +msgstr "TrueTypeフォントのテキスト (&T)" + +#: graphicswin.cpp:139 +msgid "I&mage" +msgstr "画像 (&m)" + +#: graphicswin.cpp:141 +msgid "To&ggle Construction" +msgstr "補助線との切り替え (&g)" + +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" +msgstr "フィレット(&n)" + +#: graphicswin.cpp:143 +msgid "Split Curves at &Intersection" +msgstr "交差箇所で分割 (&I)" + +#: graphicswin.cpp:145 +msgid "&Constrain" +msgstr "拘束 (&C)" + +#: graphicswin.cpp:146 +msgid "&Distance / Diameter" +msgstr "距離・直径 (&D)" + +#: graphicswin.cpp:147 +msgid "Re&ference Dimension" +msgstr "参照寸法 (&f)" + +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "指定角度 / 角度を等しく (&n)" + +#: graphicswin.cpp:149 +msgid "Reference An&gle" +msgstr "参照角度 (&g)" + +#: graphicswin.cpp:150 +msgid "Other S&upplementary Angle" +msgstr "補角 (&u)" + +#: graphicswin.cpp:151 +msgid "Toggle R&eference Dim" +msgstr "拘束と参照を切り替え (&e)" + +#: graphicswin.cpp:153 +msgid "&Horizontal" +msgstr "水平 (&H)" + +#: graphicswin.cpp:154 +msgid "&Vertical" +msgstr "垂直 (&V)" + +#: graphicswin.cpp:156 +msgid "&On Point / Curve / Plane" +msgstr "一致 (&O)" + +#: graphicswin.cpp:157 +msgid "E&qual Length / Radius" +msgstr "長さ・半径を等値にする (&q)" + +#: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "長さ・円弧の比率 (&t)" + +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "長さ・円弧の差 (&e)" + +#: graphicswin.cpp:160 +msgid "At &Midpoint" +msgstr "中点 (&M)" + +#: graphicswin.cpp:161 +msgid "S&ymmetric" +msgstr "対称 (&y)" + +#: graphicswin.cpp:162 +msgid "Para&llel / Tangent" +msgstr "平行・正接 (&l)" + +#: graphicswin.cpp:163 +msgid "&Perpendicular" +msgstr "垂線 (&P)" + +#: graphicswin.cpp:164 +msgid "Same Orient&ation" +msgstr "同じ方向 (&a)" + +#: graphicswin.cpp:165 +msgid "Lock Point Where &Dragged" +msgstr "点を他のオブジェクトに連動して動かさない (&D)" + +#: graphicswin.cpp:167 +msgid "Comment" +msgstr "コメントを配置" + +#: graphicswin.cpp:169 +msgid "&Analyze" +msgstr "解析 (&A)" + +#: graphicswin.cpp:170 +msgid "Measure &Volume" +msgstr "体積を計測 (&V)" + +#: graphicswin.cpp:171 +msgid "Measure A&rea" +msgstr "面積を計測 (&r)" + +#: graphicswin.cpp:172 +msgid "Measure &Perimeter" +msgstr "外周長を計測 (&P)" + +#: graphicswin.cpp:173 +msgid "Show &Interfering Parts" +msgstr "部品の干渉を強調表示 (&I)" + +#: graphicswin.cpp:174 +msgid "Show &Naked Edges" +msgstr "浮いたエッジを強調表示 (&N)" + +#: graphicswin.cpp:175 +msgid "Show &Center of Mass" +msgstr "重心を表示 (&C)" + +#: graphicswin.cpp:177 +msgid "Show &Underconstrained Points" +msgstr "拘束されていない点を強調表示 (&U)" + +#: graphicswin.cpp:179 +msgid "&Trace Point" +msgstr "点の位置の記録を開始 (&T)" + +#: graphicswin.cpp:180 +msgid "&Stop Tracing..." +msgstr "記録を終了して保存する... (&S)" + +#: graphicswin.cpp:181 +msgid "Step &Dimension..." +msgstr "寸法を段階的に変更... (&D)" + +#: graphicswin.cpp:183 +msgid "&Help" +msgstr "ヘルプ (&H)" + +#: graphicswin.cpp:184 +msgid "&Language" +msgstr "言語 (&L)" + +#: graphicswin.cpp:185 +msgid "&Website / Manual" +msgstr "Webサイト・マニュアル" + +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "GitHubの該当コミットへ移動 (&G)" + +#: graphicswin.cpp:188 +msgid "&About" +msgstr "SolveSpaceについて (&A)" + +#: graphicswin.cpp:362 +msgid "(no recent files)" +msgstr "(履歴なし)" + +#: graphicswin.cpp:370 +#, c-format +msgid "File '%s' does not exist." +msgstr "ファイル '%s' は存在しません。" + +#: graphicswin.cpp:779 +msgid "No workplane is active, so the grid will not appear." +msgstr "作業平面がアクティブでないため、グリッドは表示されません。" + +#: graphicswin.cpp:794 +msgid "" +"The perspective factor is set to zero, so the view will always be a parallel " +"projection.\n" +"\n" +"For a perspective projection, modify the perspective factor in the " +"configuration screen. A value around 0.3 is typical." +msgstr "" +"透視投影の係数が 0 になっているため、表示は常に平行投影になります。\n" +"\n" +"透視投影にするには、係数を configuration から変更してください。値は 0.3 くら" +"いが一般的です。" + +#: graphicswin.cpp:884 +msgid "" +"Select a point; this point will become the center of the view on screen." +msgstr "スクリーンの中央とする点を指定してください。" + +#: graphicswin.cpp:1193 +msgid "No additional entities share endpoints with the selected entities." +msgstr "選択した要素と端点を共有する要素は、これ以上ありません。" + +#: graphicswin.cpp:1211 +msgid "" +"To use this command, select a point or other entity from an linked part, or " +"make a link group the active group." +msgstr "" +"このコマンドを利用するには、リンクされた部品の要素や点を選択するか、リンクの" +"グループをアクティブにしてください。" + +#: graphicswin.cpp:1234 +msgid "" +"No workplane is active. Activate a workplane (with Sketch -> In Workplane) " +"to define the plane for the snap grid." +msgstr "" +"作業平面がアクティブではありません。グリッドの平面を指定するには作業平面をア" +"クティブにしてください (「スケッチ -> 作業平面上でスケッチする」)。" + +#: graphicswin.cpp:1241 +msgid "" +"Can't snap these items to grid; select points, text comments, or constraints " +"with a label. To snap a line, select its endpoints." +msgstr "" +"この要素をグリッドに合わせることはできません。点、コメントテキスト、もしくは" +"値のある拘束を選択してください。直線をグリッドに合わせるには、端点を選択して" +"ください。" + +#: graphicswin.cpp:1326 +msgid "No workplane selected. Activating default workplane for this group." +msgstr "" +"作業平面がアクティブではありません。現在のグループのデフォルトの作業平面をア" +"クティブにします。" + +#: graphicswin.cpp:1329 +msgid "" +"No workplane is selected, and the active group does not have a default " +"workplane. Try selecting a workplane, or activating a sketch-in-new-" +"workplane group." +msgstr "" +"作業平面がアクティブではありません。また、現在のグループにはデフォルトの作業" +"平面がありません。作業平面を選択するか、新しく作業平面グループを作成してくだ" +"さい。" + +#: graphicswin.cpp:1350 +msgid "" +"Bad selection for tangent arc at point. Select a single point, or select " +"nothing to set up arc parameters." +msgstr "" +"フィェットの作成には無効な選択です。ひとつの点が選択されていればフィレットを" +"作成します。なにも選択されていなければ、フィレット作成時のパラメーターを設定" +"できます。" + +#: graphicswin.cpp:1361 +msgid "click point on arc (draws anti-clockwise)" +msgstr "円弧上の点をクリックで指定 (反時計回りに描きます)" + +#: graphicswin.cpp:1362 +msgid "click to place datum point" +msgstr "点の位置をクリックで指定" + +#: graphicswin.cpp:1363 +msgid "click first point of line segment" +msgstr "線分の最初の点をクリックで指定" + +#: graphicswin.cpp:1365 +msgid "click first point of construction line segment" +msgstr "補助線の線分の最初の点をクリックで指定" + +#: graphicswin.cpp:1366 +msgid "click first point of cubic segment" +msgstr "ベジェ曲線の最初の点をクリックで指定" + +#: graphicswin.cpp:1367 +msgid "click center of circle" +msgstr "円の中央をクリックで指定" + +#: graphicswin.cpp:1368 +msgid "click origin of workplane" +msgstr "作業平面の原点をクリックで指定" + +#: graphicswin.cpp:1369 +msgid "click one corner of rectangle" +msgstr "四角形の角のひとつをクリックで指定" + +#: graphicswin.cpp:1370 +msgid "click top left of text" +msgstr "テキストの右上をクリックで指定する" + +#: graphicswin.cpp:1376 +msgid "click top left of image" +msgstr "画像の右上をクリックで指定する" + +#: graphicswin.cpp:1402 +msgid "" +"No entities are selected. Select entities before trying to toggle their " +"construction state." +msgstr "" +"スケッチ要素が選択されていません。拘束と参照を切り替えるには、最初に要素を選" +"択してください。" + +#: group.cpp:86 +msgctxt "group-name" +msgid "sketch-in-3d" +msgstr "" + +#: group.cpp:154 +msgid "" +"Bad selection for new sketch in workplane. This group can be created with:\n" +"\n" +" * a point (through the point, orthogonal to coordinate axes)\n" +" * a point and two line segments (through the point, parallel to the " +"lines)\n" +" * a point and a normal (through the point, orthogonal to the normal)\n" +" * a workplane (copy of the workplane)\n" +msgstr "" +"作業平面にスケッチを作成できませんでした。次のように選択してください。\n" +"\n" +" * 点 (点を通り、座標軸に直交する作業平面を作成します)\n" +" * 点と、2 つの線分 (点を通り、直線に平行する作業平面を作成します)\n" +" * 点と、法線 (点を通り、法線と直交する作業平面を作成します)\n" +" * 作業平面 (あるいは作業平面のコピー)\n" + +#: group.cpp:170 +msgid "" +"Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " +"will be extruded normal to the workplane." +msgstr "" +"押し出しを作成する前に、「スケッチ -> 作業平面上でスケッチする」で作業平面を" +"アクティブにしてください。作業平面の法線方向へ押し出しを作成します。" + +#: group.cpp:179 +msgctxt "group-name" +msgid "extrude" +msgstr "" + +#: group.cpp:184 +msgid "Lathe operation can only be applied to planar sketches." +msgstr "周回操作は作業平面上にあるスケッチにのみ適用できます。" + +#: group.cpp:195 +msgid "" +"Bad selection for new lathe group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"周回を作成できませんでした。次のように選択してください。\n" +"\n" +" * 点と、線分もしくは法線 (点を通る回転軸を仮定し、直線・法線に平行に回転し" +"ます)\n" +" * 線分 (直線を回転軸とします)\n" + +#: group.cpp:205 +msgctxt "group-name" +msgid "lathe" +msgstr "" + +#: group.cpp:210 +msgid "Revolve operation can only be applied to planar sketches." +msgstr "回転操作は作業平面上にあるスケッチにのみ適用できます。" + +#: group.cpp:221 +msgid "" +"Bad selection for new revolve group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"回転を作成できませんでした。次のように選択してください。\n" +"\n" +" * 点と、線分もしくは法線 (点を通る回転軸を仮定し、直線・法線に平行に回転し" +"ます)\n" +" * 線分 (直線を回転軸とします)\n" + +#: group.cpp:233 +msgctxt "group-name" +msgid "revolve" +msgstr "" + +#: group.cpp:238 +msgid "Helix operation can only be applied to planar sketches." +msgstr "螺旋操作は作業平面上にあるスケッチにのみ適用できます。" + +#: group.cpp:249 +msgid "" +"Bad selection for new helix group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"螺旋を作成できませんでした。次のように選択してください。\n" +"\n" +" * 点と、線分もしくは法線 (点を通る回転軸を仮定し、直線・法線に平行に螺旋を" +"作成します)\n" +" * 線分 (直線を回転軸とします)\n" + +#: group.cpp:261 +msgctxt "group-name" +msgid "helix" +msgstr "" + +#: group.cpp:274 +msgid "" +"Bad selection for new rotation. This group can be created with:\n" +"\n" +" * a point, while locked in workplane (rotate in plane, about that " +"point)\n" +" * a point and a line or a normal (rotate about an axis through the " +"point, and parallel to line / normal)\n" +msgstr "" +"円上にコピーを作成できませんでした。次のように選択してください。\n" +"\n" +" * 作業平面上にある点 (作業平面上で、これを原点として周回します)\n" +" * 点と、直線もしくは法線 (点を通る回転軸を仮定し、直線・法線と平行に周回し" +"ます)\n" + +#: group.cpp:287 +msgctxt "group-name" +msgid "rotate" +msgstr "" + +#: group.cpp:298 +msgctxt "group-name" +msgid "translate" +msgstr "" + +#: group.cpp:422 +msgid "(unnamed)" +msgstr "(名称未設定)" + +#: groupmesh.cpp:710 +msgid "not closed contour, or not all same style!" +msgstr "" +"閉じた輪郭でない、もしくは、輪郭を構成する要素のスタイルが同一ではありませ" +"ん。" + +#: groupmesh.cpp:723 +msgid "points not all coplanar!" +msgstr "点が同一平面上にありません。" + +#: groupmesh.cpp:725 +msgid "contour is self-intersecting!" +msgstr "輪郭線が自己交差しています。" + +#: groupmesh.cpp:727 +msgid "zero-length edge!" +msgstr "長さが 0 のエッジです。" + +#: importmesh.cpp:136 +msgid "Text-formated STL files are not currently supported" +msgstr "テキスト形式のSTLファイルは、いまのところサポートされていません。" + +#: modify.cpp:252 +msgid "Must be sketching in workplane to create tangent arc." +msgstr "正接円弧をスケッチするには、作業平面上でスケッチしてください。" + +#: modify.cpp:299 +msgid "" +"To create a tangent arc, select a point where two non-construction lines or " +"circles in this group and workplane join." +msgstr "" +"正接円弧を作成するには、同じグループかつ同じ作業平面上にある、補助線ではない2" +"つの直線もしくは円が交差している点を選択してください。" + +#: modify.cpp:386 +msgid "" +"Couldn't round this corner. Try a smaller radius, or try creating the " +"desired geometry by hand with tangency constraints." +msgstr "" +"この角をフィレットにできませんでした。もっと小さい半径を指定するか、手作業で" +"拘束してください。" + +#: modify.cpp:595 +msgid "Couldn't split this entity; lines, circles, or cubics only." +msgstr "この要素は分割できません。分割できるのは直線・円・ベジェ曲線のみです。" + +#: modify.cpp:622 +msgid "Must be sketching in workplane to split." +msgstr "分割するには作業平面上でスケッチしてください。" + +#: modify.cpp:629 +msgid "" +"Select two entities that intersect each other (e.g. two lines/circles/arcs " +"or a line/circle/arc and a point)." +msgstr "互いに交差している 2 つの要素を選択してください (直線・円・円弧・点)" + +#: modify.cpp:734 +msgid "Can't split; no intersection found." +msgstr "交差箇所が見つからないため、分割できません。" + +#: mouse.cpp:558 +msgid "Assign to Style" +msgstr "スタイルを割り当て" + +#: mouse.cpp:574 +msgid "No Style" +msgstr "スタイル指定なし" + +#: mouse.cpp:577 +msgid "Newly Created Custom Style..." +msgstr "スタイルを作成して割り当て..." + +#: mouse.cpp:584 +msgid "Group Info" +msgstr "グループ情報" + +#: mouse.cpp:604 +msgid "Style Info" +msgstr "スタイル情報" + +#: mouse.cpp:624 +msgid "Select Edge Chain" +msgstr "接続しているエッジをまとめて選択" + +#: mouse.cpp:630 +msgid "Toggle Reference Dimension" +msgstr "参照寸法と切り替え" + +#: mouse.cpp:636 +msgid "Other Supplementary Angle" +msgstr "補角と切り替え" + +#: mouse.cpp:641 +msgid "Snap to Grid" +msgstr "グリッドに沿わせる" + +#: mouse.cpp:650 +msgid "Remove Spline Point" +msgstr "スプラインの制御点を削除" + +#: mouse.cpp:685 +msgid "Add Spline Point" +msgstr "スプラインの制御点を追加" + +#: mouse.cpp:689 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "" +"スプラインの制御点を追加できませんでした。点の個数の上限に達しています。" + +#: mouse.cpp:714 +msgid "Toggle Construction" +msgstr "補助線との切り替え" + +#: mouse.cpp:730 +msgid "Delete Point-Coincident Constraint" +msgstr "点の一致拘束を削除する" + +#: mouse.cpp:748 +msgid "Cut" +msgstr "切り取り" + +#: mouse.cpp:750 +msgid "Copy" +msgstr "コピー" + +#: mouse.cpp:754 +msgid "Select All" +msgstr "すべてを選択" + +#: mouse.cpp:759 +msgid "Paste" +msgstr "貼り付け" + +#: mouse.cpp:761 +msgid "Paste Transformed..." +msgstr "複製しながら貼り付け..." + +#: mouse.cpp:766 +msgid "Delete" +msgstr "削除" + +#: mouse.cpp:769 +msgid "Unselect All" +msgstr "選択を解除" + +#: mouse.cpp:776 +msgid "Unselect Hovered" +msgstr "カーソルを当てている要素の選択を解除" + +#: mouse.cpp:785 +msgid "Zoom to Fit" +msgstr "選択オブジェクトの最適表示" + +#: mouse.cpp:987 mouse.cpp:1276 +msgid "click next point of line, or press Esc" +msgstr "直線の次の点をクリックで指定、もしくはESCで終了" + +#: mouse.cpp:993 +msgid "" +"Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"三次元空間内に四角形はスケッチできません。「スケッチ -> 作業平面上でスケッチ" +"する」で作業平面をアクティブにしてください。" + +#: mouse.cpp:1027 +msgid "click to place other corner of rectangle" +msgstr "四角形のもう一つの角をクリックで指定" + +#: mouse.cpp:1048 +msgid "click to set radius" +msgstr "半径をクリックで指定" + +#: mouse.cpp:1053 +msgid "" +"Can't draw arc in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"三次元空間内に円弧はスケッチできません。「スケッチ -> 作業平面上でスケッチす" +"る」で作業平面をアクティブにしてください。" + +#: mouse.cpp:1072 +msgid "click to place point" +msgstr "点の位置をクリックで指定" + +#: mouse.cpp:1088 +msgid "click next point of cubic, or press Esc" +msgstr "ベジェ曲線の次の点をクリックで指定、もしくはESCで終了" + +#: mouse.cpp:1093 +msgid "" +"Sketching in a workplane already; sketch in 3d before creating new workplane." +msgstr "" +"すでに作業平面上でのスケッチをしています。新しい作業平面を作成する前に、三次" +"元空間内でスケッチしてください。" + +#: mouse.cpp:1109 +msgid "" +"Can't draw text in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"三次元空間内にテキストはスケッチできません。「スケッチ -> 作業平面上でスケッ" +"チする」で作業平面をアクティブにしてください。" + +#: mouse.cpp:1126 +msgid "click to place bottom right of text" +msgstr "テキストの左下の位置をクリックで指定" + +#: mouse.cpp:1132 +msgid "" +"Can't draw image in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"三次元空間内に画像はスケッチできません。「スケッチ -> 作業平面上でスケッチす" +"る」で作業平面をアクティブにしてください。" + +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "SolveSpaceモデル" + +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "(すべてのファイル)" + +#: platform/gui.cpp:91 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "IDF 電子基板" + +#: platform/gui.cpp:92 +msgctxt "file-type" +msgid "STL triangle mesh" +msgstr "STL 三角メッシュ" + +#: platform/gui.cpp:96 +msgctxt "file-type" +msgid "PNG image" +msgstr "PNG 画像" + +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "STL mesh" +msgstr "STL メッシュ" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Wavefront OBJ メッシュ" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "Three.js 互換のメッシュ (ビューワー付)" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "Three.js 互換のメッシュ (メッシュのみ)" + +#: platform/gui.cpp:104 +msgctxt "file-type" +msgid "VRML text file" +msgstr "VRML テキストファイル" + +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "STEP file" +msgstr "STEP ファイル" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "PDF file" +msgstr "PDF ファイル" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "EPS ファイル (Encapsulated PostScript)" + +#: platform/gui.cpp:114 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "SVG ファイル" + +#: platform/gui.cpp:116 platform/gui.cpp:123 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "DXF ファイル (AutoDesk 2007)" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "HPGL file" +msgstr "HPG ファイル" + +#: platform/gui.cpp:118 +msgctxt "file-type" +msgid "G Code" +msgstr "G コード" + +#: platform/gui.cpp:127 +msgctxt "file-type" +msgid "AutoCAD DXF and DWG files" +msgstr "AudoCAD DXF/DWG ファイル" + +#: platform/gui.cpp:131 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "CSV ファイル (Comma-separated values)" + +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 +msgid "untitled" +msgstr "名称未設定" + +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 +msgctxt "title" +msgid "Save File" +msgstr "ファイルを保存" + +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 +msgctxt "title" +msgid "Open File" +msgstr "ファイルを開く" + +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 +msgctxt "button" +msgid "_Cancel" +msgstr "キャンセル (_C)" + +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 +msgctxt "button" +msgid "_Save" +msgstr "保存 (_S)" + +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 +msgctxt "button" +msgid "_Open" +msgstr "開く (_O)" + +#: solvespace.cpp:175 +msgctxt "title" +msgid "Autosave Available" +msgstr "オートセーブファイルがあります" + +#: solvespace.cpp:176 +msgctxt "dialog" +msgid "An autosave file is available for this sketch." +msgstr "このスケッチにはオートセーブファイルがあります。" + +#: solvespace.cpp:177 +msgctxt "dialog" +msgid "Do you want to load the autosave file instead?" +msgstr "代わりにオートセーブファイル読み込みますか?" + +#: solvespace.cpp:178 +msgctxt "button" +msgid "&Load autosave" +msgstr "オートセーブファイルを読み込む (&L)" + +#: solvespace.cpp:180 +msgctxt "button" +msgid "Do&n't Load" +msgstr "読み込まない (&n)" + +#: solvespace.cpp:640 +msgctxt "title" +msgid "Modified File" +msgstr "編集済みファイル" + +#: solvespace.cpp:642 +#, c-format +msgctxt "dialog" +msgid "Do you want to save the changes you made to the sketch “%s”?" +msgstr "スケッチ \"%s\" への変更を保存しますか?" + +#: solvespace.cpp:645 +msgctxt "dialog" +msgid "Do you want to save the changes you made to the new sketch?" +msgstr "新規スケッチへの変更を保存しますか?" + +#: solvespace.cpp:648 +msgctxt "dialog" +msgid "Your changes will be lost if you don't save them." +msgstr "(未保存の変更があります。)" + +#: solvespace.cpp:649 +msgctxt "button" +msgid "&Save" +msgstr "保存する (&S)" + +#: solvespace.cpp:651 +msgctxt "button" +msgid "Do&n't Save" +msgstr "保存しない (&n)" + +#: solvespace.cpp:672 +msgctxt "title" +msgid "(new sketch)" +msgstr "(新規スケッチ)" + +#: solvespace.cpp:683 +msgctxt "title" +msgid "Property Browser" +msgstr "プロパティブラウザ" + +#: solvespace.cpp:746 +msgid "" +"Constraints are currently shown, and will be exported in the toolpath. This " +"is probably not what you want; hide them by clicking the link at the top of " +"the text window." +msgstr "" +"表示されている拘束はツールパスとしてエクスポートされます。これはあなたが期待" +"した動作ではないかもしれません。プロパティブラウザの上部にあるボタンで非表示" +"にできます。" + +#: solvespace.cpp:834 +#, c-format +msgid "" +"Can't identify file type from file extension of filename '%s'; try .dxf or ." +"dwg." +msgstr "" +"拡張子からファイル形式を特定できませんでした ( '%s' )。 \".dxf\" や \".dwg\" " +"を試してください。" + +#: solvespace.cpp:886 +msgid "Constraint must have a label, and must not be a reference dimension." +msgstr "参照寸法ではなく、かつ、値のある拘束を選択してください。" + +#: solvespace.cpp:890 +msgid "Bad selection for step dimension; select a constraint." +msgstr "寸法の段階的変更には無効な選択です。値のある拘束を選択してください。" + +#: solvespace.cpp:914 +msgid "The assembly does not interfere, good." +msgstr "部品の干渉はありませんでした。" + +#: solvespace.cpp:930 +#, c-format +msgid "" +"The volume of the solid model is:\n" +"\n" +" %s" +msgstr "" +"ソリッドモデルの容積:\n" +"\n" +" %s" + +#: solvespace.cpp:939 +#, c-format +msgid "" +"\n" +"The volume of current group mesh is:\n" +"\n" +" %s" +msgstr "" +"\n" +"現在のグループのメッシュの容積:\n" +"\n" +" %s" + +#: solvespace.cpp:944 +msgid "" +"\n" +"\n" +"Curved surfaces have been approximated as triangles.\n" +"This introduces error, typically of around 1%." +msgstr "" +"\n" +"\n" +"曲面はトライアングルに近似します。\n" +"これにより通常、約 1% の誤差が発生します。" + +#: solvespace.cpp:959 +#, c-format +msgid "" +"The surface area of the selected faces is:\n" +"\n" +" %s\n" +"\n" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." +msgstr "" +"選択した面のファーフェスの面積:\n" +"\n" +" %s\n" +"\n" +"曲線は区分線形に近似します。\n" +"これにより通常、約 1%% の誤差が発生します。" + +#: solvespace.cpp:968 +msgid "" +"This group does not contain a correctly-formed 2d closed area. It is open, " +"not coplanar, or self-intersecting." +msgstr "" +"このグループは正常に構成された二次元の閉じた領域が含まれていません。領域が開" +"いているか、同一平面上にないか、自己交差しています。" + +#: solvespace.cpp:980 +#, c-format +msgid "" +"The area of the region sketched in this group is:\n" +"\n" +" %s\n" +"\n" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." +msgstr "" +"このグループ内のスケッチされた領域の面積:\n" +"\n" +" %s\n" +"\n" +"曲線は区分線形に近似します。\n" +"これにより通常、約 1%% の誤差が発生します。" + +#: solvespace.cpp:1000 +#, c-format +msgid "" +"The total length of the selected entities is:\n" +"\n" +" %s\n" +"\n" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." +msgstr "" +"選択された要素の合計の長さ:\n" +"\n" +" %s\n" +"\n" +"曲線は区分線形に近似します。\n" +"これにより通常、約 1%% の誤差が発生します。" + +#: solvespace.cpp:1006 +msgid "Bad selection for perimeter; select line segments, arcs, and curves." +msgstr "" +"外周長の計測には無効な選択です。線分、円弧、もしくは曲線を選択してください。" + +#: solvespace.cpp:1022 +msgid "Bad selection for trace; select a single point." +msgstr "点の位置の記録には無効な選択です。ひとつの点を選択してください。" + +#: solvespace.cpp:1049 +#, c-format +msgid "Couldn't write to '%s'" +msgstr "'%s' へ書き込めませんでした。" + +#: solvespace.cpp:1079 +msgid "The mesh is self-intersecting (NOT okay, invalid)." +msgstr "メッシュが自身と交差しています (警告)" + +#: solvespace.cpp:1080 +msgid "The mesh is not self-intersecting (okay, valid)." +msgstr "メッシュは自己交差していません (適切)" + +#: solvespace.cpp:1082 +msgid "The mesh has naked edges (NOT okay, invalid)." +msgstr "メッシュには浮いたエッジが含まれています (警告)" + +#: solvespace.cpp:1083 +msgid "The mesh is watertight (okay, valid)." +msgstr "メッシュには隙間はありません (適切)" + +#: solvespace.cpp:1086 +#, c-format +msgid "" +"\n" +"\n" +"The model contains %d triangles, from %d surfaces." +msgstr "" +"\n" +"\n" +"モデルには %d 個のトライアングルがあり、これが %d 個のサーフェスを構成しま" +"す。" + +#: solvespace.cpp:1090 +#, c-format +msgid "" +"%s\n" +"\n" +"%s\n" +"\n" +"Zero problematic edges, good.%s" +msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"問題のあるエッジは見つかりませんでした。%s" + +#: solvespace.cpp:1093 +#, c-format +msgid "" +"%s\n" +"\n" +"%s\n" +"\n" +"%d problematic edges, bad.%s" +msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"%d 個の問題のあるエッジが見つかりました。%s" + +#: solvespace.cpp:1106 +#, c-format +msgid "" +"This is SolveSpace version %s.\n" +"\n" +"For more information, see http://solvespace.com/\n" +"\n" +"SolveSpace is free software: you are free to modify\n" +"and/or redistribute it under the terms of the GNU\n" +"General Public License (GPL) version 3 or later.\n" +"\n" +"There is NO WARRANTY, to the extent permitted by\n" +"law. For details, visit http://gnu.org/licenses/\n" +"\n" +"© 2008-%d Jonathan Westhues and other authors.\n" +msgstr "" + +#: style.cpp:185 +msgid "" +"Can't assign style to an entity that's derived from another entity; try " +"assigning a style to this entity's parent." +msgstr "" +"他の要素から派生した要素には個別のスタイルを割り当てることはできません。親要" +"素にスタイルを割り当ててください。" + +#: style.cpp:735 +msgid "Style name cannot be empty" +msgstr "スタイル名は空にできません" + +#: textscreens.cpp:837 +msgid "Can't repeat fewer than 1 time." +msgstr "繰り返しは 1 以上の回数を指定してください。" + +#: textscreens.cpp:841 +msgid "Can't repeat more than 999 times." +msgstr "999回を超えて繰り返すことはできません。" + +#: textscreens.cpp:866 +msgid "Group name cannot be empty" +msgstr "グループ名は空にできません" + +#: textscreens.cpp:918 +msgid "Opacity must be between zero and one." +msgstr "透明度は0から1の間で指定してください。" + +#: textscreens.cpp:953 +msgid "Radius cannot be zero or negative." +msgstr "半径は0もしくは負の値にできません。" + +#: toolbar.cpp:18 +msgid "Sketch line segment" +msgstr "線分をスケッチ" + +#: toolbar.cpp:20 +msgid "Sketch rectangle" +msgstr "四角形をスケッチ" + +#: toolbar.cpp:22 +msgid "Sketch circle" +msgstr "円をスケッチ" + +#: toolbar.cpp:24 +msgid "Sketch arc of a circle" +msgstr "円弧をスケッチ" + +#: toolbar.cpp:26 +msgid "Sketch curves from text in a TrueType font" +msgstr "TrueTypeフォントのテキストからアウトラインをスケッチ" + +#: toolbar.cpp:28 +msgid "Sketch image from a file" +msgstr "ファイルから画像を取り込む" + +#: toolbar.cpp:30 +msgid "Create tangent arc at selected point" +msgstr "選択された点でフィレットを作る" + +#: toolbar.cpp:32 +msgid "Sketch cubic Bezier spline" +msgstr "ベジェ曲線をスケッチ" + +#: toolbar.cpp:34 +msgid "Sketch datum point" +msgstr "点をスケッチ" + +#: toolbar.cpp:36 +msgid "Toggle construction" +msgstr "補助線と切り替え" + +#: toolbar.cpp:38 +msgid "Split lines / curves where they intersect" +msgstr "直線・曲線を交差箇所で分割" + +#: toolbar.cpp:42 +msgid "Constrain distance / diameter / length" +msgstr "距離・直径・長さを拘束" + +#: toolbar.cpp:44 +msgid "Constrain angle" +msgstr "角度を拘束" + +#: toolbar.cpp:46 +msgid "Constrain to be horizontal" +msgstr "水平に拘束" + +#: toolbar.cpp:48 +msgid "Constrain to be vertical" +msgstr "垂直に拘束" + +#: toolbar.cpp:50 +msgid "Constrain to be parallel or tangent" +msgstr "平行もしくは正接で拘束" + +#: toolbar.cpp:52 +msgid "Constrain to be perpendicular" +msgstr "垂線で拘束" + +#: toolbar.cpp:54 +msgid "Constrain point on line / curve / plane / point" +msgstr "点を線・曲線・面・点の上に拘束" + +#: toolbar.cpp:56 +msgid "Constrain symmetric" +msgstr "対称に拘束" + +#: toolbar.cpp:58 +msgid "Constrain equal length / radius / angle" +msgstr "長さ・半径・角度を等値に拘束" + +#: toolbar.cpp:60 +msgid "Constrain normals in same orientation" +msgstr "法線を同じ方向に拘束" + +#: toolbar.cpp:62 +msgid "Other supplementary angle" +msgstr "角度の拘束を補角へ移動" + +#: toolbar.cpp:64 +msgid "Toggle reference dimension" +msgstr "拘束と参照寸法を切り替え" + +#: toolbar.cpp:68 +msgid "New group extruding active sketch" +msgstr "押し出しを作成" + +#: toolbar.cpp:70 +msgid "New group rotating active sketch" +msgstr "周回を作成" + +#: toolbar.cpp:72 +msgid "New group helix from active sketch" +msgstr "螺旋を作成" + +#: toolbar.cpp:74 +msgid "New group revolve active sketch" +msgstr "回転を作成" + +#: toolbar.cpp:76 +msgid "New group step and repeat rotating" +msgstr "円上にコピーを作成" + +#: toolbar.cpp:78 +msgid "New group step and repeat translating" +msgstr "直線上にコピーを作成" + +#: toolbar.cpp:80 +msgid "New group in new workplane (thru given entities)" +msgstr "選択した要素で定義される作業平面でスケッチを開始" + +#: toolbar.cpp:82 +msgid "New group in 3d" +msgstr "三次元空間内でスケッチを開始" + +#: toolbar.cpp:84 +msgid "New group linking / assembling file" +msgstr "他のファイルをリンクする" + +#: toolbar.cpp:88 +msgid "Nearest isometric view" +msgstr "近傍の等角図視点" + +#: toolbar.cpp:90 +msgid "Align view to active workplane" +msgstr "アクティブな作業平面に視点を合わせる" + +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "エラー" + +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "メッセージ" + +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "OK (&O)" + +#: view.cpp:127 +msgid "Scale cannot be zero or negative." +msgstr "縮尺は 0 や負の値にはできません。" + +#: view.cpp:139 view.cpp:148 +msgid "Bad format: specify x, y, z" +msgstr "書式が誤っています。\"x, y, z\" で指定してください。" + +#~ msgid "" +#~ "Bad selection for on point / curve / plane constraint. This constraint " +#~ "can apply to:\n" +#~ "\n" +#~ " * two points (points coincident)\n" +#~ " * a point and a workplane (point in plane)\n" +#~ " * a point and a line segment (point on line)\n" +#~ " * a point and a circle or arc (point on curve)\n" +#~ " * a point and a plane face (point on face)\n" +#~ msgstr "" +#~ "一致拘束ができませんでした。次の要素に適用できます:\n" +#~ "\n" +#~ " * 2 つの点 (点を同一位置に)\n" +#~ " * 1 つの点と 1 つの作業平面 (点を平面上に)\n" +#~ " * 1 つの点と 1 つの線分 (点を直線状に)\n" +#~ " * 1 つの点と、1つの円もしくは円弧 (点を曲線上に)\n" +#~ " * 1 つの点と 1 つの平面フェイス (点をフェイス上に)\n" + +#~ msgid "" +#~ "Bad selection for equal length / radius constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (equal length)\n" +#~ " * two line segments and two points (equal point-line distances)\n" +#~ " * a line segment and two points (equal point-line distances)\n" +#~ " * a line segment, and a point and line segment (point-line distance " +#~ "equals length)\n" +#~ " * four line segments or normals (equal angle between A,B and C,D)\n" +#~ " * three line segments or normals (equal angle between A,B and B,C)\n" +#~ " * two circles or arcs (equal radius)\n" +#~ " * a line segment and an arc (line segment length equals arc length)\n" +#~ msgstr "" +#~ "長さ・半径・角度の同値拘束ができませんでした。次の要素に適用できます:\n" +#~ "\n" +#~ " * 2 つの線分 (長さを同じに)\n" +#~ " * 2 つの線分と 2 つの点 (それぞれ直線と点の距離を同じに)\n" +#~ " * 1 つの線分と 2 つの点 (直線とそれぞれの点の距離を同じに)\n" +#~ " * 1 つの線分と、1 つの点と 1 つの線分 (線分の長さと、直線と点の距離を同" +#~ "じに)\n" +#~ " * 4つの線分もしくは法線 (直線A,B、直線C,Dそれぞれの交差する角度を同じ" +#~ "に)\n" +#~ " * 3 つの線分もしくは法線 (直線A,B、直線B,Cそれぞれの交差する角度を同じ" +#~ "に)\n" +#~ " * 2 つの円もしくは円弧 (半径を同じに)\n" +#~ " * 1 つの線分と 1 つの円弧 (線分の長さと円弧の長さを同じに)\n" + +#~ msgid "" +#~ "Bad selection for horizontal / vertical constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two points\n" +#~ " * a line segment\n" +#~ msgstr "" +#~ "垂直・水平拘束ができませんでした。次の要素に適用できます:\n" +#~ "\n" +#~ " * 2 つの点\n" +#~ " * 1 つの線分\n" + +#~ msgid "" +#~ "Bad selection for angle constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" +#~ msgstr "" +#~ "角度拘束ができませんでした。次の要素に適用できます:\n" +#~ "\n" +#~ " * 2 つの線分\n" +#~ " * 線分と法線\n" +#~ " * 2 つの法線\n" + +#~ msgid "" +#~ "Bad selection for parallel / tangent constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (parallel)\n" +#~ " * a line segment and a normal (parallel)\n" +#~ " * two normals (parallel)\n" +#~ " * two line segments, arcs, or beziers, that share an endpoint " +#~ "(tangent)\n" +#~ msgstr "" +#~ "平行・正接拘束ができませんでした。次の要素に適用できます:\n" +#~ "\n" +#~ " * 2 つの線分 (平行)\n" +#~ " * 1 つの線分と 1 つの法線 (平行)\n" +#~ " * 2 つの法線 (平行)\n" +#~ " * 片方の端点を共有する 2 つの線分、円弧、もしくはベジェ曲線 (正接)\n" + +#~ msgid "" +#~ "Bad selection for perpendicular constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" +#~ msgstr "" +#~ "垂線拘束ができませんでした。次の要素に適用できます:\n" +#~ "\n" +#~ " * 2 つの線分\n" +#~ " * 1 つの線分と 1 つの法線\n" +#~ " * 2 つの法線\n" + +#~ msgid "A&ngle" +#~ msgstr "角度 (&n)" + +#~ msgid "E&qual Length / Radius / Angle" +#~ msgstr "長さ・半径・角度を等値にする (&q)" diff --git a/res/locales/ru_RU.po b/res/locales/ru_RU.po index 42a3fa3a6..36f1d2e1e 100644 --- a/res/locales/ru_RU.po +++ b/res/locales/ru_RU.po @@ -2,75 +2,80 @@ # Copyright (C) 2017 the SolveSpace authors # This file is distributed under the same license as the SolveSpace package. # EvilSpirit , 2017. +# Olesya Gerasimenko , 2021. +# Alexandre Prokoudine , 2023. +# Milan Djurovic , 2025. +# msgid "" msgstr "" "Project-Id-Version: SolveSpace 3.0\n" -"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" -"POT-Creation-Date: 2021-02-01 15:45+0200\n" -"PO-Revision-Date: 2021-01-22 18:50+0700\n" -"Last-Translator: evilspirit@evilspirit.org\n" -"Language-Team: EvilSpirit\n" -"Language: ru_RU\n" +"Report-Msgid-Bugs-To: phkahler@gmail.com\n" +"POT-Creation-Date: 2025-01-26 21:04+0200\n" +"PO-Revision-Date: 2025-02-22 14:48-0800\n" +"Last-Translator: Milan Djurovic \n" +"Language-Team: Russian \n" +"Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.4.2\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" +"X-Generator: Gtranslator 41.0\n" -#: clipboard.cpp:310 +#: clipboard.cpp:314 msgid "" "Cut, paste, and copy work only in a workplane.\n" "\n" "Activate one with Sketch -> In Workplane." msgstr "" -"Копировать, вставить или вырезать\n" -"можно только находясь в рабочей плоскости.\n" -"Активируйте рабочую плоскость через Эскиз->В Рабочей Плоскости" +"Копировать, вставить или вырезать можно\n" +"только находясь в рабочей плоскости.\n" +"Активируйте ее через Эскиз->В рабочей плоскости." -#: clipboard.cpp:327 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." msgstr "Буфер обмена пуст; нечего вставлять." -#: clipboard.cpp:374 +#: clipboard.cpp:378 msgid "Number of copies to paste must be at least one." msgstr "Укажите в поле 'количество' хотя бы одну копию для вставки." -#: clipboard.cpp:390 textscreens.cpp:783 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "Масштабный коэффициент не может быть нулевым." -#: clipboard.cpp:432 +#: clipboard.cpp:436 msgid "Select one point to define origin of rotation." msgstr "Выберите одну точку в качестве центра вращения." -#: clipboard.cpp:444 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." msgstr "Выберите две точки, чтобы задать вектор смещения." -#: clipboard.cpp:454 +#: clipboard.cpp:458 msgid "" "Transformation is identity. So all copies will be exactly on top of each " "other." msgstr "" "Трансформация не задана. Все копии будут расположены в одном и том же месте." -#: clipboard.cpp:458 +#: clipboard.cpp:462 msgid "Too many items to paste; split this into smaller pastes." msgstr "Слишком много элементов для вставки; разбейте на несколько частей." -#: clipboard.cpp:463 +#: clipboard.cpp:467 msgid "No workplane active." -msgstr "Рабочая плоскость не активна" +msgstr "Нет активной рабочей плоскости." -#: confscreen.cpp:418 +#: confscreen.cpp:410 msgid "Bad format: specify coordinates as x, y, z" msgstr "Неверный формат: введите координаты как x, y, z" -#: confscreen.cpp:428 style.cpp:659 textscreens.cpp:805 +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 msgid "Bad format: specify color as r, g, b" msgstr "Неверный формат: введите цвет как r, g, b" -#: confscreen.cpp:454 +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." @@ -78,25 +83,25 @@ msgstr "" "Коэффициент перспективы не будет иметь эффект, пока вы не включите Вид-" ">Перспективная Проекция." -#: confscreen.cpp:467 confscreen.cpp:477 +#: confscreen.cpp:464 confscreen.cpp:474 #, c-format msgid "Specify between 0 and %d digits after the decimal." msgstr "Введите число от 0 до %d." -#: confscreen.cpp:489 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" msgstr "Масштабный коэффициент не может быть нулевым!" -#: confscreen.cpp:501 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" msgstr "Радиус режущего инструмента не может быть отрицательным!" -#: confscreen.cpp:555 +#: confscreen.cpp:557 msgid "Bad value: autosave interval should be positive" msgstr "" "Неверное значение: интервал автосохранения должен быть положительным числом" -#: confscreen.cpp:558 +#: confscreen.cpp:560 msgid "Bad format: specify interval in integral minutes" msgstr "" "Неверный формат: введите целое число, чтобы задать интервал автосохранения" @@ -168,142 +173,222 @@ msgstr "отношение-длин" #: constraint.cpp:25 msgctxt "constr-name" +msgid "arc-arc-length-ratio" +msgstr "отношение-длин-дуга-дуга" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "arc-line-length-ratio" +msgstr "отношение-длин-дуга-отрезок" + +#: constraint.cpp:27 +msgctxt "constr-name" msgid "length-difference" msgstr "разность-длин" -#: constraint.cpp:26 +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "arc-arc-len-difference" +msgstr "разность-длин-дуга-дуга" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "arc-line-len-difference" +msgstr "разность-длин-дуга-отрезок" + +#: constraint.cpp:30 msgctxt "constr-name" msgid "symmetric" msgstr "симметричность" -#: constraint.cpp:27 +#: constraint.cpp:31 msgctxt "constr-name" msgid "symmetric-h" msgstr "симметричность-гориз" -#: constraint.cpp:28 +#: constraint.cpp:32 msgctxt "constr-name" msgid "symmetric-v" msgstr "симметричность-верт" -#: constraint.cpp:29 +#: constraint.cpp:33 msgctxt "constr-name" msgid "symmetric-line" msgstr "симметричность-по-оси" -#: constraint.cpp:30 +#: constraint.cpp:34 msgctxt "constr-name" msgid "at-midpoint" msgstr "на-середине" -#: constraint.cpp:31 +#: constraint.cpp:35 msgctxt "constr-name" msgid "horizontal" msgstr "горизонтальность" -#: constraint.cpp:32 +#: constraint.cpp:36 msgctxt "constr-name" msgid "vertical" msgstr "вертикальность" -#: constraint.cpp:33 +#: constraint.cpp:37 msgctxt "constr-name" msgid "diameter" msgstr "диаметр" -#: constraint.cpp:34 +#: constraint.cpp:38 msgctxt "constr-name" msgid "pt-on-circle" msgstr "тчк-на-окружности" -#: constraint.cpp:35 +#: constraint.cpp:39 msgctxt "constr-name" msgid "same-orientation" msgstr "идентичная-ориентация" -#: constraint.cpp:36 +#: constraint.cpp:40 msgctxt "constr-name" msgid "angle" msgstr "угол" -#: constraint.cpp:37 +#: constraint.cpp:41 msgctxt "constr-name" msgid "parallel" msgstr "параллельность" -#: constraint.cpp:38 +#: constraint.cpp:42 msgctxt "constr-name" msgid "arc-line-tangent" msgstr "кас-дуга-линия" -#: constraint.cpp:39 +#: constraint.cpp:43 msgctxt "constr-name" msgid "cubic-line-tangent" msgstr "кас-сплайн-линия" -#: constraint.cpp:40 +#: constraint.cpp:44 msgctxt "constr-name" msgid "curve-curve-tangent" msgstr "кас-кривых" -#: constraint.cpp:41 +#: constraint.cpp:45 msgctxt "constr-name" msgid "perpendicular" msgstr "перпендикулярность" -#: constraint.cpp:42 +#: constraint.cpp:46 msgctxt "constr-name" msgid "eq-radius" msgstr "равенство-радиусов" -#: constraint.cpp:43 +#: constraint.cpp:47 msgctxt "constr-name" msgid "eq-angle" msgstr "равенство-углов" -#: constraint.cpp:44 +#: constraint.cpp:48 msgctxt "constr-name" msgid "eq-line-len-arc-len" msgstr "равен-длины-линии-длины-дуги" -#: constraint.cpp:45 +#: constraint.cpp:49 msgctxt "constr-name" msgid "lock-where-dragged" msgstr "фиксация" -#: constraint.cpp:46 +#: constraint.cpp:50 msgctxt "constr-name" msgid "comment" msgstr "комментарий" -#: constraint.cpp:140 +#: constraint.cpp:151 +msgid "" +"The point you selected does not belong to the arc. The arc and line segment " +"do not share an end point.\n" +"\n" +"Select the end point of the arc at which you want it to be tangent to the " +"line." +msgstr "" +"Выбранная точка не принадлежит дуге. Дуга и отрезок не имеют общей конечной " +"точки.\n" +"\n" +"Выберите конечную точку дуги, в которой вы хотите, чтобы она была " +"касательной к отрезку." + +#: constraint.cpp:158 msgid "" "The tangent arc and line segment must share an endpoint. Constrain them with " -"Constrain -> On Point before constraining tangent." +"Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be " +"tangent to the line." msgstr "" "Дуга и отрезок должны быть соединены. Соедините их крайние точки с помощью " -"'Ограничения -> Точка на Примитиве' перед тем, как применять ограничение " -"касательности." +"'Ограничения -> Точка на примитиве' перед тем, как применять ограничения " +"касательности.\n" +"\n" +"Или выберите конечную точку дуги, в которой вы хотите, чтобы она была " +"касательной к отрезку." -#: constraint.cpp:158 +#: constraint.cpp:186 msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them " -"with Constrain -> On Point before constraining tangent." +"The point you selected is not an end point of the cubic spline. The spline " +"and line segment do not share an end point.\n" +"\n" +"Select the end point of the spline at which you want it to be tangent to the " +"line." +msgstr "" +"Выбранная вами точка не является конечной точкой кубического сплайна. Сплайн " +"и отрезок не имеют общей конечной точки.\n" +"\n" +"Выберите конечную точку сплайна, в которой вы хотите, чтобы он был " +"касательным к отрезку." + +#: constraint.cpp:193 +msgid "" +"The tangent cubic spline and line segment must share an endpoint. Constrain " +"them with Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it " +"to be tangent to the line." msgstr "" "Сплайн и отрезок должны быть соединены. Соедините их крайние точки с помощью " "'Ограничения -> Точка на Примитиве' перед тем, как применять ограничение " -"касательности." +"касательности.\n" +"\n" +"Или выберите конечную точку кубического сплайна, в которой вы хотите, чтобы " +"он былкасательным к отрезку." -#: constraint.cpp:183 +#: constraint.cpp:237 +msgid "" +"The points you selected are not end points of the two curves. The curves do " +"not share an end point.\n" +"\n" +"Select the end points of both curves at which you want them to be tangent to " +"each other." +msgstr "" +"Выбранные вами точки не являются конечными точками двух кривых. У кривых нет " +"общей конечной точки.\n" +"\n" +"Выберите конечные точки обеих кривых, в которых вы хотите, чтобы они были " +"касательными друг к другу." + +#: constraint.cpp:244 msgid "" "The curves must share an endpoint. Constrain them with Constrain -> On Point " -"before constraining tangent." +"before constraining tangent.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." msgstr "" "Кривые должны быть соединены. Соедините их крайние точки с помощью " "'Ограничения -> Точка на Примитиве' перед тем, как применять ограничение " -"касательности." +"касательности.\n" +"\n" +"Или выберите конечные точки обеих кривых, в которых вы хотите, чтобы они " +"быликасательными." -#: constraint.cpp:231 +#: constraint.cpp:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply " "to:\n" @@ -328,78 +413,82 @@ msgstr "" " * грань и точку (расстояние от точки до плоскости грани)\n" " * окружность или дугу (диаметр / радиус)\n" -#: constraint.cpp:284 +#: constraint.cpp:366 msgid "" "Bad selection for on point / curve / plane constraint. This constraint can " "apply to:\n" "\n" -" * two points (points coincident)\n" +" * two or more points (points coincident)\n" " * a point and a workplane (point in plane)\n" " * a point and a line segment (point on line)\n" " * a point and a circle or arc (point on curve)\n" -" * a point and a plane face (point on face)\n" +" * a point and one to three plane faces (point on face(s))\n" msgstr "" "Неправильное выделение для ограничения 'точка на примитиве'.\n" "Ограничение может принимать в качестве выделения следующие примитивы:\n" "\n" -" * две точки (совпадение точек)\n" +" * две или более точек (совпадение точек)\n" " * точку и рабочую плоскость (точка в плоскости)\n" " * точку и отрезок (точка на линии)\n" " * точку и окружность / дугу / сплайн (точка на кривой)\n" -" * точку и грань (точка на грани)\n" +" * точку и от одной до трех граней (точка на грани)\n" -#: constraint.cpp:346 +#: constraint.cpp:427 msgid "" "Bad selection for equal length / radius constraint. This constraint can " "apply to:\n" "\n" -" * two line segments (equal length)\n" +" * two or more line segments (equal length)\n" " * two line segments and two points (equal point-line distances)\n" " * a line segment and two points (equal point-line distances)\n" " * a line segment, and a point and line segment (point-line distance " "equals length)\n" -" * four line segments or normals (equal angle between A,B and C,D)\n" -" * three line segments or normals (equal angle between A,B and B,C)\n" -" * two circles or arcs (equal radius)\n" +" * two or more circles or arcs (equal radius)\n" " * a line segment and an arc (line segment length equals arc length)\n" msgstr "" "Неправильное выделение для ограничения 'равенство примитивов'.\n" "Ограничение может принимать в качестве выделения следующие примитивы:\n" "\n" -" * два отрезка (равенство длин отрезков)\n" +" * два или более отрезка (равенство длин отрезков)\n" " * два отрезка и две точки (равенство расстояний от точек до линий)\n" " * отрезок и две точки (равенство расстояний от точек до линии)\n" " * отрезок, точку и отрезок (равенство длины отрезка расстоянию от точки " "до линии)\n" -" * четыре отрезка или нормали (равенство углов A,B и C,D)\n" -" * три отрезка или нормали (равенство углов A,B and B,C)\n" -" * две окружности / дуги (равенство радиусов)\n" +" * две или более окружностей / дуги (равенство радиусов)\n" " * отрезок и дугу (равенство длины отрезка и длины дуги)\n" -#: constraint.cpp:385 +#: constraint.cpp:480 msgid "" "Bad selection for length ratio constraint. This constraint can apply to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" -"Неправильное выделение для ограничения 'отношение длин'.\n" -"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"Неправильное выделение для ограничения 'отношение длин'. Ограничение может " +"принимать в качестве выделения следующие примитивы:\n" "\n" " * два отрезка\n" +" * две дуги\n" +" * дугу и отрезок\n" -#: constraint.cpp:402 +#: constraint.cpp:515 msgid "" "Bad selection for length difference constraint. This constraint can apply " "to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" -"Неправильное выделение для ограничения 'разница длин'.\n" -"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"Неправильное выделение для ограничения 'разность длин'. Ограничение может " +"принимать в качестве выделения следующие примитивы:\n" "\n" " * два отрезка\n" +" * две дуги\n" +" * дугу и отрезок\n" -#: constraint.cpp:428 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" @@ -412,7 +501,7 @@ msgstr "" " * точку и отрезок (точка на середине отрезка)\n" " * отрезок и рабочую плоскость (середина отрезка на плоскости)\n" -#: constraint.cpp:486 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -432,7 +521,7 @@ msgstr "" " * рабочую плоскость и две точки / отрезок (симметричность относительно " "рабочей плоскости\n" -#: constraint.cpp:500 +#: constraint.cpp:623 msgid "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." @@ -440,7 +529,7 @@ msgstr "" "Рабочая плоскость должна быть активна для того, чтобы создать\n" "ограничение симметричности без явного указания плоскости симметрии." -#: constraint.cpp:530 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." @@ -448,86 +537,104 @@ msgstr "" "Рабочая плоскость должна быть активирована (Эскиз -> В рабочей плоскости)\n" "перед тем, как накладывать ограничения горизонтальности / вертикальности." -#: constraint.cpp:543 +#: constraint.cpp:679 msgid "" "Bad selection for horizontal / vertical constraint. This constraint can " "apply to:\n" "\n" -" * two points\n" -" * a line segment\n" +" * two or more points\n" +" * one or more line segments\n" msgstr "" "Неправильное выделение для ограничения 'горизонтальность / вертикальность'.\n" "Ограничение может принимать в качестве выделения следующие примитивы:\n" "\n" -" * две точки\n" -" * отрезок\n" +" * две или более точек\n" +" * один или более отрезков\n" -#: constraint.cpp:564 +#: constraint.cpp:697 msgid "" "Bad selection for same orientation constraint. This constraint can apply " "to:\n" "\n" " * two normals\n" msgstr "" -"Неправильное выделение для ограничения \"идентичная ориентация\".\n" +"Неправильное выделение для ограничения 'идентичная ориентация'.\n" "Ограничение может принимать в качестве выделения следующие примитивы:\n" "\n" " * два координатных базиса('нормали')\n" -#: constraint.cpp:614 +#: constraint.cpp:748 msgid "Must select an angle constraint." msgstr "" "Переключатся между смежными углами можно только выбрав ограничение угла." -#: constraint.cpp:627 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." msgstr "" "Переключать режим 'размера для справок' возможно только для ограничений, " "имеющих размерное значение." -#: constraint.cpp:638 +#: constraint.cpp:784 msgid "" "Bad selection for angle constraint. This constraint can apply to:\n" "\n" +"Angle between:\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" +"\n" +"Equal angles:\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" msgstr "" -"Неправильное выделение для ограничения углового размера.\n" +"Неправильное выделение для ограничения углов.\n" "Ограничение может принимать в качестве выделения следующие примитивы:\n" "\n" +"Угол между:\n" " * два отрезка\n" " * отрезок и координатный базис (нормаль)\n" " * два координатных базиса (нормали)\n" +"\n" +"Равные углы:\n" +" * четыре отрезка или нормали (равенство углов A,B и C,D)\n" +" * три отрезка или нормали (равенство углов A,B and B,C)\n" -#: constraint.cpp:701 +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." msgstr "" "Ограничение касательности может быть наложено только в рабочей плоскости." -#: constraint.cpp:711 +#: constraint.cpp:887 msgid "" "Bad selection for parallel / tangent constraint. This constraint can apply " "to:\n" "\n" -" * two line segments (parallel)\n" -" * a line segment and a normal (parallel)\n" -" * two normals (parallel)\n" +" * two faces\n" +" * two or more line segments (parallel)\n" +" * one or more line segments and one or more normals (parallel)\n" +" * two or more normals (parallel)\n" " * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +" * two line segments, arcs, or beziers, that do not share an endpoint and " +"the end point(s) of the curve(s) (tangent)\n" msgstr "" "Неправильное выделение для ограничения параллельности / касательности.\n" "Ограничение может принимать в качестве выделения следующие примитивы:\n" "\n" -" * два отрезка (параллельность)\n" -" * отрезок и координатный базис (нормаль) (параллельность)\n" -" * два координатных базиса (нормали) (параллельность)\n" +" * две граней\n" +" * два или более отрезка (параллельность)\n" +" * два или более отрезка и одинь или более координатных базисов (нормали) " +"(параллельность)\n" +" * два или более координатных базиса (нормали) (параллельность)\n" " * два отрезка, две дуги или два сплайна, соединенных крайними точками " "(касательность)\n" +" * два отрезка, две дуги или два сплайна, не имеющие общей крайней точки," +"но с выбранными крайними точками (касательность)\n" -#: constraint.cpp:729 +#: constraint.cpp:914 msgid "" "Bad selection for perpendicular constraint. This constraint can apply to:\n" "\n" +" * two faces\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" @@ -535,23 +642,28 @@ msgstr "" "Неправильное выделение для ограничения перпендикулярности.\n" "Ограничение может принимать в качестве выделения следующие примитивы:\n" "\n" +" * две граней\n" " * два отрезка\n" " * отрезок и координатный базис (нормаль)\n" " * два координатных базиса (нормали)\n" -#: constraint.cpp:744 +#: constraint.cpp:931 msgid "" "Bad selection for lock point where dragged constraint. This constraint can " "apply to:\n" "\n" " * a point\n" msgstr "" -"Неправильное выделение для ограничения 'Фиксация'.\n" +"Неправильное выделение для ограничения 'фиксация'.\n" "Ограничение может принимать в качестве выделения следующие примитивы:\n" "\n" " * точку\n" -#: constraint.cpp:755 +#: constraint.cpp:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "КОММЕНТАРИЙ -- ДВОЙНОЙ ЩЕЛЧОК ДЛЯ РЕДАКТИРОВАНИЯ" + +#: constraint.cpp:952 msgid "click center of comment text" msgstr "кликните мышью там, где будет расположен текстовый комментарий" @@ -579,26 +691,26 @@ msgstr "" " * точку и два отрезка (сечение плоскостью, заданной двумя отрезками, " "построенной через указанную точку)\n" -#: export.cpp:822 +#: export.cpp:818 msgid "Active group mesh is empty; nothing to export." msgstr "Активная группа не содержит тел; нечего экспортировать." -#: exportvector.cpp:337 +#: exportvector.cpp:336 msgid "freehand lines were replaced with continuous lines" -msgstr "Стили линии 'от руки' были заменены сплошными линиями" +msgstr "стили линии 'от руки' были заменены сплошными линиями" -#: exportvector.cpp:339 +#: exportvector.cpp:338 msgid "zigzag lines were replaced with continuous lines" -msgstr "Стили линии 'зиг-заг' были заменены сплошными линиями" +msgstr "ломаные линии были заменены сплошными линиями" -#: exportvector.cpp:593 +#: exportvector.cpp:592 msgid "" "Some aspects of the drawing have no DXF equivalent and were not exported:\n" msgstr "" "Некоторые элементы чертежа не имеют аналогов в DXF-представлении и не были " "экспортированы:\n" -#: exportvector.cpp:839 +#: exportvector.cpp:838 msgid "" "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." msgstr "" @@ -615,30 +727,30 @@ msgctxt "group-name" msgid "#references" msgstr "система-координат" -#: file.cpp:552 +#: file.cpp:555 msgid "The file is empty. It may be corrupt." msgstr "Файл пуст. Возможно он поврежден." -#: file.cpp:557 +#: file.cpp:560 msgid "" "Unrecognized data in file. This file may be corrupt, or from a newer version " "of the program." msgstr "" "Некоторые данные из этого файла не распознаны. Возможно, файл поврежден или " -"создан в более новой версии программы" +"создан в более новой версии программы." -#: file.cpp:867 +#: file.cpp:876 msgctxt "title" msgid "Missing File" msgstr "Файл Отсутствует" -#: file.cpp:868 +#: file.cpp:877 #, c-format msgctxt "dialog" msgid "The linked file “%s” is not present." msgstr "Связанный файл “%s” отсутствует." -#: file.cpp:870 +#: file.cpp:879 msgctxt "dialog" msgid "" "Do you want to locate it manually?\n" @@ -650,17 +762,17 @@ msgstr "" "Если вы ответите \"Нет\", то вся геометрия, которая зависит от " "отсутствующего файла будет удалена." -#: file.cpp:873 +#: file.cpp:882 msgctxt "button" msgid "&Yes" msgstr "Да" -#: file.cpp:875 +#: file.cpp:884 msgctxt "button" msgid "&No" msgstr "Нет" -#: file.cpp:877 solvespace.cpp:569 +#: file.cpp:886 solvespace.cpp:652 msgctxt "button" msgid "&Cancel" msgstr "Отменить" @@ -671,7 +783,7 @@ msgstr "&Файл" #: graphicswin.cpp:42 msgid "&New" -msgstr "&Новый" +msgstr "Соз&дать" #: graphicswin.cpp:43 msgid "&Open..." @@ -679,7 +791,7 @@ msgstr "&Открыть..." #: graphicswin.cpp:44 msgid "Open &Recent" -msgstr "Открыть Н&едавний" +msgstr "Открыть н&едавние файлы" #: graphicswin.cpp:45 msgid "&Save" @@ -687,31 +799,31 @@ msgstr "&Сохранить" #: graphicswin.cpp:46 msgid "Save &As..." -msgstr "Сохранить &Как..." +msgstr "Сохранить &Как…" #: graphicswin.cpp:48 msgid "Export &Image..." -msgstr "Экспортировать И&зображение..." +msgstr "Экспортировать и&зображение…" #: graphicswin.cpp:49 msgid "Export 2d &View..." -msgstr "Экспортировать &2d Вид..." +msgstr "Экспортировать &2d-вид…" #: graphicswin.cpp:50 msgid "Export 2d &Section..." -msgstr "Экспортировать 2d Се&чение..." +msgstr "Экспортировать 2d-се&чение…" #: graphicswin.cpp:51 msgid "Export 3d &Wireframe..." -msgstr "Экспортировать &3d Каркас..." +msgstr "Экспортировать &3d-каркас…" #: graphicswin.cpp:52 msgid "Export Triangle &Mesh..." -msgstr "Экспортировать &Полигональную сетку..." +msgstr "Экспортировать &полигональную сетку…" #: graphicswin.cpp:53 msgid "Export &Surfaces..." -msgstr "Экспортировать Повер&хности..." +msgstr "Экспортировать повер&хности…" #: graphicswin.cpp:54 msgid "Im&port..." @@ -735,15 +847,15 @@ msgstr "&Вернуть" #: graphicswin.cpp:63 msgid "Re&generate All" -msgstr "&Перегенерировать Все" +msgstr "&Перегенерировать все" #: graphicswin.cpp:65 msgid "Snap Selection to &Grid" -msgstr "Привязать Выделение к &Сетке" +msgstr "Привязать выделение к &сетке" #: graphicswin.cpp:66 msgid "Rotate Imported &90°" -msgstr "Повернуть Импортированное на &90°" +msgstr "Повернуть импортированное на &90°" #: graphicswin.cpp:68 msgid "Cu&t" @@ -759,7 +871,7 @@ msgstr "В&ставить" #: graphicswin.cpp:71 msgid "Paste &Transformed..." -msgstr "Вставить с &Трансформацией..." +msgstr "Вставить с &трансформацией…" #: graphicswin.cpp:72 msgid "&Delete" @@ -767,23 +879,23 @@ msgstr "&Удалить" #: graphicswin.cpp:74 msgid "Select &Edge Chain" -msgstr "Вы&делить Последовательность Ребер" +msgstr "Вы&делить последовательность ребер" #: graphicswin.cpp:75 msgid "Select &All" -msgstr "В&ыделить Все" +msgstr "В&ыделить всё" #: graphicswin.cpp:76 msgid "&Unselect All" -msgstr "С&бросить Выделение" +msgstr "С&бросить выделение" #: graphicswin.cpp:78 msgid "&Line Styles..." -msgstr "Стили Линий..." +msgstr "Стили линий…" #: graphicswin.cpp:79 msgid "&View Projection..." -msgstr "&View Прое&кция..." +msgstr "&Проекция вида..." #: graphicswin.cpp:81 msgid "Con&figuration..." @@ -803,326 +915,338 @@ msgstr "От&далить" #: graphicswin.cpp:87 msgid "Zoom To &Fit" -msgstr "Показать &Все / Выделенное" +msgstr "Показать &всё / выделенное" #: graphicswin.cpp:89 msgid "Align View to &Workplane" -msgstr "Выровнять Вид на &Рабочую Плоскость" +msgstr "Выровнять вид по &рабочей плоскости" #: graphicswin.cpp:90 msgid "Nearest &Ortho View" -msgstr "Ближайший &Ортогональный Вид" +msgstr "Ближайший &ортогональный вид" #: graphicswin.cpp:91 msgid "Nearest &Isometric View" -msgstr "Ближайший &Изометрический Вид" +msgstr "Ближайший &изометрический вид" #: graphicswin.cpp:92 msgid "&Center View At Point" -msgstr "&Центрировать Вид на Точке" +msgstr "&Центрировать вид на точке" #: graphicswin.cpp:94 msgid "Show Snap &Grid" -msgstr "Показать &Сетку" +msgstr "Показать &сетку" #: graphicswin.cpp:95 msgid "Darken Inactive Solids" -msgstr "Затемнять Неактивные Тела" +msgstr "Затемнять неактивные тела" #: graphicswin.cpp:96 msgid "Use &Perspective Projection" -msgstr "Перспективная Прое&кция" +msgstr "Перспективная прое&кция" #: graphicswin.cpp:97 -msgid "Dimension &Units" -msgstr "Единицы Измерения" +msgid "Show E&xploded View" +msgstr "Показать &развернутый вид" #: graphicswin.cpp:98 -msgid "Dimensions in &Millimeters" -msgstr "Размеры в Ми&ллиметрах" +msgid "Dimension &Units" +msgstr "Единицы &измерения" #: graphicswin.cpp:99 -msgid "Dimensions in M&eters" -msgstr "Размеры в Метрах" +msgid "Dimensions in &Millimeters" +msgstr "Размеры в ми&ллиметрах" #: graphicswin.cpp:100 +msgid "Dimensions in M&eters" +msgstr "Размеры в метрах" + +#: graphicswin.cpp:101 msgid "Dimensions in &Inches" -msgstr "Размеры в Дю&ймах" +msgstr "Размеры в дю&ймах" #: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "Размеры в &футах и дюймах" + +#: graphicswin.cpp:104 msgid "Show &Toolbar" -msgstr "Показывать Па&нель Инструментов" +msgstr "Показывать па&нель инструментов" -#: graphicswin.cpp:103 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" -msgstr "Показывать Брау&зер" +msgstr "Показывать брау&зер" -#: graphicswin.cpp:105 +#: graphicswin.cpp:107 msgid "&Full Screen" -msgstr "Полно&экранный Режим" +msgstr "Полно&экранный режим" -#: graphicswin.cpp:107 +#: graphicswin.cpp:109 msgid "&New Group" -msgstr "&Группа" +msgstr "&Новая группа" -#: graphicswin.cpp:108 +#: graphicswin.cpp:110 msgid "Sketch In &3d" -msgstr "Создать Эскиз в &3d" +msgstr "Создать эскиз в &3d" -#: graphicswin.cpp:109 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" -msgstr "Создать Эскиз в Новой &Рабочей Плоскости" +msgstr "Создать эскиз в новой &рабочей плоскости" -#: graphicswin.cpp:111 +#: graphicswin.cpp:113 msgid "Step &Translating" -msgstr "&Линейный Массив" +msgstr "&Линейный массив" -#: graphicswin.cpp:112 +#: graphicswin.cpp:114 msgid "Step &Rotating" -msgstr "&Круговой Массив" +msgstr "&Круговой массив" -#: graphicswin.cpp:114 +#: graphicswin.cpp:116 msgid "E&xtrude" -msgstr "Тело &Выдавливания" +msgstr "Тело &выдавливания" -#: graphicswin.cpp:115 +#: graphicswin.cpp:117 msgid "&Helix" -msgstr "Тело Винтовое" +msgstr "Тело в&интовое" -#: graphicswin.cpp:116 +#: graphicswin.cpp:118 msgid "&Lathe" -msgstr "Тело В&ращения" +msgstr "Тело в&ращения" -#: graphicswin.cpp:117 +#: graphicswin.cpp:119 msgid "Re&volve" -msgstr "Тело В&ращения" +msgstr "Тело в&ращения на угол" -#: graphicswin.cpp:119 +#: graphicswin.cpp:121 msgid "Link / Assemble..." -msgstr "&Импорт Детали / Сборка..." +msgstr "&Импортировать деталь/сборку…" -#: graphicswin.cpp:120 +#: graphicswin.cpp:122 msgid "Link Recent" -msgstr "Последние &Детали" +msgstr "Импортировать недавний файл" -#: graphicswin.cpp:122 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "&Эскиз" -#: graphicswin.cpp:123 +#: graphicswin.cpp:125 msgid "In &Workplane" -msgstr "В &Рабочей Плоскости" +msgstr "В &рабочей плоскости" -#: graphicswin.cpp:124 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" msgstr "Режим &3d" -#: graphicswin.cpp:126 +#: graphicswin.cpp:128 msgid "Datum &Point" -msgstr "Опорная &Точка" - -#: graphicswin.cpp:127 -msgid "&Workplane" -msgstr "Рабочая &Плоскость" +msgstr "Опорная &точка" #: graphicswin.cpp:129 +msgid "Wor&kplane" +msgstr "Рабочая &плоскость" + +#: graphicswin.cpp:131 msgid "Line &Segment" msgstr "&Отрезок" -#: graphicswin.cpp:130 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" -msgstr "&Вспомогательный Отрезок" +msgstr "&Вспомогательный отрезок" -#: graphicswin.cpp:131 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "Прямоу&гольник" -#: graphicswin.cpp:132 +#: graphicswin.cpp:134 msgid "&Circle" msgstr "О&кружность" -#: graphicswin.cpp:133 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" -msgstr "Д&уга Окружности" +msgstr "Д&уга окружности" -#: graphicswin.cpp:134 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" -msgstr "Кубический &Сплайн Безье" +msgstr "Кубический &сплайн Безье" -#: graphicswin.cpp:136 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" msgstr "Т&екст TrueType" -#: graphicswin.cpp:137 -msgid "&Image" +#: graphicswin.cpp:139 +msgid "I&mage" msgstr "И&зображение" -#: graphicswin.cpp:139 +#: graphicswin.cpp:141 msgid "To&ggle Construction" -msgstr "Переключить Режим Вс&помогательных Построений" +msgstr "Переключить режим вс&помогательных построений" -#: graphicswin.cpp:140 -msgid "Tangent &Arc at Point" -msgstr "Кас&ательная в Точке" +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" +msgstr "Кас&ательная в точке" -#: graphicswin.cpp:141 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" -msgstr "Ра&збить Кривые Пересечением" +msgstr "Ра&збить кривые пересечением" -#: graphicswin.cpp:143 +#: graphicswin.cpp:145 msgid "&Constrain" -msgstr "&Ограничения" +msgstr "&Ограничение" -#: graphicswin.cpp:144 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "&Расстояние / Диаметр" -#: graphicswin.cpp:145 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" -msgstr "&Справочный Размер" +msgstr "Справочный ра&змер" -#: graphicswin.cpp:146 -msgid "A&ngle" -msgstr "&Угол" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "&Угол / Равенство углов" -#: graphicswin.cpp:147 +#: graphicswin.cpp:149 msgid "Reference An&gle" -msgstr "С&правочный Угол" +msgstr "Справочный &угол" -#: graphicswin.cpp:148 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" -msgstr "Переключить Сме&жный Угол" +msgstr "Переключить сме&жный угол" -#: graphicswin.cpp:149 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" -msgstr "Переключить Режим Размера Для Спра&вок" +msgstr "Переключить &режим справочного размера" -#: graphicswin.cpp:151 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "&Горизонтальность" -#: graphicswin.cpp:152 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "&Вертикальность" -#: graphicswin.cpp:154 -msgid "&On Point / Curve / Plane" -msgstr "&Точка на Примитиве" - -#: graphicswin.cpp:155 -msgid "E&qual Length / Radius / Angle" -msgstr "&Равенство Длин / Радиусов / Углов" - #: graphicswin.cpp:156 -msgid "Length Ra&tio" -msgstr "Отно&шение Длин" +msgid "&On Point / Curve / Plane" +msgstr "&Точка на примитиве" #: graphicswin.cpp:157 -msgid "Length Diff&erence" -msgstr "Ра&зница Длин" +msgid "E&qual Length / Radius" +msgstr "&Равенство длин / радиусов" #: graphicswin.cpp:158 -msgid "At &Midpoint" -msgstr "&На Середине" +msgid "Length / Arc Ra&tio" +msgstr "&Отношение длин (дуга)" #: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Р&азность длин (дуга)" + +#: graphicswin.cpp:160 +msgid "At &Midpoint" +msgstr "&На середине" + +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "С&имметричность" -#: graphicswin.cpp:160 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "Пара&ллельность / Касательность" -#: graphicswin.cpp:161 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "Перпендикул&ярность" -#: graphicswin.cpp:162 +#: graphicswin.cpp:164 msgid "Same Orient&ation" -msgstr "Идентичная &Ориентация" +msgstr "Идентичная &ориентация" -#: graphicswin.cpp:163 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "За&фиксировать" -#: graphicswin.cpp:165 +#: graphicswin.cpp:167 msgid "Comment" -msgstr "Текстовый &Комментарий" +msgstr "Комментарий" -#: graphicswin.cpp:167 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "&Анализ" -#: graphicswin.cpp:168 +#: graphicswin.cpp:170 msgid "Measure &Volume" -msgstr "Измерить &Объем" +msgstr "Измерить &объем" -#: graphicswin.cpp:169 +#: graphicswin.cpp:171 msgid "Measure A&rea" -msgstr "Измерить П&лощадь" +msgstr "Измерить п&лощадь" -#: graphicswin.cpp:170 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" -msgstr "Измерить П&ериметр" +msgstr "Измерить п&ериметр" -#: graphicswin.cpp:171 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" -msgstr "Показать Пе&ресекающиеся Детали" +msgstr "Показать пе&ресекающиеся детали" -#: graphicswin.cpp:172 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" -msgstr "Показать Про&блемные Ребра" +msgstr "Показать про&блемные ребра" -#: graphicswin.cpp:173 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" -msgstr "Показать Центр Масс" +msgstr "Показать &центр массы" -#: graphicswin.cpp:175 +#: graphicswin.cpp:177 msgid "Show &Underconstrained Points" -msgstr "Показать Свободные Точки" +msgstr "Показать c&вободные точки" -#: graphicswin.cpp:177 +#: graphicswin.cpp:179 msgid "&Trace Point" -msgstr "Включить &Трассировку Точки" +msgstr "Включить &трассировку точки" -#: graphicswin.cpp:178 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." -msgstr "Остановить Тра&ссировку..." +msgstr "Остановить тра&ссировку..." -#: graphicswin.cpp:179 +#: graphicswin.cpp:181 msgid "Step &Dimension..." -msgstr "Плавное Из&менение Размера..." +msgstr "Плавное из&менение размера…" -#: graphicswin.cpp:181 +#: graphicswin.cpp:183 msgid "&Help" -msgstr "&Помощь" +msgstr "&Справка" -#: graphicswin.cpp:182 +#: graphicswin.cpp:184 msgid "&Language" msgstr "&Язык" -#: graphicswin.cpp:183 +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "Вебсайт / &Справка" -#: graphicswin.cpp:185 +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "Пере&йти к коммиту на GitHub" + +#: graphicswin.cpp:188 msgid "&About" msgstr "О &Программе" -#: graphicswin.cpp:355 +#: graphicswin.cpp:362 msgid "(no recent files)" msgstr "(пусто)" -#: graphicswin.cpp:363 +#: graphicswin.cpp:370 #, c-format msgid "File '%s' does not exist." msgstr "Файл '%s' не существует." -#: graphicswin.cpp:725 +#: graphicswin.cpp:779 msgid "No workplane is active, so the grid will not appear." msgstr "Сетку не будет видно, пока рабочая плоскость не активирована." -#: graphicswin.cpp:740 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel " "projection.\n" @@ -1136,25 +1260,25 @@ msgstr "" "перспективы на конфигурационной странице браузера.\n" "Значение по умолчанию 0.3." -#: graphicswin.cpp:819 +#: graphicswin.cpp:884 msgid "" "Select a point; this point will become the center of the view on screen." msgstr "Выделите точку. Вид будет отцентрован по этой точке." -#: graphicswin.cpp:1114 +#: graphicswin.cpp:1193 msgid "No additional entities share endpoints with the selected entities." msgstr "Нет дополнительных объектов, соединенных с выбранными примитивами." -#: graphicswin.cpp:1132 +#: graphicswin.cpp:1211 msgid "" "To use this command, select a point or other entity from an linked part, or " "make a link group the active group." msgstr "" "Чтобы использовать эту команду, выделите точку или другой примитив, " -"принадлежащий импортированной детали или активируйте группу импортированной " +"принадлежащий импортированной детали, или активируйте группу импортированной " "детали." -#: graphicswin.cpp:1155 +#: graphicswin.cpp:1234 msgid "" "No workplane is active. Activate a workplane (with Sketch -> In Workplane) " "to define the plane for the snap grid." @@ -1162,7 +1286,7 @@ msgstr "" "Рабочая плоскость не активна. Активируйте ее через Эскиз -> В Рабочей " "Плоскости чтобы определить плоскость для сетки." -#: graphicswin.cpp:1162 +#: graphicswin.cpp:1241 msgid "" "Can't snap these items to grid; select points, text comments, or constraints " "with a label. To snap a line, select its endpoints." @@ -1171,13 +1295,13 @@ msgstr "" "текстовые комментарии или ограничения с размерными значениями. Чтобы " "привязать отрезок или другой примитив, выбирайте его точки." -#: graphicswin.cpp:1247 +#: graphicswin.cpp:1326 msgid "No workplane selected. Activating default workplane for this group." msgstr "" "Рабочая плоскость не активна. Активирована рабочая плоскость по умолчанию " "для данной группы." -#: graphicswin.cpp:1250 +#: graphicswin.cpp:1329 msgid "" "No workplane is selected, and the active group does not have a default " "workplane. Try selecting a workplane, or activating a sketch-in-new-" @@ -1187,7 +1311,7 @@ msgstr "" "по умолчанию. Попробуйте выделить рабочую плоскость или создать новую с " "помощью Группа -> Создать Эскиз в Новой Рабочей Плоскости." -#: graphicswin.cpp:1271 +#: graphicswin.cpp:1350 msgid "" "Bad selection for tangent arc at point. Select a single point, or select " "nothing to set up arc parameters." @@ -1196,54 +1320,54 @@ msgstr "" "точку, либо запустите команду без выделения, чтобы перейти к окну настроек " "этой команды." -#: graphicswin.cpp:1282 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" msgstr "" "кликните мышью там, где хотите создать дугу окружности (дуга будет " "нарисована против часовой стрелки)" -#: graphicswin.cpp:1283 +#: graphicswin.cpp:1362 msgid "click to place datum point" msgstr "кликните мышью там, где хотите создать опорную точку" -#: graphicswin.cpp:1284 +#: graphicswin.cpp:1363 msgid "click first point of line segment" msgstr "кликните мышью там, где хотите создать первую точку отрезка" -#: graphicswin.cpp:1286 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" msgstr "" "кликните мышью там, где хотите создать первую точку вспомогательного отрезка" -#: graphicswin.cpp:1287 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" msgstr "" "кликните мышью там, где хотите создать первую точку кубического сплайна Безье" -#: graphicswin.cpp:1288 +#: graphicswin.cpp:1367 msgid "click center of circle" msgstr "кликните мышью там, где будет находиться центр окружности" -#: graphicswin.cpp:1289 +#: graphicswin.cpp:1368 msgid "click origin of workplane" msgstr "" "кликните мышью там, где будет находиться точка, через которую будет " "построена рабочая плоскость" -#: graphicswin.cpp:1290 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" msgstr "кликните мышью там, где будет находиться один из углов прямоугольника" -#: graphicswin.cpp:1291 +#: graphicswin.cpp:1370 msgid "click top left of text" msgstr "кликните мышью там, где хотите создать текст" -#: graphicswin.cpp:1297 +#: graphicswin.cpp:1376 msgid "click top left of image" msgstr "" "кликните мышью там, где будет расположен левый верхний угол изображения" -#: graphicswin.cpp:1309 +#: graphicswin.cpp:1402 msgid "" "No entities are selected. Select entities before trying to toggle their " "construction state." @@ -1256,26 +1380,25 @@ msgctxt "group-name" msgid "sketch-in-3d" msgstr "эскиз-в-3d" -#: group.cpp:142 +#: group.cpp:154 msgid "" "Bad selection for new sketch in workplane. This group can be created with:\n" "\n" " * a point (through the point, orthogonal to coordinate axes)\n" " * a point and two line segments (through the point, parallel to the " "lines)\n" +" * a point and a normal (through the point, orthogonal to the normal)\n" " * a workplane (copy of the workplane)\n" msgstr "" -"Неправильное выделение для создания эскиза.\n" -"Группа может быть создана, используя в качестве выделения следующие " -"примитивы:\n" +"Неправильное выделение для создания эскиза в рабочей плоскости. Группа может " +"быть создана, используя в качестве выделения следующие примитивы:\n" "\n" -" * точку (рабочая плоскость, ориентированная к ближайшему виду, " -"проходящая через точку)\n" -" * точку и два отрезка (рабочая плоскость, проходящая через точку и " -"параллельная отрезкам)\n" -" * рабочую плоскость (копия рабочей плоскости)\n" +" * точку (через точку, ортогонально осям координат)\n" +" * точку и два отрезка (через точку, параллельно отрезкам)\n" +" * точку и нормаль (через точку, ортогонально нормали)\n" +" * рабочую плоскость (копию рабочей плоскости)\n" -#: group.cpp:154 +#: group.cpp:170 msgid "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " "will be extruded normal to the workplane." @@ -1283,18 +1406,18 @@ msgstr "" "Выберите рабочую плоскость (Эскиз -> В Рабочей Плоскости) перед созданием " "группы выдавливания. Эскиз будет выдавлен по нормали к рабочей плоскости." -#: group.cpp:163 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" msgstr "тело-выдавливания" -#: group.cpp:168 +#: group.cpp:184 msgid "Lathe operation can only be applied to planar sketches." msgstr "" "Операция создания тела вращения может быть применена только к плоским " "эскизам." -#: group.cpp:179 +#: group.cpp:195 msgid "" "Bad selection for new lathe group. This group can be created with:\n" "\n" @@ -1306,23 +1429,23 @@ msgstr "" "Группа может быть создана, используя в качестве выделения следующие " "примитивы:\n" "\n" -" * точку и отрезок / координатных базис (нормаль) (тело вращения вокруг " -"оси, проходящей через точку и параллельной отрезку / нормали)\n" -" * отрезок (тело вращения вокруг оси, проходящей через отрезок)\n" +" * точку и отрезок / координатный базис (нормаль) (вращение вокруг оси, " +"проходящей через точку и параллельной отрезку / нормали)\n" +" * отрезок (вращение вокруг оси, проходящей через отрезок)\n" "\n" -#: group.cpp:189 +#: group.cpp:205 msgctxt "group-name" msgid "lathe" msgstr "тело-вращения" -#: group.cpp:194 +#: group.cpp:210 msgid "Revolve operation can only be applied to planar sketches." msgstr "" -"Операция создания тела вращения может быть применена только к плоским " -"эскизам." +"Операция создания тела вращения на угол может быть применена только к " +"плоским эскизам." -#: group.cpp:205 +#: group.cpp:221 msgid "" "Bad selection for new revolve group. This group can be created with:\n" "\n" @@ -1330,27 +1453,27 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" -"Неправильное выделение для создания группы тела вращения. \n" +"Неправильное выделение для создания группы тела вращения на угол. \n" "Группа может быть создана, используя в качестве выделения следующие " "примитивы:\n" "\n" -" * точку и отрезок / координатных базис (нормаль) (тело вращения вокруг " -"оси, проходящей через точку и параллельной отрезку / нормали)\n" -" * отрезок (тело вращения вокруг оси, проходящей через отрезок)\n" +" * точку и отрезок / координатный базис (нормаль) (вращение вокруг оси, " +"проходящей через точку и параллельной отрезку / нормали)\n" +" * отрезок (вращение вокруг оси, проходящей через отрезок)\n" "\n" -#: group.cpp:217 +#: group.cpp:233 msgctxt "group-name" msgid "revolve" -msgstr "тело-вращения" +msgstr "тело-вращения-на-угол" -#: group.cpp:222 +#: group.cpp:238 msgid "Helix operation can only be applied to planar sketches." msgstr "" "Операция создания винтового тела может быть применена только к плоским " "эскизам." -#: group.cpp:233 +#: group.cpp:249 msgid "" "Bad selection for new helix group. This group can be created with:\n" "\n" @@ -1367,12 +1490,12 @@ msgstr "" "точку)\n" " * отрезок (вращение вокруг отрезка)\n" -#: group.cpp:245 +#: group.cpp:261 msgctxt "group-name" msgid "helix" msgstr "тело-винтовое" -#: group.cpp:258 +#: group.cpp:274 msgid "" "Bad selection for new rotation. This group can be created with:\n" "\n" @@ -1387,47 +1510,51 @@ msgstr "" "\n" " * точку при активной рабочей плоскости (вращение в плоскости вокруг " "выбранной точки)\n" -" * точку и отрезок / координатных базис (нормаль) (вращение вокруг оси, " +" * точку и отрезок / координатный базис (нормаль) (вращение вокруг оси, " "проходящей через точку и параллельной отрезку / нормали)\n" "\n" -#: group.cpp:271 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" msgstr "круговой-массив" -#: group.cpp:282 +#: group.cpp:298 msgctxt "group-name" msgid "translate" msgstr "линейный-массив" -#: group.cpp:400 +#: group.cpp:422 msgid "(unnamed)" msgstr "(без имени)" -#: groupmesh.cpp:709 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" msgstr "незамкнутый контур или несовпадение стилей!" -#: groupmesh.cpp:722 +#: groupmesh.cpp:723 msgid "points not all coplanar!" msgstr "не все точки лежат в одной плоскости!" -#: groupmesh.cpp:724 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" msgstr "контур имеет самопересечения!" -#: groupmesh.cpp:726 +#: groupmesh.cpp:727 msgid "zero-length edge!" -msgstr "вырожденный отрезок!" +msgstr "ребро нулевой длины!" -#: modify.cpp:254 +#: importmesh.cpp:136 +msgid "Text-formated STL files are not currently supported" +msgstr "Текстовые файлы STL пока не поддерживаются" + +#: modify.cpp:252 msgid "Must be sketching in workplane to create tangent arc." msgstr "" "Скругления эскиза можно создавать только когда рабочая плоскость " "активирована." -#: modify.cpp:301 +#: modify.cpp:299 msgid "" "To create a tangent arc, select a point where two non-construction lines or " "circles in this group and workplane join." @@ -1435,7 +1562,7 @@ msgstr "" "Чтобы создать скругление эскиза, выберите точку, где соединяются два " "примитива, не принадлежащих к вспомогательной геометрии." -#: modify.cpp:388 +#: modify.cpp:386 msgid "" "Couldn't round this corner. Try a smaller radius, or try creating the " "desired geometry by hand with tangency constraints." @@ -1443,18 +1570,18 @@ msgstr "" "Невозможно скруглить угол. Попробуйте радиус поменьше или создайте требуемую " "геометрию с помощью Ограничения -> Параллельность / Касательность." -#: modify.cpp:597 +#: modify.cpp:595 msgid "Couldn't split this entity; lines, circles, or cubics only." msgstr "" "Невозможно разделить такие примитивы. Выберите линии, окружности или " "кубические сплайны." -#: modify.cpp:624 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." msgstr "" "Пересечение примитивов работает только когда рабочая плоскость активна." -#: modify.cpp:631 +#: modify.cpp:629 msgid "" "Select two entities that intersect each other (e.g. two lines/circles/arcs " "or a line/circle/arc and a point)." @@ -1462,116 +1589,116 @@ msgstr "" "Выберите два пересекающихся примитива (два отрезка/окружности/дуги или " "отрезок/окружность/дугу и точку)" -#: modify.cpp:736 +#: modify.cpp:734 msgid "Can't split; no intersection found." msgstr "Невозможно разделить пересекаемые примитивы: пересечений нет." -#: mouse.cpp:559 +#: mouse.cpp:558 msgid "Assign to Style" -msgstr "Применить Стиль" +msgstr "Назначить стилю" -#: mouse.cpp:575 +#: mouse.cpp:574 msgid "No Style" -msgstr "Стиль по Умолчанию" +msgstr "Без стиля" -#: mouse.cpp:578 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." -msgstr "Создать Новый Стиль..." +msgstr "Создать новый стиль…" -#: mouse.cpp:585 +#: mouse.cpp:584 msgid "Group Info" -msgstr "Настройки Группы" +msgstr "Настройки группы" -#: mouse.cpp:605 +#: mouse.cpp:604 msgid "Style Info" -msgstr "Настройки Стиля" +msgstr "Настройки стиля" -#: mouse.cpp:625 +#: mouse.cpp:624 msgid "Select Edge Chain" -msgstr "Выделить Последовательность Примитивов" +msgstr "Выделить последовательность ребер" -#: mouse.cpp:631 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" -msgstr "Переключить Режим Размера Для Справок" +msgstr "Переключить справочный размер" -#: mouse.cpp:637 +#: mouse.cpp:636 msgid "Other Supplementary Angle" -msgstr "Переключить Смежный Угол" +msgstr "Переключить смежный угол" -#: mouse.cpp:642 +#: mouse.cpp:641 msgid "Snap to Grid" -msgstr "Привязать к Сетке" +msgstr "Привязать к сетке" -#: mouse.cpp:651 +#: mouse.cpp:650 msgid "Remove Spline Point" -msgstr "Удалить Точку Сплайна" +msgstr "Удалить точку сплайна" -#: mouse.cpp:686 +#: mouse.cpp:685 msgid "Add Spline Point" -msgstr "Добавить Точку Сплайна" +msgstr "Добавить точку сплайна" -#: mouse.cpp:690 +#: mouse.cpp:689 msgid "Cannot add spline point: maximum number of points reached." msgstr "" "Невозможно добавить точку сплайна: достигнуто ограничение максимального " "количества точек для сплайна." -#: mouse.cpp:715 +#: mouse.cpp:714 msgid "Toggle Construction" -msgstr "Переключить Режим 'Дополнительные Построения'." +msgstr "Переключить режим «Дополнительные построения»" #: mouse.cpp:730 msgid "Delete Point-Coincident Constraint" -msgstr "Удалить Ограничение Совпадения Точек" +msgstr "Удалить ограничение совпадения точек" -#: mouse.cpp:749 +#: mouse.cpp:748 msgid "Cut" msgstr "Вырезать" -#: mouse.cpp:751 +#: mouse.cpp:750 msgid "Copy" msgstr "Копировать" -#: mouse.cpp:755 +#: mouse.cpp:754 msgid "Select All" -msgstr "Выделить Все" +msgstr "Выделить всё" -#: mouse.cpp:760 +#: mouse.cpp:759 msgid "Paste" msgstr "Вставить" -#: mouse.cpp:762 +#: mouse.cpp:761 msgid "Paste Transformed..." -msgstr "Вставить с Трансформацией..." +msgstr "Вставить с трансформацией…" -#: mouse.cpp:767 +#: mouse.cpp:766 msgid "Delete" msgstr "Удалить" -#: mouse.cpp:770 +#: mouse.cpp:769 msgid "Unselect All" -msgstr "Сбросить Выделение" +msgstr "Сбросить выделение" -#: mouse.cpp:777 +#: mouse.cpp:776 msgid "Unselect Hovered" -msgstr "Снять Выделение с Выбранного" +msgstr "Снять выделение с выбранного" -#: mouse.cpp:786 +#: mouse.cpp:785 msgid "Zoom to Fit" -msgstr "Показать Все" +msgstr "Показать всё" -#: mouse.cpp:988 mouse.cpp:1275 +#: mouse.cpp:987 mouse.cpp:1276 msgid "click next point of line, or press Esc" msgstr "кликните мышью там, где хотите расположить следующую точку" -#: mouse.cpp:994 +#: mouse.cpp:993 msgid "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" "Невозможно начертить прямоугольник, когда рабочая плоскость не активна." -#: mouse.cpp:1028 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" msgstr "кликните мышью там, где хотите расположить другой угол прямоугольника" @@ -1591,9 +1718,7 @@ msgstr "кликните мышью там, где хотите создать #: mouse.cpp:1088 msgid "click next point of cubic, or press Esc" -msgstr "" -"кликните мышью там, где хотите создать следующую точку сплайна или нажмите " -"Esc для завершения операции." +msgstr "кликните следующую точку сплайна или нажмите Esc" #: mouse.cpp:1093 msgid "" @@ -1618,193 +1743,199 @@ msgid "" "Workplane." msgstr "Невозможно создать изображение. Активируйте рабочую плоскость." -#: mouse.cpp:1159 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" -msgstr "КОММЕНТАРИЙ -- ДВОЙНОЙ ЩЕЛЧОК ДЛЯ РЕДАКТИРОВАНИЯ" - -#: platform/gui.cpp:85 platform/gui.cpp:89 solvespace.cpp:511 +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 msgctxt "file-type" msgid "SolveSpace models" -msgstr "проекты SolveSpace" +msgstr "Проекты SolveSpace" + +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "ВСЕ" -#: platform/gui.cpp:90 +#: platform/gui.cpp:91 msgctxt "file-type" msgid "IDF circuit board" msgstr "IDF печатная плата" -#: platform/gui.cpp:94 +#: platform/gui.cpp:92 +msgctxt "file-type" +msgid "STL triangle mesh" +msgstr "STL треугольная сетка" + +#: platform/gui.cpp:96 msgctxt "file-type" msgid "PNG image" msgstr "PNG изображение" -#: platform/gui.cpp:98 +#: platform/gui.cpp:100 msgctxt "file-type" msgid "STL mesh" msgstr "STL полигональная сетка" -#: platform/gui.cpp:99 +#: platform/gui.cpp:101 msgctxt "file-type" msgid "Wavefront OBJ mesh" msgstr "Wavefront OBJ полигональная сетка" -#: platform/gui.cpp:100 +#: platform/gui.cpp:102 msgctxt "file-type" msgid "Three.js-compatible mesh, with viewer" -msgstr "Three.js-совместимая полигональная сетка с просмторщиком" +msgstr "Three.js-совместимая полигональная сетка с просмотрщиком" -#: platform/gui.cpp:101 +#: platform/gui.cpp:103 msgctxt "file-type" msgid "Three.js-compatible mesh, mesh only" msgstr "Three.js-совместимая полигональная сетка" -#: platform/gui.cpp:102 +#: platform/gui.cpp:104 msgctxt "file-type" msgid "VRML text file" msgstr "VRML файл" -#: platform/gui.cpp:106 platform/gui.cpp:113 platform/gui.cpp:120 +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 msgctxt "file-type" msgid "STEP file" msgstr "STEP файл" -#: platform/gui.cpp:110 +#: platform/gui.cpp:112 msgctxt "file-type" msgid "PDF file" msgstr "PDF документ" -#: platform/gui.cpp:111 +#: platform/gui.cpp:113 msgctxt "file-type" msgid "Encapsulated PostScript" msgstr "Encapsulated PostScript" -#: platform/gui.cpp:112 +#: platform/gui.cpp:114 msgctxt "file-type" msgid "Scalable Vector Graphics" msgstr "SVG изображение" -#: platform/gui.cpp:114 platform/gui.cpp:121 +#: platform/gui.cpp:116 platform/gui.cpp:123 msgctxt "file-type" msgid "DXF file (AutoCAD 2007)" msgstr "DXF файл (AutoCAD 2007)" -#: platform/gui.cpp:115 +#: platform/gui.cpp:117 msgctxt "file-type" msgid "HPGL file" msgstr "HPGL файл" -#: platform/gui.cpp:116 +#: platform/gui.cpp:118 msgctxt "file-type" msgid "G Code" msgstr "G Code" -#: platform/gui.cpp:125 +#: platform/gui.cpp:127 msgctxt "file-type" msgid "AutoCAD DXF and DWG files" msgstr "AutoCAD DXF и DWG файлы" -#: platform/gui.cpp:129 +#: platform/gui.cpp:131 msgctxt "file-type" msgid "Comma-separated values" msgstr "CSV файлы (значения, разделенные запятой)" -#: platform/guigtk.cpp:1324 platform/guimac.mm:1363 platform/guiwin.cpp:1639 +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" msgstr "без имени" -#: platform/guigtk.cpp:1335 platform/guigtk.cpp:1368 platform/guimac.mm:1321 -#: platform/guiwin.cpp:1582 +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 msgctxt "title" msgid "Save File" -msgstr "Сохранить Файл" +msgstr "Сохранить файл" -#: platform/guigtk.cpp:1336 platform/guigtk.cpp:1369 platform/guimac.mm:1304 -#: platform/guiwin.cpp:1584 +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 msgctxt "title" msgid "Open File" -msgstr "Открыть Файл" +msgstr "Открыть файл" -#: platform/guigtk.cpp:1339 platform/guigtk.cpp:1375 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" msgid "_Cancel" -msgstr "Отменить" +msgstr "О_тменить" -#: platform/guigtk.cpp:1340 platform/guigtk.cpp:1373 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" msgid "_Save" -msgstr "Сохранить" +msgstr "_Сохранить" -#: platform/guigtk.cpp:1341 platform/guigtk.cpp:1374 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" msgid "_Open" msgstr "_Открыть" -#: solvespace.cpp:169 +#: solvespace.cpp:175 msgctxt "title" msgid "Autosave Available" -msgstr "Автосохранение Доступно" +msgstr "Доступно автосохранение" -#: solvespace.cpp:170 +#: solvespace.cpp:176 msgctxt "dialog" msgid "An autosave file is available for this sketch." msgstr "Автоматически сохраненный файл доступен для данного проекта." -#: solvespace.cpp:171 +#: solvespace.cpp:177 msgctxt "dialog" msgid "Do you want to load the autosave file instead?" msgstr "Хотите загрузить автосохраненный файл вместо исходного?" -#: solvespace.cpp:172 +#: solvespace.cpp:178 msgctxt "button" msgid "&Load autosave" -msgstr "&Загрузить Автосохранение" +msgstr "&Загрузить автосохранение" -#: solvespace.cpp:174 +#: solvespace.cpp:180 msgctxt "button" msgid "Do&n't Load" -msgstr "&Не Загружать" +msgstr "&Не загружать" -#: solvespace.cpp:557 +#: solvespace.cpp:640 msgctxt "title" msgid "Modified File" -msgstr "Измененный Файл" +msgstr "Измененный файл" -#: solvespace.cpp:559 +#: solvespace.cpp:642 #, c-format msgctxt "dialog" msgid "Do you want to save the changes you made to the sketch “%s”?" msgstr "Сохранить изменения, сделанные в файле “%s”?" -#: solvespace.cpp:562 +#: solvespace.cpp:645 msgctxt "dialog" msgid "Do you want to save the changes you made to the new sketch?" msgstr "Сохранить изменения, сделанные в новом проекте?" -#: solvespace.cpp:565 +#: solvespace.cpp:648 msgctxt "dialog" msgid "Your changes will be lost if you don't save them." msgstr "Изменения будут утеряны, если их не сохранить." -#: solvespace.cpp:566 +#: solvespace.cpp:649 msgctxt "button" msgid "&Save" msgstr "&Сохранить" -#: solvespace.cpp:568 +#: solvespace.cpp:651 msgctxt "button" msgid "Do&n't Save" -msgstr "&Не Сохранять" +msgstr "&Не сохранять" -#: solvespace.cpp:589 +#: solvespace.cpp:672 msgctxt "title" msgid "(new sketch)" msgstr "(новый проект)" -#: solvespace.cpp:596 +#: solvespace.cpp:683 msgctxt "title" msgid "Property Browser" msgstr "Браузер" -#: solvespace.cpp:658 +#: solvespace.cpp:746 msgid "" "Constraints are currently shown, and will be exported in the toolpath. This " "is probably not what you want; hide them by clicking the link at the top of " @@ -1814,30 +1945,30 @@ msgstr "" "это не то, что требуется, если так, необходимо спрятать их, нажав ссылку " "вверху окна Браузера." -#: solvespace.cpp:730 +#: solvespace.cpp:834 #, c-format msgid "" "Can't identify file type from file extension of filename '%s'; try .dxf or ." "dwg." msgstr "" -"Неподдерживаемый тип файла '%s'; Поддерживаются файлы с расширением .dxf и ." +"Неподдерживаемый тип файла '%s'. Поддерживаются файлы с расширением .dxf и ." "dwg." -#: solvespace.cpp:778 +#: solvespace.cpp:886 msgid "Constraint must have a label, and must not be a reference dimension." msgstr "У ограничения должно быть значение и оно не должно быть справочным." -#: solvespace.cpp:782 +#: solvespace.cpp:890 msgid "Bad selection for step dimension; select a constraint." msgstr "" "Неправильное выделение для операции изменения значения с заданным шагом; " "необходимо выбрать ограничение со значением." -#: solvespace.cpp:806 +#: solvespace.cpp:914 msgid "The assembly does not interfere, good." msgstr "Сборка не содержит пересечения деталей - это хорошо." -#: solvespace.cpp:822 +#: solvespace.cpp:930 #, c-format msgid "" "The volume of the solid model is:\n" @@ -1848,7 +1979,7 @@ msgstr "" "\n" " %s" -#: solvespace.cpp:831 +#: solvespace.cpp:939 #, c-format msgid "" "\n" @@ -1861,7 +1992,7 @@ msgstr "" "\n" " %s" -#: solvespace.cpp:836 +#: solvespace.cpp:944 msgid "" "\n" "\n" @@ -1873,7 +2004,7 @@ msgstr "" "Кривые аппроксимированы кусочно-линейными функциями.\n" "Это приводит к ошибке в расчетах, обычно в пределах 1%." -#: solvespace.cpp:851 +#: solvespace.cpp:959 #, c-format msgid "" "The surface area of the selected faces is:\n" @@ -1890,7 +2021,7 @@ msgstr "" "Кривые аппроксимированы кусочно-линейными функциями.\n" "Это приводит к ошибке в расчетах, обычно в пределах 1%%." -#: solvespace.cpp:860 +#: solvespace.cpp:968 msgid "" "This group does not contain a correctly-formed 2d closed area. It is open, " "not coplanar, or self-intersecting." @@ -1898,7 +2029,7 @@ msgstr "" "Эта группа не содержит замкнутых областей. В ней нет замкнутых контуров, " "примитивы не лежат в одной плоскости или самопересекаются." -#: solvespace.cpp:872 +#: solvespace.cpp:980 #, c-format msgid "" "The area of the region sketched in this group is:\n" @@ -1915,7 +2046,7 @@ msgstr "" "Кривые аппроксимированы кусочно-линейными функциями.\n" "Это приводит к ошибке в расчетах, обычно в пределах 1%%." -#: solvespace.cpp:892 +#: solvespace.cpp:1000 #, c-format msgid "" "The total length of the selected entities is:\n" @@ -1932,38 +2063,38 @@ msgstr "" "Кривые аппроксимированы кусочно-линейными функциями.\n" "Это приводит к ошибке в расчетах, обычно в пределах 1%%." -#: solvespace.cpp:898 +#: solvespace.cpp:1006 msgid "Bad selection for perimeter; select line segments, arcs, and curves." msgstr "" "Неправильное выделение для расчета периметра; необходимо выбирать только " -"отрезки, дуги и кривые в качестве исходных данных" +"отрезки, дуги и кривые." -#: solvespace.cpp:914 +#: solvespace.cpp:1022 msgid "Bad selection for trace; select a single point." msgstr "Неправильное выделение для трассировки; необходимо выбрать одну точку." -#: solvespace.cpp:941 +#: solvespace.cpp:1049 #, c-format msgid "Couldn't write to '%s'" msgstr "Невозможно записать в '%s'" -#: solvespace.cpp:971 +#: solvespace.cpp:1079 msgid "The mesh is self-intersecting (NOT okay, invalid)." -msgstr "Полигональная стека содержит самопересечения (это плохо)" +msgstr "Полигональная сетка содержит самопересечения (это плохо)" -#: solvespace.cpp:972 +#: solvespace.cpp:1080 msgid "The mesh is not self-intersecting (okay, valid)." -msgstr "Полигональная стека не содержит самопересечений (это хорошо)" +msgstr "Полигональная сетка не содержит самопересечений (это хорошо)" -#: solvespace.cpp:974 +#: solvespace.cpp:1082 msgid "The mesh has naked edges (NOT okay, invalid)." msgstr "Полигональная сетка содержит \"оголенные\" ребра (это плохо)" -#: solvespace.cpp:975 +#: solvespace.cpp:1083 msgid "The mesh is watertight (okay, valid)." msgstr "Полигональная сетка герметична (это хорошо)" -#: solvespace.cpp:978 +#: solvespace.cpp:1086 #, c-format msgid "" "\n" @@ -1974,7 +2105,7 @@ msgstr "" "\n" "Модель содержит %d треугольников, содержащихся в %d поверхностях." -#: solvespace.cpp:982 +#: solvespace.cpp:1090 #, c-format msgid "" "%s\n" @@ -1989,7 +2120,7 @@ msgstr "" "\n" "Нет проблемных ребер - это хорошо.%s" -#: solvespace.cpp:985 +#: solvespace.cpp:1093 #, c-format msgid "" "%s\n" @@ -2004,7 +2135,7 @@ msgstr "" "\n" "%d найдены проблемные ребра - это плохо.%s" -#: solvespace.cpp:998 +#: solvespace.cpp:1106 #, c-format msgid "" "This is SolveSpace version %s.\n" @@ -2024,7 +2155,7 @@ msgstr "" "\n" "Для дополнительной информации посетите сайт: http://solvespace.com/\n" "\n" -"SolveSpace - это свободное программное обеспечение: поощряется изменение, " +"SolveSpace — свободное программное обеспечение: поощряется изменение, " "улучшение\n" "распространение программы по условиям лицензии GNU\n" "General Public License (GPL) версии 3 или новее.\n" @@ -2033,9 +2164,9 @@ msgstr "" "предусмотренных законом. Дополнительная информация на сайте: http://gnu.org/" "licenses/\n" "\n" -"© 2008-%d Джонатан Вэстью и другие авторы.\n" +"© 2008-%d Джонатан Уэстхьюс и другие авторы.\n" -#: style.cpp:166 +#: style.cpp:185 msgid "" "Can't assign style to an entity that's derived from another entity; try " "assigning a style to this entity's parent." @@ -2043,27 +2174,27 @@ msgstr "" "Невозможно применить стиль к примитиву, который произошел от другого " "примитива. Попробуйте применить стиль к исходному примитиву." -#: style.cpp:665 +#: style.cpp:735 msgid "Style name cannot be empty" -msgstr "Имя стиля не может быть пустым." +msgstr "Имя стиля не может быть пустым" -#: textscreens.cpp:741 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." msgstr "Невозможно сделать повторение меньше, чем 1 раз." -#: textscreens.cpp:745 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." msgstr "Невозможно сделать повтор больше, чем 999 раз." -#: textscreens.cpp:770 +#: textscreens.cpp:866 msgid "Group name cannot be empty" -msgstr "Имя группы не может быть пустым." +msgstr "Имя группы не может быть пустым" -#: textscreens.cpp:813 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." msgstr "Прозрачность должна быть числом от нуля до единицы." -#: textscreens.cpp:848 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "Радиус не может быть нулевым или отрицательным." @@ -2165,7 +2296,7 @@ msgstr "Создать группу выдавливания текущего э #: toolbar.cpp:70 msgid "New group rotating active sketch" -msgstr "Создать группу вращения текущего эскиза" +msgstr "Создать группу тела вращения текущего эскиза" #: toolbar.cpp:72 msgid "New group helix from active sketch" @@ -2173,7 +2304,7 @@ msgstr "Создать группу тела выдавливания по ви #: toolbar.cpp:74 msgid "New group revolve active sketch" -msgstr "Создать группу тела вращения из текущего эскиза" +msgstr "Создать группу тела вращения на угол из текущего эскиза" #: toolbar.cpp:76 msgid "New group step and repeat rotating" @@ -2201,7 +2332,7 @@ msgstr "Ближайший изометрический вид" #: toolbar.cpp:90 msgid "Align view to active workplane" -msgstr "Выровнять вид на рабочую плоскость" +msgstr "Выровнять вид по рабочей плоскости" #: util.cpp:165 msgctxt "title" @@ -2216,16 +2347,176 @@ msgstr "Сообщение" #: util.cpp:170 msgctxt "button" msgid "&OK" -msgstr "ХОРОШО" +msgstr "&OK" -#: view.cpp:78 +#: view.cpp:127 msgid "Scale cannot be zero or negative." msgstr "Масштабный коэффициент не может быть нулевым или отрицательным." -#: view.cpp:90 view.cpp:99 +#: view.cpp:139 view.cpp:148 msgid "Bad format: specify x, y, z" msgstr "Неверный формат: введите данные как x, y, z" +#~ msgid "" +#~ "Bad selection for on point / curve / plane constraint. This constraint " +#~ "can apply to:\n" +#~ "\n" +#~ " * two points (points coincident)\n" +#~ " * a point and a workplane (point in plane)\n" +#~ " * a point and a line segment (point on line)\n" +#~ " * a point and a circle or arc (point on curve)\n" +#~ " * a point and a plane face (point on face)\n" +#~ msgstr "" +#~ "Неправильное выделение для ограничения 'точка на примитиве'.\n" +#~ "Ограничение может принимать в качестве выделения следующие примитивы:\n" +#~ "\n" +#~ " * две точки (совпадение точек)\n" +#~ " * точку и рабочую плоскость (точка в плоскости)\n" +#~ " * точку и отрезок (точка на линии)\n" +#~ " * точку и окружность / дугу / сплайн (точка на кривой)\n" +#~ " * точку и грань (точка на грани)\n" + +#~ msgid "" +#~ "Bad selection for equal length / radius constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (equal length)\n" +#~ " * two line segments and two points (equal point-line distances)\n" +#~ " * a line segment and two points (equal point-line distances)\n" +#~ " * a line segment, and a point and line segment (point-line distance " +#~ "equals length)\n" +#~ " * four line segments or normals (equal angle between A,B and C,D)\n" +#~ " * three line segments or normals (equal angle between A,B and B,C)\n" +#~ " * two circles or arcs (equal radius)\n" +#~ " * a line segment and an arc (line segment length equals arc length)\n" +#~ msgstr "" +#~ "Неправильное выделение для ограничения 'равенство примитивов'.\n" +#~ "Ограничение может принимать в качестве выделения следующие примитивы:\n" +#~ "\n" +#~ " * два отрезка (равенство длин отрезков)\n" +#~ " * два отрезка и две точки (равенство расстояний от точек до линий)\n" +#~ " * отрезок и две точки (равенство расстояний от точек до линии)\n" +#~ " * отрезок, точку и отрезок (равенство длины отрезка расстоянию от " +#~ "точки до линии)\n" +#~ " * четыре отрезка или нормали (равенство углов A,B и C,D)\n" +#~ " * три отрезка или нормали (равенство углов A,B and B,C)\n" +#~ " * две окружности / дуги (равенство радиусов)\n" +#~ " * отрезок и дугу (равенство длины отрезка и длины дуги)\n" + +#~ msgid "" +#~ "Bad selection for horizontal / vertical constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two points\n" +#~ " * a line segment\n" +#~ msgstr "" +#~ "Неправильное выделение для ограничения 'горизонтальность / " +#~ "вертикальность'.\n" +#~ "Ограничение может принимать в качестве выделения следующие примитивы:\n" +#~ "\n" +#~ " * две точки\n" +#~ " * отрезок\n" + +#~ msgid "" +#~ "Bad selection for angle constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" +#~ msgstr "" +#~ "Неправильное выделение для ограничения углового размера.\n" +#~ "Ограничение может принимать в качестве выделения следующие примитивы:\n" +#~ "\n" +#~ " * два отрезка\n" +#~ " * отрезок и координатный базис (нормаль)\n" +#~ " * два координатных базиса (нормали)\n" + +#~ msgid "" +#~ "Bad selection for parallel / tangent constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (parallel)\n" +#~ " * a line segment and a normal (parallel)\n" +#~ " * two normals (parallel)\n" +#~ " * two line segments, arcs, or beziers, that share an endpoint " +#~ "(tangent)\n" +#~ msgstr "" +#~ "Неправильное выделение для ограничения параллельности / касательности.\n" +#~ "Ограничение может принимать в качестве выделения следующие примитивы:\n" +#~ "\n" +#~ " * два отрезка (параллельность)\n" +#~ " * отрезок и координатный базис (нормаль) (параллельность)\n" +#~ " * два координатных базиса (нормали) (параллельность)\n" +#~ " * два отрезка, две дуги или два сплайна, соединенных крайними точками " +#~ "(касательность)\n" + +#~ msgid "" +#~ "Bad selection for perpendicular constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" +#~ msgstr "" +#~ "Неправильное выделение для ограничения перпендикулярности.\n" +#~ "Ограничение может принимать в качестве выделения следующие примитивы:\n" +#~ "\n" +#~ " * два отрезка\n" +#~ " * отрезок и координатный базис (нормаль)\n" +#~ " * два координатных базиса (нормали)\n" + +#~ msgid "A&ngle" +#~ msgstr "&Угол" + +#~ msgid "E&qual Length / Radius / Angle" +#~ msgstr "&Равенство Длин / Радиусов / Углов" + +#~ msgid "" +#~ "Bad selection for length ratio constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ msgstr "" +#~ "Неправильное выделение для ограничения 'отношение длин'.\n" +#~ "Ограничение может принимать в качестве выделения следующие примитивы:\n" +#~ "\n" +#~ " * два отрезка\n" + +#~ msgid "" +#~ "Bad selection for length difference constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ msgstr "" +#~ "Неправильное выделение для ограничения 'разница длин'.\n" +#~ "Ограничение может принимать в качестве выделения следующие примитивы:\n" +#~ "\n" +#~ " * два отрезка\n" + +#~ msgid "Length Ra&tio" +#~ msgstr "Отно&шение Длин" + +#~ msgid "Length Diff&erence" +#~ msgstr "Ра&зница Длин" + +#~ msgid "" +#~ "Bad selection for new sketch in workplane. This group can be created " +#~ "with:\n" +#~ "\n" +#~ " * a point (through the point, orthogonal to coordinate axes)\n" +#~ " * a point and two line segments (through the point, parallel to the " +#~ "lines)\n" +#~ " * a workplane (copy of the workplane)\n" +#~ msgstr "" +#~ "Неправильное выделение для создания эскиза.\n" +#~ "Группа может быть создана, используя в качестве выделения следующие " +#~ "примитивы:\n" +#~ "\n" +#~ " * точку (рабочая плоскость, ориентированная к ближайшему виду, " +#~ "проходящая через точку)\n" +#~ " * точку и два отрезка (рабочая плоскость, проходящая через точку и " +#~ "параллельная отрезкам)\n" +#~ " * рабочую плоскость (копия рабочей плоскости)\n" + #~ msgid "Specify between 0 and 8 digits after the decimal." #~ msgstr "Введите число от 0 до 8." diff --git a/res/locales/tr_TR.po b/res/locales/tr_TR.po index 9e46b0dde..097e14afd 100644 --- a/res/locales/tr_TR.po +++ b/res/locales/tr_TR.po @@ -1,109 +1,110 @@ # Turkish translations for SolveSpace package. # Copyright (C) 2017 the SolveSpace authors # This file is distributed under the same license as the SolveSpace package. -# Automatically generated, 2017. +# SPDX-FileCopyrightText: 2022, 2025 Mustafa Halil GÖRENTAŞ # msgid "" msgstr "" -"Project-Id-Version: SolveSpace 3.0\n" -"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" -"POT-Creation-Date: 2021-02-01 15:45+0200\n" -"PO-Revision-Date: 2021-03-09 22:58+0300\n" -"Last-Translator: Mustafa Halil GÖRENTAŞ \n" -"Language-Team: app4soft\n" -"Language: tr\n" +"Project-Id-Version: SolveSpace 3.1\n" +"Report-Msgid-Bugs-To: phkahler@gmail.com\n" +"POT-Creation-Date: 2025-01-26 21:04+0200\n" +"PO-Revision-Date: 2025-01-29 21:16+0300\n" +"Last-Translator: mustafa halil \n" +"Language-Team: Turkish \n" +"Language: tr_TR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.4.2\n" -"X-Poedit-SourceCharset: UTF-8\n" +"X-Generator: Lokalize 23.08.5\n" -#: clipboard.cpp:310 +#: clipboard.cpp:314 +#, fuzzy msgid "" "Cut, paste, and copy work only in a workplane.\n" "\n" "Activate one with Sketch -> In Workplane." msgstr "" -"İşi yalnızca bir çalışma düzleminde kesin, yapıştır ve kopyalayın.\n" +"Çalışmayı yalnızca bir çalışma düzleminde kesin, yapıştırın ve kopyalayın.\n" "\n" "Çizim -> Çalışma Düzleminde menüsü ile bir düzlemi etkinleştirin." -#: clipboard.cpp:327 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." msgstr "Pano boş; yapıştırılacak bir şey yok." -#: clipboard.cpp:374 +#: clipboard.cpp:378 msgid "Number of copies to paste must be at least one." msgstr "Yapıştırılacak kopya sayısı en az bir olmalıdır." -#: clipboard.cpp:390 textscreens.cpp:783 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "Ölçek sıfır olamaz." -#: clipboard.cpp:432 +#: clipboard.cpp:436 msgid "Select one point to define origin of rotation." -msgstr "Dönüşün başlangıç noktasını tanımlamak için bir nokta seçin." +msgstr "Çevirme merkezini tanımlamak için bir nokta seçin." -#: clipboard.cpp:444 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." -msgstr "Öteleme vektörünü tanımlamak için iki nokta seçin." +msgstr "Doğrusal kopyalama vektörünü tanımlamak için iki nokta seçin." -#: clipboard.cpp:454 +#: clipboard.cpp:458 msgid "" "Transformation is identity. So all copies will be exactly on top of each " "other." msgstr "Dönüşüm özdeştir. Yani tüm kopyalar tam olarak üst üste gelecek." -#: clipboard.cpp:458 +#: clipboard.cpp:462 msgid "Too many items to paste; split this into smaller pastes." -msgstr "Yapıştırılamayacak kadar çok öğe; bunu daha küçük yapıştımalara bölün." +msgstr "" +"Yapıştırılamayacak kadar çok öğe; bunu daha küçük yapıştıma şeklinde bölün." -#: clipboard.cpp:463 +#: clipboard.cpp:467 msgid "No workplane active." -msgstr "Etkin Çalışma Düzlemi yok." +msgstr "Etkin çalışma düzlemi yok." -#: confscreen.cpp:418 +#: confscreen.cpp:410 msgid "Bad format: specify coordinates as x, y, z" msgstr "Hatalı biçim: koordinatları x, y, z olarak belirtin" -#: confscreen.cpp:428 style.cpp:659 textscreens.cpp:805 +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 msgid "Bad format: specify color as r, g, b" msgstr "Hatalı biçim: rengi r, g, b olarak belirtin" -#: confscreen.cpp:454 +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." msgstr "" "Görünüm -> Perspektif Projeksiyonu Kullan'ı etkinleştirene kadar perspektif " -"çarpanının hiçbir etkisi olmayacaktır." +"katsayısının hiçbir etkisi olmayacaktır." -#: confscreen.cpp:467 confscreen.cpp:477 +#: confscreen.cpp:464 confscreen.cpp:474 #, c-format msgid "Specify between 0 and %d digits after the decimal." -msgstr "Ondalık basamak sonra 0 ile %d arasında basamak belirtin." +msgstr "Ondalık basamak sonrası 0 ile %d arasında rakam belirtin." -#: confscreen.cpp:489 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" msgstr "Dışa aktarma ölçeği sıfır olmamalıdır!" -#: confscreen.cpp:501 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" msgstr "Kesici yarıçap ofseti negatif olmamalıdır!" -#: confscreen.cpp:555 +#: confscreen.cpp:557 msgid "Bad value: autosave interval should be positive" msgstr "Hatalı değer: otomatik kaydetme süresi pozitif olmalıdır" -#: confscreen.cpp:558 +#: confscreen.cpp:560 msgid "Bad format: specify interval in integral minutes" msgstr "Hatalı biçim: süre aralığını dakika cinsinden belirtin" #: constraint.cpp:12 msgctxt "constr-name" msgid "pts-coincident" -msgstr "nktlar-kesişen" +msgstr "çakışık-nktlar" #: constraint.cpp:13 msgctxt "constr-name" @@ -138,7 +139,7 @@ msgstr "düzlemde-nkt" #: constraint.cpp:19 msgctxt "constr-name" msgid "pt-on-line" -msgstr "nkt-çizgide" +msgstr "çizgide-nkt" #: constraint.cpp:20 msgctxt "constr-name" @@ -148,17 +149,17 @@ msgstr "yüzeyde-nkt" #: constraint.cpp:21 msgctxt "constr-name" msgid "eq-length" -msgstr "eş-uzunluk" +msgstr "eşit-uzunluk" #: constraint.cpp:22 msgctxt "constr-name" msgid "eq-length-and-pt-ln-dist" -msgstr "eş-uzunluk-ve-çzg-nkt-mesafesi" +msgstr "eşit-uzunluk-ve-çzg-nkt-mesafesi" #: constraint.cpp:23 msgctxt "constr-name" msgid "eq-pt-line-distances" -msgstr "eş-nkt-çizgi-mesafesi" +msgstr "eşit-nkt-çizgi-mesafesi" #: constraint.cpp:24 msgctxt "constr-name" @@ -167,139 +168,218 @@ msgstr "uzunluk-oranı" #: constraint.cpp:25 msgctxt "constr-name" +msgid "arc-arc-length-ratio" +msgstr "yay-yay-uzunluk-oranı" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "arc-line-length-ratio" +msgstr "yay-çizgi-uzunluk-oranı" + +#: constraint.cpp:27 +msgctxt "constr-name" msgid "length-difference" msgstr "uzunluk-farkı" -#: constraint.cpp:26 +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "arc-arc-len-difference" +msgstr "yay-yay-uzunluk-farkı" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "arc-line-len-difference" +msgstr "yay-çizgi-uzunluk-farkı" + +#: constraint.cpp:30 msgctxt "constr-name" msgid "symmetric" msgstr "simetrik" -#: constraint.cpp:27 +#: constraint.cpp:31 msgctxt "constr-name" msgid "symmetric-h" msgstr "simetrik-y" -#: constraint.cpp:28 +#: constraint.cpp:32 msgctxt "constr-name" msgid "symmetric-v" msgstr "simetrik-d" -#: constraint.cpp:29 +#: constraint.cpp:33 msgctxt "constr-name" msgid "symmetric-line" msgstr "simetrik-çizgi" -#: constraint.cpp:30 +#: constraint.cpp:34 msgctxt "constr-name" msgid "at-midpoint" msgstr "orta noktada" -#: constraint.cpp:31 +#: constraint.cpp:35 msgctxt "constr-name" msgid "horizontal" msgstr "yatay" -#: constraint.cpp:32 +#: constraint.cpp:36 msgctxt "constr-name" msgid "vertical" msgstr "dikey" -#: constraint.cpp:33 +#: constraint.cpp:37 msgctxt "constr-name" msgid "diameter" msgstr "çap" -#: constraint.cpp:34 +#: constraint.cpp:38 msgctxt "constr-name" msgid "pt-on-circle" -msgstr "nkt-çemberde" +msgstr "çemberde-nkt" -#: constraint.cpp:35 +#: constraint.cpp:39 msgctxt "constr-name" msgid "same-orientation" msgstr "aynı-yön" -#: constraint.cpp:36 +#: constraint.cpp:40 msgctxt "constr-name" msgid "angle" msgstr "açı" -#: constraint.cpp:37 +#: constraint.cpp:41 msgctxt "constr-name" msgid "parallel" msgstr "paralel" -#: constraint.cpp:38 +#: constraint.cpp:42 msgctxt "constr-name" msgid "arc-line-tangent" msgstr "yay-çizgi-teğet" -#: constraint.cpp:39 +#: constraint.cpp:43 msgctxt "constr-name" msgid "cubic-line-tangent" -msgstr "kubik-çizgi-teğet" +msgstr "k.eğri-çizgi-teğet" -#: constraint.cpp:40 +#: constraint.cpp:44 msgctxt "constr-name" msgid "curve-curve-tangent" msgstr "eğri-eğri-teğet" -#: constraint.cpp:41 +#: constraint.cpp:45 msgctxt "constr-name" msgid "perpendicular" msgstr "dik" -#: constraint.cpp:42 +#: constraint.cpp:46 msgctxt "constr-name" msgid "eq-radius" -msgstr "eş-yarıçap" +msgstr "eşit-yarıçap" -#: constraint.cpp:43 +#: constraint.cpp:47 msgctxt "constr-name" msgid "eq-angle" -msgstr "eş-açı" +msgstr "eşit-açı" -#: constraint.cpp:44 +#: constraint.cpp:48 msgctxt "constr-name" msgid "eq-line-len-arc-len" -msgstr "eş-çizgi-uzn-yay-uzn" +msgstr "eşit-çizgi-uzn-yay-uzn" -#: constraint.cpp:45 +#: constraint.cpp:49 msgctxt "constr-name" msgid "lock-where-dragged" msgstr "sürüklendiği-yerde-kilitli" -#: constraint.cpp:46 +#: constraint.cpp:50 msgctxt "constr-name" msgid "comment" msgstr "yorum" -#: constraint.cpp:140 +#: constraint.cpp:151 msgid "" -"The tangent arc and line segment must share an endpoint. Constrain them with " -"Constrain -> On Point before constraining tangent." +"The point you selected does not belong to the arc. The arc and line segment " +"do not share an end point.\n" +"\n" +"Select the end point of the arc at which you want it to be tangent to the " +"line." msgstr "" -"Teğet, yay ve çizgi parçası bir uç noktayı paylaşmalıdır. Teğeti " -"sınırlandırmadan önce bunları Sınırlandır -> Noktada ile sınırlandırın." +"Seçtiğiniz nokta yaya ait değildir. Yay ve doğru parçası bir bitiş " +"noktasını paylaşmaz.\n" +"\n" +"Çizgiye teğet olmasını istediğiniz yayın bitiş noktasını seçin." #: constraint.cpp:158 msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them " -"with Constrain -> On Point before constraining tangent." +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be " +"tangent to the line." msgstr "" -"Teğet kübik ve çizgi parçası bir uç noktayı paylaşmalıdır. Teğeti " -"sınırlandırmadan önce onları Sınırlandır -> Noktada ile sınırlandırın." +"Teğet için yay ve çizgi parçası bir uç noktayı paylaşmalıdır. Teğet " +"kısıtlamasından önce bunları Kısıtla -> Noktada ile kısıtlayın.\n" +"\n" +"Alternatif olarak, çizgiye teğet olmasını istediğiniz yayın uç " +"noktasını seçin." -#: constraint.cpp:183 +#: constraint.cpp:186 +msgid "" +"The point you selected is not an end point of the cubic spline. The spline " +"and line segment do not share an end point.\n" +"\n" +"Select the end point of the spline at which you want it to be tangent to the " +"line." +msgstr "" +"Seçtiğiniz nokta kübik eğrinin bir uç noktası değildir. Eğri ve doğru " +"parçası bir bitiş noktasını paylaşmaz.\n" +"\n" +"Çizgiye teğet olmasını istediğiniz eğrinin uç noktasını seçin." + +#: constraint.cpp:193 +msgid "" +"The tangent cubic spline and line segment must share an endpoint. Constrain " +"them with Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it " +"to be tangent to the line." +msgstr "" +"Teğet için kübik eğri ve çizgi parçası bir uç noktayı paylaşmalıdır. Teğet " +"kısıtlamasından önce onları Kısıtla -> Noktada ile kısıtlayın.\n" +"\n" +"Alternatif olarak, kübik eğrinin çizgiye teğet olmasını istediğiniz uç " +"noktasını seçin." + +#: constraint.cpp:237 +msgid "" +"The points you selected are not end points of the two curves. The curves do " +"not share an end point.\n" +"\n" +"Select the end points of both curves at which you want them to be tangent to " +"each other." +msgstr "" +"Seçtiğiniz noktalar iki eğrinin uç noktaları değildir. Eğriler bir uç" +" noktasını " +"paylaşmazlar.\n" +"\n" +"Birbirlerine teğet olmalarını istediğiniz her iki eğrinin uç noktalarını" +" seçin." + +#: constraint.cpp:244 msgid "" "The curves must share an endpoint. Constrain them with Constrain -> On Point " -"before constraining tangent." +"before constraining tangent.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." msgstr "" -"Eğriler bir uç noktayı paylaşmalıdır. Teğeti sınırlandırmadan önce onları " -"Sınırlandır -> Noktada ile sınırlandırın." +"Eğriler bir uç noktayı paylaşmalıdır. Teğet kısıtlamasından önce onları " +"Kısıtla -> Noktada ile kısıtlayın.\n" +"\n" +"Alternatif olarak, teğet olmasını istediğiniz eğrilerin iki uç noktalarını " +"seçin." -#: constraint.cpp:231 +#: constraint.cpp:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply " "to:\n" @@ -312,104 +392,108 @@ msgid "" " * a plane face and a point (minimum distance)\n" " * a circle or an arc (diameter)\n" msgstr "" -"Mesafe / çap sınırlandırması için hatalı seçim. Bu sınırlandırma şunlara " +"Mesafe / çap kısıtlaması oluşturmak için hatalı seçim. Bu kısıtlama şunlara " "uygulanabilir:\n" "\n" -" * iki nokta (noktalar arasındaki mesafe)\n" -" * bir çizgi parçası (uzunluk)\n" -" * iki nokta ve bir çizgi parçası veya normal (öngörülen mesafe)\n" -" * bir çalışma düzlemi ve bir nokta (minimum mesafe)\n" -" * bir çizgi parçası ve bir nokta (minimum mesafe)\n" -" * bir düzlem yüzeyi ve bir nokta (minimum mesafe)\n" -" * bir daire veya yay (çap)\n" +" * iki nokta (noktalar arası mesafe)\n" +" * bir çizgi parçası (uzunluk)\n" +" * iki nokta ve bir çizgi parçası veya normal (izdüşüm mesafesi)\n" +" * bir çalışma düzlemi ve bir nokta (minimum mesafe)\n" +" * bir çizgi parçası ve bir nokta (minimum mesafe)\n" +" * bir düzlem yüzeyi ve bir nokta (minimum mesafe)\n" +" * bir daire veya yay (çap)\n" -#: constraint.cpp:284 +#: constraint.cpp:366 msgid "" "Bad selection for on point / curve / plane constraint. This constraint can " "apply to:\n" "\n" -" * two points (points coincident)\n" +" * two or more points (points coincident)\n" " * a point and a workplane (point in plane)\n" " * a point and a line segment (point on line)\n" " * a point and a circle or arc (point on curve)\n" -" * a point and a plane face (point on face)\n" +" * a point and one to three plane faces (point on face(s))\n" msgstr "" -"Nokta / eğri / düzlem sınırlandırması için hatalı seçim. Bu sınırlandırma " -"şunlara uygulanabilir:\n" +"Noktada / Eğride / Düzlemde kısıtlaması oluşturmak için hatalı seçim. Bu " +"kısıtlama şunlara uygulanabilir:\n" "\n" -" * iki nokta (çakışan noktalar)\n" -" * bir nokta ve bir çalışma düzlemi (düzlemdeki nokta)\n" -" * bir nokta ve bir çizgi parçası (çizgi üzerinde nokta)\n" -" * bir nokta ve bir daire veya yay (eğri üzerinde nokta)\n" -" * bir nokta ve bir düzlem yüzeyi (yüzeyin üzerine gelin)\n" +" * iki nokta (çakışan noktalar)\n" +" * bir nokta ve bir çalışma düzlemi (nokta ile düzlem çakışır)\n" +" * bir nokta ve bir çizgi parçası (nokta ile çizgi çakışır)\n" +" * bir nokta ve bir daire veya yay (nokta ile eğri çakışır)\n" +" * bir nokta ve bir düzlem yüzeyi (nokta ile yüzey çakışır)\n" -#: constraint.cpp:346 +#: constraint.cpp:427 msgid "" "Bad selection for equal length / radius constraint. This constraint can " "apply to:\n" "\n" -" * two line segments (equal length)\n" +" * two or more line segments (equal length)\n" " * two line segments and two points (equal point-line distances)\n" " * a line segment and two points (equal point-line distances)\n" " * a line segment, and a point and line segment (point-line distance " "equals length)\n" -" * four line segments or normals (equal angle between A,B and C,D)\n" -" * three line segments or normals (equal angle between A,B and B,C)\n" -" * two circles or arcs (equal radius)\n" +" * two or more circles or arcs (equal radius)\n" " * a line segment and an arc (line segment length equals arc length)\n" msgstr "" -"Eşit uzunluk / yarıçap sınırlandırması için hatalı seçim. Bu sınırlandırma " -"şunlara uygulanabilir:\n" +"Eşit uzunluk / yarıçap kısıtlaması oluşturmak için hatalı seçim. Bu " +"kısıtlama şunlara uygulanabilir:\n" "\n" -" * iki çizgi parçası (eşit uzunluk)\n" -" * iki çizgi parçası ve iki nokta (eşit nokta-çizgi mesafeleri)\n" -" * bir çizgi parçası ve iki nokta (eşit nokta-çizgi mesafeleri)\n" -" * bir çizgi parçası ve bir nokta ve çizgi parçası (nokta-çizgi mesafesi " -"uzunluğa eşittir)\n" -" * dört çizgi parçası veya normal (A, B ve C, D arasında eşit açı)\n" -" * üç çizgi parçası veya normal (A, B ve B, C arasında eşit açı)\n" -" * iki daire veya yay (eşit yarıçap)\n" -" * bir çizgi parçası ve bir yay (çizgi parçası uzunluğu yay uzunluğuna " +" * iki veya daha fazla çizgi parçası (eşit uzunlukta)\n" +" * iki çizgi parçası ve iki nokta (eşit nokta-çizgi mesafeleri)\n" +" * bir çizgi parçası ve iki nokta (eşit nokta-çizgi mesafeleri)\n" +" * bir çizgi parçası ile bir nokta ve çizgi parçası (nokta-çizgi mesafesi " +"uzunluğu eşit)\n" +" * iki veya daha fazla daire veya yay (eşit yarıçap)\n" +" * bir çizgi parçası ve bir yay (çizgi parçası uzunluğu yay uzunluğuna " "eşittir)\n" -#: constraint.cpp:385 +#: constraint.cpp:480 msgid "" "Bad selection for length ratio constraint. This constraint can apply to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" -"Uzunluk oranı sınırlandırması için hatalı seçim. Bu sınırlandırma şunlara " -"uygulanabilir:\n" +"Uzunluk oranı kısıtlaması oluşturmak için hatalı seçim. Bu kısıtlama " +"aşağıdakiler için geçerli olabilir:\n" "\n" -" * iki çizgi parçası\n" +" * iki çizgi parçası\n" +" * iki yay\n" +" * bir yay ve bir çizgi parçası\n" -#: constraint.cpp:402 +#: constraint.cpp:515 msgid "" "Bad selection for length difference constraint. This constraint can apply " "to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" -"Uzunluk farkı sınırlandırması için hatalı seçim. Bu sınırlandırma şunlara " -"uygulanabilir:\n" +"Uzunluk farkı kısıtlaması oluşturmak için hatalı seçim. Bu kısıtlama " +"aşağıdakiler için geçerli olabilir:\n" "\n" -" * iki çizgi parçası\n" +" * iki çizgi parçası\n" +" * iki yay\n" +" * bir yay ve bir çizgi parçası\n" -#: constraint.cpp:428 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" " * a line segment and a point (point at midpoint)\n" " * a line segment and a workplane (line's midpoint on plane)\n" msgstr "" -"Orta nokta sınırlandırması için hatalı seçim. Bu sınırlandırma şunlara " +"Orta nokta kısıtlaması oluşturmak için hatalı seçim. Bu kısıtlama şunlara " "uygulanabilir:\n" "\n" -" * bir çizgi parçası ve bir nokta (orta noktayı işaret edin)\n" -" * bir çizgi parçası ve bir çalışma düzlemi (düzlemdeki çizginin orta " -"noktası)\n" +" * bir çizgi parçası ve bir nokta (nokta çizgi ortasında)\n" +" * bir çizgi parçası ve bir çalışma düzlemi (çizgi düzlemin ortasına " +"taşınır )\n" -#: constraint.cpp:486 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -420,132 +504,157 @@ msgid "" " * workplane, and two points or a line segment (symmetric about " "workplane)\n" msgstr "" -"Simetrik sınırlandırması için hatalı seçim. Bu sınırlandırma şunlara " +"Simetri kısıtlaması oluşturmak için hatalı seçim. Bu kısıtlama şunlara " "uygulanabilir:\n" "\n" -" * iki nokta veya bir çizgi parçası (çalışma düzleminin koordinat ekseni " -"etrafında simetrik)\n" -" * çizgi parçası ve iki nokta veya bir çizgi parçası (çizgi parçası " -"etrafında simetrik)\n" -" * çalışma düzlemi ve iki nokta veya bir çizgi parçası (çalışma düzlemi " +" * iki nokta veya bir çizgi parçası (çalışma düzleminin koordinat " +"eksenine göre simetrik)\n" +" * çizgi parçası ve iki nokta veya bir çizgi parçası (çizgi parçası " "etrafında simetrik)\n" +" * çalışma düzlemi ve iki nokta veya bir çizgi parçası (çalışma düzlemine " +"göre simetrik)\n" -#: constraint.cpp:500 +#: constraint.cpp:623 msgid "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." msgstr "" -"Açık bir simetri düzlemi olmadan simetriyi sınırlandırırken bir çalışma " +"Simetri kısıtlamasında simetri düzlemini olarak kullanılacak bir çalışma " "düzlemi etkin olmalıdır." -#: constraint.cpp:530 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." msgstr "" -"Yatay veya dikey bir sınırlandırma uygulamadan önce bir çalışma düzlemini " -"(Çizim -> Çalışma Düzleminde menüsü) etkinleştirin." +"Yatay veya dikey bir kısıtlama uygulamadan önce bir çalışma düzlemini (Çizim " +"-> Çalışma Düzleminde menüsü ile) etkinleştirin." -#: constraint.cpp:543 +#: constraint.cpp:679 msgid "" "Bad selection for horizontal / vertical constraint. This constraint can " "apply to:\n" "\n" -" * two points\n" -" * a line segment\n" +" * two or more points\n" +" * one or more line segments\n" msgstr "" -"Yatay / dikey sınırlandırma için hatalı seçim. Bu sınırlandırma şunlara " +"Yatay / Dikey kısıtlama oluşturmak için hatalı seçim. Bu kısıtlama şunlara " "uygulanabilir:\n" "\n" -" * iki nokta\n" -" * bir çizgi parçası\n" +" * iki nokta\n" +" * bir çizgi parçası\n" -#: constraint.cpp:564 +#: constraint.cpp:697 msgid "" "Bad selection for same orientation constraint. This constraint can apply " "to:\n" "\n" " * two normals\n" msgstr "" -"Aynı yön sınırlandırması için hatalı seçim. Bu sınırlandırma şunlara " +"Aynı yön kısıtlaması oluşturmak için hatalı seçim. Bu kısıtlama şunlara " "uygulanabilir:\n" "\n" -" * iki normal\n" +" * iki normal\n" -#: constraint.cpp:614 +#: constraint.cpp:748 msgid "Must select an angle constraint." -msgstr "Bir açı sınırlaması seçilmelidir." +msgstr "Bir açı kısıtlaması seçilmelidir." -#: constraint.cpp:627 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." -msgstr "İlişkili etikete sahip bir sınırlama seçilmelidir." +msgstr "İlgili etikete sahip bir kısıtlama seçilmelidir." -#: constraint.cpp:638 +#: constraint.cpp:784 msgid "" "Bad selection for angle constraint. This constraint can apply to:\n" "\n" +"Angle between:\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" +"\n" +"Equal angles:\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" msgstr "" -"Açı sınırlaması için hatalı seçim. Bu sınırlandırma şunlara uygulanabilir:\n" +"Açı kısıtlaması oluşturmak için hatalı seçim. Bu kısıtlama şunlara " +"uygulanabilir:\n" "\n" -" * iki çizgi parçası\n" -" * bir çizgi parçası ve normal\n" -" * iki normal\n" +"Şunların arasındaki açı:\n" +" * iki çizgi parçası\n" +" * bir çizgi parçası ve bir normal\n" +" * iki normal\n" +"\n" +"Eşit açılar:\n" +" * dört çizgi parçası veya normal (A,B ve C,D arasında eşit açı)\n" +" * üç çizgi parçası veya normal (A,B ve B,C arasında eşit açı)\n" -#: constraint.cpp:701 +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." msgstr "Eğri-eğri teğetliği çalışma düzlemine uygulanmalıdır." -#: constraint.cpp:711 +#: constraint.cpp:887 msgid "" "Bad selection for parallel / tangent constraint. This constraint can apply " "to:\n" "\n" -" * two line segments (parallel)\n" -" * a line segment and a normal (parallel)\n" -" * two normals (parallel)\n" +" * two faces\n" +" * two or more line segments (parallel)\n" +" * one or more line segments and one or more normals (parallel)\n" +" * two or more normals (parallel)\n" " * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +" * two line segments, arcs, or beziers, that do not share an endpoint and " +"the end point(s) of the curve(s) (tangent)\n" msgstr "" -"Paralel / teğet sınırlaması için hatalı seçim. Bu sınırlandırma şunlara " -"uygulanabilir:\n" +"Paralel / Teğet kısıtlaması oluşturmak için hatalı seçim. Bu kısıtlama " +"şunlara uygulanabilir:\n" "\n" -" * iki çizgi parçası (paralel)\n" -" * bir çizgi parçası ve normal (paralel)\n" -" * iki normal (paralel)\n" -" * bir uç noktayı paylaşan(teğet) iki çizgi parçası, yay veya " -"bezier'ler\n" +" * iki yüzey\n" +" * iki veya daha fazla doğru parçası (paralel)\n" +" * bir veya daha fazla doğru parçası ve bir veya daha fazla normal " +"(paralel)\n" +" * iki veya daha fazla normal (paralel)\n" +" * bir uç noktayı (teğet) paylaşan iki doğru parçası, yay veya bezier " +"eğriler\n" +" *bir uç noktayı paylaşmayan iki doğru parçası, yay veya bezier eğriler ve " +"eğri(ler)in son noktası(ları) (teğet)\n" -#: constraint.cpp:729 +#: constraint.cpp:914 msgid "" "Bad selection for perpendicular constraint. This constraint can apply to:\n" "\n" +" * two faces\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" msgstr "" -"Dikey sınırlama için hatalı seçim. Bu sınırlandırma şunlara uygulanabilir:\n" +"Dikey kısıtlama oluşturmak için hatalı seçim. Bu kısıtlama şunlara " +"uygulanabilir:\n" "\n" +" * iki yüzey\n" " * iki çizgi parçası\n" -" * bir çizgi parçası ve normal\n" +" * bir çizgi parçası ve bir normal\n" " * iki normal\n" -#: constraint.cpp:744 +#: constraint.cpp:931 msgid "" "Bad selection for lock point where dragged constraint. This constraint can " "apply to:\n" "\n" " * a point\n" msgstr "" -"Sürüklendiği yerde noktayı sınırlandırmak için hatalı seçim. Bu " -"sınırlandırma şunlara uygulanabilir:\n" +"Sürüklendiği yerde noktayı kısıtlamak için hatalı seçim. Bu kısıtlama " +"şunlara uygulanabilir:\n" "\n" -"* bir nokta\n" +" * bir nokta\n" + +#: constraint.cpp:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "YENI YORUM -- DÜZENLEMEK ICIN CIFT-TIKLAYIN" -#: constraint.cpp:755 +#: constraint.cpp:952 msgid "click center of comment text" -msgstr "yorum metninin merkezine tıklayın" +msgstr "yorum metninin ortasını tıklatın" #: export.cpp:19 msgid "" @@ -570,27 +679,27 @@ msgstr "" " * aktif bir çalışma düzleminde iken hiçbir şey seçmeyin (çalışma " "düzlemi, kesit düzlemidir)\n" " * bir yüzey (yüzeyden kesit düzlemi)\n" -" * bir nokta ve iki çizgi parçası (nokta boyunca düzlem ve çizgilere " -"paralel)\n" +" * bir nokta ve iki çizgi parçası (nokta ve paralel çizgiler boyunca " +"düzlem)\n" -#: export.cpp:822 +#: export.cpp:818 msgid "Active group mesh is empty; nothing to export." -msgstr "Etkin Mesh grubu boş; dışa aktarılacak bir şey yok." +msgstr "Etkin grup ağı boş; dışa aktarılacak bir şey yok." -#: exportvector.cpp:337 +#: exportvector.cpp:336 msgid "freehand lines were replaced with continuous lines" msgstr "serbest çizgiler, sürekli çizgilerle değiştirildi" -#: exportvector.cpp:339 +#: exportvector.cpp:338 msgid "zigzag lines were replaced with continuous lines" msgstr "zikzak çizgiler sürekli çizgilerle değiştirildi" -#: exportvector.cpp:593 +#: exportvector.cpp:592 msgid "" "Some aspects of the drawing have no DXF equivalent and were not exported:\n" msgstr "Çizimin bazı yönlerinin DXF eşdeğeri yoktur ve dışa aktarılmamıştır:\n" -#: exportvector.cpp:839 +#: exportvector.cpp:838 msgid "" "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." msgstr "" @@ -607,11 +716,11 @@ msgctxt "group-name" msgid "#references" msgstr "#referanslar" -#: file.cpp:552 +#: file.cpp:555 msgid "The file is empty. It may be corrupt." msgstr "Dosya boş. Bozuk olabilir." -#: file.cpp:557 +#: file.cpp:560 msgid "" "Unrecognized data in file. This file may be corrupt, or from a newer version " "of the program." @@ -619,18 +728,18 @@ msgstr "" "Dosyada veriler tanınmadı. Bu dosya bozuk veya programın daha yeni bir " "sürümü ile oluşturulmuş olabilir." -#: file.cpp:867 +#: file.cpp:876 msgctxt "title" msgid "Missing File" -msgstr "Eksik Dosya" +msgstr "Kayıp Dosya" -#: file.cpp:868 +#: file.cpp:877 #, c-format msgctxt "dialog" msgid "The linked file “%s” is not present." msgstr "\"%s\" bağlantılı dosya yok." -#: file.cpp:870 +#: file.cpp:879 msgctxt "dialog" msgid "" "Do you want to locate it manually?\n" @@ -638,22 +747,22 @@ msgid "" "If you decline, any geometry that depends on the missing file will be " "permanently removed." msgstr "" -"Yerini manuel olarak mı bulmak istiyorsunuz?\n" +"Konumu El ile bulmak istiyor musunuz?\n" "\n" "Reddederseniz, eksik dosyaya bağlı olan geometri kalıcı olarak " "kaldırılacaktır." -#: file.cpp:873 +#: file.cpp:882 msgctxt "button" msgid "&Yes" msgstr "&Evet" -#: file.cpp:875 +#: file.cpp:884 msgctxt "button" msgid "&No" msgstr "&Hayır" -#: file.cpp:877 solvespace.cpp:569 +#: file.cpp:886 solvespace.cpp:652 msgctxt "button" msgid "&Cancel" msgstr "&İptal" @@ -700,7 +809,7 @@ msgstr "3d &TelKafes olarak dışa aktar..." #: graphicswin.cpp:52 msgid "Export Triangle &Mesh..." -msgstr "&Üçgensel Mesh olarak dışa aktar..." +msgstr "&Üçgensel Ağ olarak dışa aktar..." #: graphicswin.cpp:53 msgid "Export &Surfaces..." @@ -760,7 +869,7 @@ msgstr "&Sil" #: graphicswin.cpp:74 msgid "Select &Edge Chain" -msgstr "Kenar &Zincirini Seçin" +msgstr "Kenar &Zincirini Seç" #: graphicswin.cpp:75 msgid "Select &All" @@ -804,7 +913,7 @@ msgstr "Görünümü &Çalışma Düzlemine Hizala" #: graphicswin.cpp:90 msgid "Nearest &Ortho View" -msgstr "En Yakın &Orto Görünüm" +msgstr "En Yakın &Dik Görünüm" #: graphicswin.cpp:91 msgid "Nearest &Isometric View" @@ -816,7 +925,7 @@ msgstr "&Noktayı Merkezde Görüntüle" #: graphicswin.cpp:94 msgid "Show Snap &Grid" -msgstr "&Izgarayı Göster" +msgstr "Yakalama &Izgarasını Göster" #: graphicswin.cpp:95 msgid "Darken Inactive Solids" @@ -824,298 +933,310 @@ msgstr "Aktif Olmayan Katıları &Koyulaştır" #: graphicswin.cpp:96 msgid "Use &Perspective Projection" -msgstr "&Perspektif Projeksiyonu Kullanın" +msgstr "&Perspektif Projeksiyonu Kullan" #: graphicswin.cpp:97 +msgid "Show E&xploded View" +msgstr "Pat&latılmış Görünümü Göster" + +#: graphicswin.cpp:98 msgid "Dimension &Units" msgstr "Ölçü &Birimleri" -#: graphicswin.cpp:98 +#: graphicswin.cpp:99 msgid "Dimensions in &Millimeters" msgstr "&Milimetre cinsinden ölçü" -#: graphicswin.cpp:99 +#: graphicswin.cpp:100 msgid "Dimensions in M&eters" msgstr "M&etre cinsinden ölçü" -#: graphicswin.cpp:100 +#: graphicswin.cpp:101 msgid "Dimensions in &Inches" msgstr "&İnç cinsinden ölçü" #: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "&Fit ve İnç cinsinden ölçü" + +#: graphicswin.cpp:104 msgid "Show &Toolbar" msgstr "&Araç Çubuğunu Göster" -#: graphicswin.cpp:103 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" -msgstr "&Özellik Tarayıcısını Göster" +msgstr "&Özellik Penceresini Göster" -#: graphicswin.cpp:105 +#: graphicswin.cpp:107 msgid "&Full Screen" msgstr "&Tam Ekran" -#: graphicswin.cpp:107 +#: graphicswin.cpp:109 msgid "&New Group" msgstr "Yeni &Grup" -#: graphicswin.cpp:108 +#: graphicswin.cpp:110 msgid "Sketch In &3d" msgstr "&3d'de Çizim Yap" -#: graphicswin.cpp:109 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" msgstr "&Yeni Çalışma Düzleminde Çizim Yap" -#: graphicswin.cpp:111 +#: graphicswin.cpp:113 msgid "Step &Translating" -msgstr "Adım &Ötele" +msgstr "&Doğrusal Kopya" -#: graphicswin.cpp:112 +#: graphicswin.cpp:114 msgid "Step &Rotating" -msgstr "Adım &Döndür" +msgstr "D&airesel Kopya" -#: graphicswin.cpp:114 +#: graphicswin.cpp:116 msgid "E&xtrude" -msgstr "&Katıla" +msgstr "&Uzatarak Katıla" -#: graphicswin.cpp:115 +#: graphicswin.cpp:117 msgid "&Helix" msgstr "&Helis" -#: graphicswin.cpp:116 +#: graphicswin.cpp:118 msgid "&Lathe" -msgstr "&Çark" +msgstr "&Tam döndürerek katıla" -#: graphicswin.cpp:117 +#: graphicswin.cpp:119 msgid "Re&volve" -msgstr "Dö&ndür" +msgstr "&Kısmen döndürerek katıla" -#: graphicswin.cpp:119 +#: graphicswin.cpp:121 msgid "Link / Assemble..." msgstr "Bağla / Montajla..." -#: graphicswin.cpp:120 +#: graphicswin.cpp:122 msgid "Link Recent" msgstr "Son Erişilenden Bağla" -#: graphicswin.cpp:122 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "&Çizim" -#: graphicswin.cpp:123 +#: graphicswin.cpp:125 msgid "In &Workplane" msgstr "Ç&alışma Düzleminde" -#: graphicswin.cpp:124 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" msgstr "&3d'de Herhangi Bir Yerde" -#: graphicswin.cpp:126 +#: graphicswin.cpp:128 msgid "Datum &Point" msgstr "Referasn &Noktası" -#: graphicswin.cpp:127 -msgid "&Workplane" +#: graphicswin.cpp:129 +msgid "Wor&kplane" msgstr "Ça&lışma Düzlemi" -#: graphicswin.cpp:129 +#: graphicswin.cpp:131 msgid "Line &Segment" -msgstr "Çizgi &Parçası" +msgstr "Ç&izgi" -#: graphicswin.cpp:130 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" -msgstr "&Yapı Çizgisi Parçası" +msgstr "&Yardımcı Çizgi" -#: graphicswin.cpp:131 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "&Dikdörtgen" -#: graphicswin.cpp:132 +#: graphicswin.cpp:134 msgid "&Circle" -msgstr "&Çember" +msgstr "D&aire" -#: graphicswin.cpp:133 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" msgstr "Çember &Yayı" -#: graphicswin.cpp:134 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" msgstr "Bezier Kübik &Eğri" -#: graphicswin.cpp:136 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" msgstr "TrueType Yazı Tipinde &Metin" -#: graphicswin.cpp:137 -msgid "&Image" -msgstr "&Resim" - #: graphicswin.cpp:139 +msgid "I&mage" +msgstr "&Resim / Görüntü" + +#: graphicswin.cpp:141 msgid "To&ggle Construction" msgstr "Yap&ıyı Değiştir" -#: graphicswin.cpp:140 -msgid "Tangent &Arc at Point" -msgstr "Noktada &Teğet Yay" +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" +msgstr "&Yuvarlat (Radyus)" -#: graphicswin.cpp:141 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" -msgstr "Kesişim yerinde Eğrileri &Böl" +msgstr "Eğrileri Kesişim Yerinden &Böl" -#: graphicswin.cpp:143 +#: graphicswin.cpp:145 msgid "&Constrain" -msgstr "&Sınırlandır" +msgstr "&Kısıtla" -#: graphicswin.cpp:144 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "&Mesafe / Çap" -#: graphicswin.cpp:145 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" msgstr "&Referans Ölçü" -#: graphicswin.cpp:146 -msgid "A&ngle" -msgstr "&Açı" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "&Açı / Eşit Açı" -#: graphicswin.cpp:147 +#: graphicswin.cpp:149 msgid "Reference An&gle" msgstr "Referans A&çı" -#: graphicswin.cpp:148 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" msgstr "Diğer &Bütünler Açı" -#: graphicswin.cpp:149 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" msgstr "Ölçüyü Re&ferans Yap / Yapma" -#: graphicswin.cpp:151 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "&Yatay" -#: graphicswin.cpp:152 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "&Dikey" -#: graphicswin.cpp:154 +#: graphicswin.cpp:156 msgid "&On Point / Curve / Plane" msgstr "&Noktada / Eğride / Düzlemde" -#: graphicswin.cpp:155 -msgid "E&qual Length / Radius / Angle" -msgstr "&Eşit Uzunluk / Yarıçap / Açı" - -#: graphicswin.cpp:156 -msgid "Length Ra&tio" -msgstr "&Uzunluk Oranı" - #: graphicswin.cpp:157 -msgid "Length Diff&erence" -msgstr "U&zunluk Farkı" +msgid "E&qual Length / Radius" +msgstr "&Eşit Uzunluk / Yarıçap" #: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "&Uzunluk / Yay Oranı" + +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Uzunluk / Yay &Farkı" + +#: graphicswin.cpp:160 msgid "At &Midpoint" msgstr "&Orta Noktada" -#: graphicswin.cpp:159 +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "&Simetrik" -#: graphicswin.cpp:160 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "&Paralel / Teğet" -#: graphicswin.cpp:161 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "D&ik" -#: graphicswin.cpp:162 +#: graphicswin.cpp:164 msgid "Same Orient&ation" msgstr "Aynı &Yön" -#: graphicswin.cpp:163 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "Sürüklendiği Yerde Noktayı &Kilitle" -#: graphicswin.cpp:165 +#: graphicswin.cpp:167 msgid "Comment" msgstr "Y&orum" -#: graphicswin.cpp:167 +#: graphicswin.cpp:169 msgid "&Analyze" -msgstr "&Analiz Et" +msgstr "&Analiz" -#: graphicswin.cpp:168 +#: graphicswin.cpp:170 msgid "Measure &Volume" -msgstr "&Hacmi Ölçün" +msgstr "&Hacmi Hesapla" -#: graphicswin.cpp:169 +#: graphicswin.cpp:171 msgid "Measure A&rea" -msgstr "&Alanı Ölçün" +msgstr "&Alanı Hesapla" -#: graphicswin.cpp:170 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" -msgstr "&Çevre Uzunluğunu Ölçün" +msgstr "&Çevre Uzunluğunu Hesapla" -#: graphicswin.cpp:171 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" -msgstr "&Engelleyen Parçaları Göster" +msgstr "&Engelleyici Parçaları Göster" -#: graphicswin.cpp:172 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" msgstr "A&çık Kenarları Göster" -#: graphicswin.cpp:173 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" msgstr "&Kütle Merkezini Göster" -#: graphicswin.cpp:175 +#: graphicswin.cpp:177 msgid "Show &Underconstrained Points" -msgstr "&Sınırlanmamış Noktaları Göster" +msgstr "Kı&sıtlanmamış Noktaları Göster" -#: graphicswin.cpp:177 +#: graphicswin.cpp:179 msgid "&Trace Point" -msgstr "&Noktayı İzle" +msgstr "&İzlenecek Nokta" -#: graphicswin.cpp:178 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." -msgstr "&İzlemeyi &Durdur..." +msgstr "&İzlemeyi Durdur..." -#: graphicswin.cpp:179 +#: graphicswin.cpp:181 msgid "Step &Dimension..." -msgstr "Adım &Ölçüsü..." +msgstr "Adım &Ölçüsünü Ayarla..." -#: graphicswin.cpp:181 +#: graphicswin.cpp:183 msgid "&Help" msgstr "&Yardım" -#: graphicswin.cpp:182 +#: graphicswin.cpp:184 msgid "&Language" msgstr "&Dil" -#: graphicswin.cpp:183 +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "&Web sitesi / Kılavuz" -#: graphicswin.cpp:185 +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "GitHub Commitlere git" + +#: graphicswin.cpp:188 msgid "&About" msgstr "&Hakkında" -#: graphicswin.cpp:355 +#: graphicswin.cpp:362 msgid "(no recent files)" -msgstr "(yeni dosyalar yok)" +msgstr "(son dosyalar yok)" -#: graphicswin.cpp:363 +#: graphicswin.cpp:370 #, c-format msgid "File '%s' does not exist." msgstr "'%s' dosyası mevcut değil." -#: graphicswin.cpp:725 +#: graphicswin.cpp:779 msgid "No workplane is active, so the grid will not appear." msgstr "Etkin çalışma düzlemi yok, bu nedenle ızgara görünmeyecektir." -#: graphicswin.cpp:740 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel " "projection.\n" @@ -1123,23 +1244,22 @@ msgid "" "For a perspective projection, modify the perspective factor in the " "configuration screen. A value around 0.3 is typical." msgstr "" -"Perspektif çarpanı sıfıra ayarlanmıştır, bu nedenle görünüm her zaman " +"Perspektif katsayısı sıfıra ayarlanmıştır, bu nedenle görünüm her zaman " "paralel bir projeksiyon olacaktır.\n" "\n" -"Perspektif bir projeksiyon için, konfigürasyon ekranındaki perspektif " -"çarpanını değiştirin. 0,3 civarında bir değer tipiktir." +"Perspektif bir projeksiyon için, yapılandırma ekranındaki perspektif " +"katsayısını değiştirin. 0.3 civarında bir değer normaldir." -#: graphicswin.cpp:819 +#: graphicswin.cpp:884 msgid "" "Select a point; this point will become the center of the view on screen." -msgstr "" -"Bir nokta seçin; bu nokta ekrandaki görüntünün merkezi haline gelecektir." +msgstr "Bir nokta seçin; bu nokta ekrandaki görüntünün merkezi olacaktır." -#: graphicswin.cpp:1114 +#: graphicswin.cpp:1193 msgid "No additional entities share endpoints with the selected entities." -msgstr "Hiçbir ek öğe, seçili öğeler ile uç noktaları paylaşmaz." +msgstr "Hiçbir ek öğe, seçili öğeler ile uç noktaları paylaşmıyor." -#: graphicswin.cpp:1132 +#: graphicswin.cpp:1211 msgid "" "To use this command, select a point or other entity from an linked part, or " "make a link group the active group." @@ -1147,135 +1267,138 @@ msgstr "" "Bu komutu kullanmak için, bağlantılı bir parçadan bir nokta veya başka bir " "öğe seçin veya bir bağlantı grubunu etkin grup haline getirin." -#: graphicswin.cpp:1155 +#: graphicswin.cpp:1234 msgid "" "No workplane is active. Activate a workplane (with Sketch -> In Workplane) " "to define the plane for the snap grid." msgstr "" -"Etkin çalışma düzlemi yok. Tutturma ızgarasının düzlemini tanımlamak için " -"bir çalışma düzlemini (Çizim -> Çalışma Düzleminde menüsü ile) etkinleştirin." +"Hiçbir çalışma düzlemi etkin değil. Izgaranın yakalanacağı düzlemini " +"tanımlamak için bir çalışma düzlemini (Çizim -> Çalışma Düzleminde ile) " +"etkinleştirin." -#: graphicswin.cpp:1162 +#: graphicswin.cpp:1241 msgid "" "Can't snap these items to grid; select points, text comments, or constraints " "with a label. To snap a line, select its endpoints." msgstr "" -"Bu öğeleri ızgaraya tutturamazsınız; noktaları, metin yorumlarını veya " -"sınırlamaları bir etiketle seçin. Bir çizgiyi tutturmak için uç noktalarını " -"seçin." +"Bu öğeler ızgaraya tutturulamıyor; etiketli noktaları, metin yorumlarını " +"veya kısıtlamaları seçin. Bir çizgiyi tutturmak için uç noktalarını seçin." -#: graphicswin.cpp:1247 +#: graphicswin.cpp:1326 msgid "No workplane selected. Activating default workplane for this group." msgstr "" "Çalışma düzlemi seçilmedi. Bu grup için varsayılan çalışma düzlemi " "etkinleştiriliyor." -#: graphicswin.cpp:1250 +#: graphicswin.cpp:1329 msgid "" "No workplane is selected, and the active group does not have a default " "workplane. Try selecting a workplane, or activating a sketch-in-new-" "workplane group." msgstr "" -"Hiçbir çalışma düzlemi seçilmemiştir ve aktif grubun varsayılan bir çalışma " -"düzlemi yoktur. Bir çalışma düzlemi seçmeyi veya yeni çalışma düzleminde " +"Hiçbir çalışma düzlemi seçilmedi ve aktif grubun varsayılan bir çalışma " +"düzlemi yok. Bir çalışma düzlemi seçmeyi veya bir yeni çalışma düzleminde " "çizim grubunu etkinleştirmeyi deneyin." -#: graphicswin.cpp:1271 +#: graphicswin.cpp:1350 msgid "" "Bad selection for tangent arc at point. Select a single point, or select " "nothing to set up arc parameters." msgstr "" -"Noktada teğet yay oluşturmak için hatalı seçim. Tek bir nokta seçin veya yay " -"parametrelerini ayarlamak için hiçbir şey seçmeyin." +"Noktada teğet yay (radyus) oluşturmak için hatalı seçim. Tek bir nokta seçin " +"veya yay parametrelerini ayarlamak için hiçbir şey seçmeyin." -#: graphicswin.cpp:1282 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" msgstr "yayın ilk noktası için tıklayın (saat yönünün tersine çizilir)" -#: graphicswin.cpp:1283 +#: graphicswin.cpp:1362 msgid "click to place datum point" -msgstr "referans noktasını yerleştirmek için tıklayın" +msgstr "referans noktayı yerleştirmek için tıklayın" -#: graphicswin.cpp:1284 +#: graphicswin.cpp:1363 msgid "click first point of line segment" msgstr "çizgi parçasının ilk noktası için tıklayın" -#: graphicswin.cpp:1286 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" -msgstr "yapı çizgisinin ilk noktası için tıklayın" +msgstr "yardımcı çizginin ilk noktası için tıklayın" -#: graphicswin.cpp:1287 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" -msgstr "kübik segmentin ilk noktası için tıklayın" +msgstr "kübik eğri parçanın ilk noktası için tıklayın" -#: graphicswin.cpp:1288 +#: graphicswin.cpp:1367 msgid "click center of circle" -msgstr "çemberin merkezi için tıklayın" +msgstr "dairenin merkez konumu için tıklayın" -#: graphicswin.cpp:1289 +#: graphicswin.cpp:1368 msgid "click origin of workplane" -msgstr "çalışma düzleminin merkezi için tıklayın" +msgstr "çalışma düzleminin merkez konumu için tıklayın" -#: graphicswin.cpp:1290 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" -msgstr "dikdörtgenin bir köşesi için tıklayın" +msgstr "dikdörtgenin bir köşe noktası için tıklayın" -#: graphicswin.cpp:1291 +#: graphicswin.cpp:1370 msgid "click top left of text" -msgstr "metnin sol üst köşesi için tıklayın" +msgstr "metnin sol üst köşe konumu için tıklayın" -#: graphicswin.cpp:1297 +#: graphicswin.cpp:1376 msgid "click top left of image" -msgstr "resmin sol üst köşesi için tıklayın" +msgstr "görüntünün sol üst köşe konumu için tıklayın" -#: graphicswin.cpp:1309 +#: graphicswin.cpp:1402 msgid "" "No entities are selected. Select entities before trying to toggle their " "construction state." msgstr "" -"Hiçbir öğe seçilmedi. Yapı durumlarını geçiş yapmaya çalışmadan önce öğeleri " +"Hiçbir öğe seçilmedi. Yapı durumlarını değiştirmeye çalışmadan önce öğeleri " "seçin." #: group.cpp:86 msgctxt "group-name" msgid "sketch-in-3d" -msgstr "3d-içinde-çizim" +msgstr "3d-de-çizim" -#: group.cpp:142 +#: group.cpp:154 msgid "" "Bad selection for new sketch in workplane. This group can be created with:\n" "\n" " * a point (through the point, orthogonal to coordinate axes)\n" " * a point and two line segments (through the point, parallel to the " "lines)\n" +" * a point and a normal (through the point, orthogonal to the normal)\n" " * a workplane (copy of the workplane)\n" msgstr "" -"Çalışma düzleminde yeni taslak çizmek için hatalı seçim. Bu grup şunlarla " -"oluşturulabilir:\n" +"Çalışma düzleminde yeni çizim oluşturmak için hatalı seçim. Bu grup " +"aşağıdakilerle oluşturulabilir:\n" "\n" -" * bir nokta (nokta boyunca, eksenleri koordine etmek için ortogonal)\n" -" * bir nokta ve iki çizgi parçası (noktadan, çizgilere paralel)\n" -" * bir çalışma düzlemi (çalışma düzleminin kopyası)\n" +" * bir nokta (noktadan geçen, koordinat eksenine dik)\n" +" * bir nokta ve iki doğru parçası (noktadan geçen, doğrulara paralel)\n" +" * bir nokta ve bir normal (noktadan normale dik)\n" +" * bir çalışma düzlemi (çalışma düzleminin kopyası)\n" -#: group.cpp:154 +#: group.cpp:170 msgid "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " "will be extruded normal to the workplane." msgstr "" -"Katılama işleminden önce bir çalışma düzlemini etkinleştirin (Çizim -> " -"Çalışma Düzleminde menüsü). Çizim, çalışma düzlemine dik olarak " -"katılanacaktır." +"Katılama işleminden önce bir çalışma düzlemini etkinleştirin. Çizim, " +"çalışma düzlemine dik olarak katılanacaktır." -#: group.cpp:163 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" -msgstr "katıla" +msgstr "doğrusal katıla" -#: group.cpp:168 +#: group.cpp:184 msgid "Lathe operation can only be applied to planar sketches." -msgstr "Çark işlemi yalnızca düzlemsel çizimlere uygulanabilir." +msgstr "" +"Tam tur döndürerek katılama işlemi, yalnızca düzlemsel çizimlere " +"uygulanabilir." -#: group.cpp:179 +#: group.cpp:195 msgid "" "Bad selection for new lathe group. This group can be created with:\n" "\n" @@ -1283,22 +1406,25 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" -"Yeni çark grubu için hatalı seçim. Bu grup şu şekilde oluşturulabilir:\n" +"Tamamen döndürerek yeni katılama grubu oluşturmak için hatalı seçim. Bu grup " +"şu şekilde oluşturulabilir:\n" "\n" -" * bir nokta ve bir çizgi parçası veya normal (çizgiye / normale paralel " +" * bir nokta ve bir çizgi parçası veya normal (çizgiye / normale paralel " "bir eksen etrafında, nokta boyunca döndürülür )\n" -" * bir çizgi parçası (çizgi parçası etrafında döndürülür)\n" +" * bir çizgi parçası (çizgi parçası etrafında döndürülür)\n" -#: group.cpp:189 +#: group.cpp:205 msgctxt "group-name" msgid "lathe" -msgstr "çark" +msgstr "tamamen döndürerek katıla" -#: group.cpp:194 +#: group.cpp:210 msgid "Revolve operation can only be applied to planar sketches." -msgstr "Döndürme işlemi yalnızca düzlemsel çizimlere uygulanabilir." +msgstr "" +"Kısmen döndürerek katılama işlemi, yalnızca düzlemsel çizimlere " +"uygulanabilir." -#: group.cpp:205 +#: group.cpp:221 msgid "" "Bad selection for new revolve group. This group can be created with:\n" "\n" @@ -1306,22 +1432,23 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" -"Yeni döndürme grup için hatalı seçim. Bu grup şunlarla oluşturulabilir:\n" +"Kısmen döndürerek yeni katılama grubu oluşturmak için hatalı seçim. Bu grup " +"şu şekilde oluşturulabilir:\n" "\n" " * bir nokta ve bir çizgi parçası veya normal (çizgiye / normale paralel, " "noktadan geçen bir eksen etrafında döndürülür)\n" " * bir çizgi parçası (çizgi parçası etrafında döndürülür)\n" -#: group.cpp:217 +#: group.cpp:233 msgctxt "group-name" msgid "revolve" -msgstr "döndür" +msgstr "kısmen döndürerek katıla" -#: group.cpp:222 +#: group.cpp:238 msgid "Helix operation can only be applied to planar sketches." msgstr "Helis işlemi yalnızca düzlemsel çizimlere uygulanabilir." -#: group.cpp:233 +#: group.cpp:249 msgid "" "Bad selection for new helix group. This group can be created with:\n" "\n" @@ -1329,18 +1456,19 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" -"Yeni helis grubu için hatalı seçim. Bu grup şunlarla oluşturulabilir:\n" +"Yeni helis grubu oluşturmak için hatalı seçim. Bu grup şunlarla " +"oluşturulabilir:\n" "\n" " * bir nokta ve bir çizgi parçası veya normal (çizgiye / normale paralel, " "noktadan geçen bir eksen etrafında döndürülür)\n" " * bir çizgi parçası (çizgi parçası etrafında döndürülür)\n" -#: group.cpp:245 +#: group.cpp:261 msgctxt "group-name" msgid "helix" msgstr "helis" -#: group.cpp:258 +#: group.cpp:274 msgid "" "Bad selection for new rotation. This group can be created with:\n" "\n" @@ -1349,180 +1477,187 @@ msgid "" " * a point and a line or a normal (rotate about an axis through the " "point, and parallel to line / normal)\n" msgstr "" -"Yeni çevirme için hatalı seçim. Bu grup şunlarla oluşturulabilir:\n" +"Yeni dairesel kopyalama/çoğaltma oluşturmak için hatalı seçim. Bu grup " +"şunlarla oluşturulabilir:\n" "\n" -" * çalışma düzleminde bir nokta, kilitli olduğu sürece (düzlemde o nokta " +" * bir nokta, çalışma düzleminde kilitli olduğu sürece (düzlemde o nokta " "etrafında çevirin)\n" -" * bir nokta ve bir çizgi veya bir normal (nokta boyunca bir eksen " -"etrafında ve çizgiye / normale paralel çevirin)\n" +" * bir nokta ve bir çizgi veya bir normal ( çizgiye / normale paralel ve " +"noktadan geçen bir eksen etrafında çevirin)\n" -#: group.cpp:271 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" -msgstr "çevir" +msgstr "dairesel kopya" -#: group.cpp:282 +#: group.cpp:298 msgctxt "group-name" msgid "translate" -msgstr "ötele" +msgstr "doğrusal kopya" -#: group.cpp:400 +#: group.cpp:422 msgid "(unnamed)" msgstr "(isimsiz)" -#: groupmesh.cpp:709 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" -msgstr "kapalı olmayan kontur veya tümü aynı biçimde değil!" +msgstr "kapali olmayan kontur veya tümü ayni bicimde degil!" -#: groupmesh.cpp:722 +#: groupmesh.cpp:723 msgid "points not all coplanar!" msgstr "noktaların hepsi aynı düzlemde değil!" -#: groupmesh.cpp:724 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" -msgstr "kontur kendisiyle kesişiyor!" +msgstr "kontur kendisiyle kesisiyor!" -#: groupmesh.cpp:726 +#: groupmesh.cpp:727 msgid "zero-length edge!" -msgstr "sıfır-uzunlukta kenar!" +msgstr "sıfır-uzunluklu kenar!" + +#: importmesh.cpp:136 +msgid "Text-formated STL files are not currently supported" +msgstr "Metin-biçimli STL dosyaları şu anda desteklenmiyor" -#: modify.cpp:254 +#: modify.cpp:252 msgid "Must be sketching in workplane to create tangent arc." msgstr "Teğet yay oluşturmak için çalışma düzleminde çizim yapılmalıdır." -#: modify.cpp:301 +#: modify.cpp:299 msgid "" "To create a tangent arc, select a point where two non-construction lines or " "circles in this group and workplane join." msgstr "" -"Bir teğet yay oluşturmak için, bu gruptaki iki yapı-dışı çizginin veya " -"dairenin ve çalışma düzleminin birleştiği bir nokta seçin." +"Bir teğet yay oluşturmak için, bu gruptaki iki (yardımcı) referans-olmayan " +"çizginin veya dairenin ve çalışma düzleminin birleştiği bir nokta seçin." -#: modify.cpp:388 +#: modify.cpp:386 msgid "" "Couldn't round this corner. Try a smaller radius, or try creating the " "desired geometry by hand with tangency constraints." msgstr "" "Bu köşe yuvarlatılamadı. Daha küçük bir yarıçap deneyin veya teğet " -"sınırlamaları ile istenen geometriyi elle oluşturmayı deneyin." +"kısıtlamaları ile istenen geometriyi elle oluşturmayı deneyin." -#: modify.cpp:597 +#: modify.cpp:595 msgid "Couldn't split this entity; lines, circles, or cubics only." -msgstr "Bu öğeler bölünemedi; yalnızca çizgiler, çemberler veya küpler." +msgstr "" +"Bu öğe bölünemedi; yalnızca çizgiler, daireler veya kübik eğriler " +"bölünebilir." -#: modify.cpp:624 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." -msgstr "Bölmek için çalışma düzleminde çizim olmalı." +msgstr "Bölmek için çalışma düzleminde çizim yapılıyor olmalı." -#: modify.cpp:631 +#: modify.cpp:629 msgid "" "Select two entities that intersect each other (e.g. two lines/circles/arcs " "or a line/circle/arc and a point)." msgstr "" -"Birbiriyle kesişen iki öğe seçin (örneğin iki çizgi / çember / yay veya bir " -"çizgi / çember / yay ve bir nokta)." +"Birbiriyle kesişen iki öğe seçin (örneğin iki çizgi / daire / yay veya bir " +"çizgi / daire / yay ve bir nokta)." -#: modify.cpp:736 +#: modify.cpp:734 msgid "Can't split; no intersection found." msgstr "Bölünemez; kesişim bulunamadı." -#: mouse.cpp:559 +#: mouse.cpp:558 msgid "Assign to Style" -msgstr "Biçime Ata" +msgstr "Biçim Ata" -#: mouse.cpp:575 +#: mouse.cpp:574 msgid "No Style" msgstr "Biçim Yok" -#: mouse.cpp:578 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." msgstr "Yeni Oluşturulan Özel Biçim ..." -#: mouse.cpp:585 +#: mouse.cpp:584 msgid "Group Info" msgstr "Grup Bilgisi" -#: mouse.cpp:605 +#: mouse.cpp:604 msgid "Style Info" msgstr "Biçim Bİlgisi" -#: mouse.cpp:625 +#: mouse.cpp:624 msgid "Select Edge Chain" -msgstr "Kenar Zinciri Seçin" +msgstr "Kenar Zinciri Seç" -#: mouse.cpp:631 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" -msgstr "Ölçü Referansını Değiştir" +msgstr "Referans Ölçüyü Değiştir" -#: mouse.cpp:637 +#: mouse.cpp:636 msgid "Other Supplementary Angle" msgstr "Diğer Bütünler Açı" -#: mouse.cpp:642 +#: mouse.cpp:641 msgid "Snap to Grid" msgstr "Izgaraya Tuttur" -#: mouse.cpp:651 +#: mouse.cpp:650 msgid "Remove Spline Point" msgstr "Eğri Noktasını Sil" -#: mouse.cpp:686 +#: mouse.cpp:685 msgid "Add Spline Point" msgstr "Eğri Noktası Ekle" -#: mouse.cpp:690 +#: mouse.cpp:689 msgid "Cannot add spline point: maximum number of points reached." msgstr "Eğri noktası eklenemiyor: en fazla nokta sayısına ulaşıldı." -#: mouse.cpp:715 +#: mouse.cpp:714 msgid "Toggle Construction" msgstr "Yapıyı değiştir" #: mouse.cpp:730 msgid "Delete Point-Coincident Constraint" -msgstr "Çakışan-Nokta Sınırlamasını Sil" +msgstr "Çakışık-Nokta Kısıtlamasını Sil" -#: mouse.cpp:749 +#: mouse.cpp:748 msgid "Cut" msgstr "Kes" -#: mouse.cpp:751 +#: mouse.cpp:750 msgid "Copy" msgstr "Kopyala" -#: mouse.cpp:755 +#: mouse.cpp:754 msgid "Select All" msgstr "Tümünü Seç" -#: mouse.cpp:760 +#: mouse.cpp:759 msgid "Paste" msgstr "Yapıştır" -#: mouse.cpp:762 +#: mouse.cpp:761 msgid "Paste Transformed..." msgstr "Dönüştürerek Yapıştır..." -#: mouse.cpp:767 +#: mouse.cpp:766 msgid "Delete" msgstr "Sil" -#: mouse.cpp:770 +#: mouse.cpp:769 msgid "Unselect All" msgstr "Tüm Seçimi Kaldır" -#: mouse.cpp:777 +#: mouse.cpp:776 msgid "Unselect Hovered" -msgstr "Fareyle Üzerine Gelinen Seçimi Kaldır" +msgstr "Üzerine Gelinen Seçimi Kaldır" -#: mouse.cpp:786 +#: mouse.cpp:785 msgid "Zoom to Fit" msgstr "Sığdırmak için Yakınlaştır" -#: mouse.cpp:988 mouse.cpp:1275 +#: mouse.cpp:987 mouse.cpp:1276 msgid "click next point of line, or press Esc" msgstr "çizginin sonraki noktası için tıklayın veya Esc tuşuna basın" -#: mouse.cpp:994 +#: mouse.cpp:993 msgid "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." @@ -1530,9 +1665,9 @@ msgstr "" "3d'de dikdörtgen çizilemez; önce Çizim -> Çalışma Düzleminde menüsü ile bir " "çalışma düzlemini etkinleştirin." -#: mouse.cpp:1028 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" -msgstr "dikdörtgenin diğer köşesini yerleştirmek için tıklayın" +msgstr "dikdörtgenin diğer köşesini belirtmek için tıklayın" #: mouse.cpp:1048 msgid "click to set radius" @@ -1552,7 +1687,7 @@ msgstr "noktayı yerleştirmek için tıklayın" #: mouse.cpp:1088 msgid "click next point of cubic, or press Esc" -msgstr "sonraki kübik noktayı tıklayın veya Esc tuşuna basın" +msgstr "sonraki kübik eğri noktasını tıklayın veya Esc tuşuna basın" #: mouse.cpp:1093 msgid "" @@ -1571,235 +1706,240 @@ msgstr "" #: mouse.cpp:1126 msgid "click to place bottom right of text" -msgstr "metnin sağ alt konumunu yerleştirmek için tıklayın" +msgstr "metnin sağ alt konumunu belirtmek için tıklayın" #: mouse.cpp:1132 msgid "" "Can't draw image in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" -"3d'de resim eklenemez; önce Çizim -> Çalışma Düzleminde menüsü ile bir " -"çalışma düzlemini etkinleştirin." +"3d'de görüntü/resim eklenemez; önce Çizim -> Çalışma Düzleminde menüsü ile " +"bir çalışma düzlemini etkinleştirin." -#: mouse.cpp:1159 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" -msgstr "YENİ YORUM - DÜZENLEMEK İÇİN ÇİFT TIKLAYIN" - -#: platform/gui.cpp:85 platform/gui.cpp:89 solvespace.cpp:511 +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 msgctxt "file-type" msgid "SolveSpace models" msgstr "SolveSpace Modelleri" -#: platform/gui.cpp:90 +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "TÜMÜ" + +#: platform/gui.cpp:91 msgctxt "file-type" msgid "IDF circuit board" msgstr "IDF devre kartı" -#: platform/gui.cpp:94 +#: platform/gui.cpp:92 +msgctxt "file-type" +msgid "STL triangle mesh" +msgstr "STL üçgensel mesh (ağ/kafes)" + +#: platform/gui.cpp:96 msgctxt "file-type" msgid "PNG image" -msgstr "PNG Resmi" +msgstr "PNG Görüntüsü" -#: platform/gui.cpp:98 +#: platform/gui.cpp:100 msgctxt "file-type" msgid "STL mesh" -msgstr "STL mesh" +msgstr "STL mesh (ağ/kafes)" -#: platform/gui.cpp:99 +#: platform/gui.cpp:101 msgctxt "file-type" msgid "Wavefront OBJ mesh" -msgstr "Wavefront OBJ mesh" +msgstr "Wavefront OBJ mesh (ağ/kafes)" -#: platform/gui.cpp:100 +#: platform/gui.cpp:102 msgctxt "file-type" msgid "Three.js-compatible mesh, with viewer" -msgstr "Görüntüleyicili, Three.js-uyumlu mesh" +msgstr "Görüntüleyicili, Three.js-uyumlu mesh (ağ/kafes)" -#: platform/gui.cpp:101 +#: platform/gui.cpp:103 msgctxt "file-type" msgid "Three.js-compatible mesh, mesh only" -msgstr "Three.js-uyumlu mesh, yalnızca mesh" +msgstr "Three.js-uyumlu mesh, sadece mesh (ağ/kafes)" -#: platform/gui.cpp:102 +#: platform/gui.cpp:104 msgctxt "file-type" msgid "VRML text file" msgstr "VRML metin dosyası" -#: platform/gui.cpp:106 platform/gui.cpp:113 platform/gui.cpp:120 +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 msgctxt "file-type" msgid "STEP file" msgstr "STEP dosyası" -#: platform/gui.cpp:110 +#: platform/gui.cpp:112 msgctxt "file-type" msgid "PDF file" msgstr "PDF dosyası" -#: platform/gui.cpp:111 +#: platform/gui.cpp:113 msgctxt "file-type" msgid "Encapsulated PostScript" -msgstr "Kapsüllenmiş PostScript" +msgstr "Encapsulated PostScript (EPS)" -#: platform/gui.cpp:112 +#: platform/gui.cpp:114 msgctxt "file-type" msgid "Scalable Vector Graphics" msgstr "Ölçeklenebilir Vektör Grafikleri (SVG)" -#: platform/gui.cpp:114 platform/gui.cpp:121 +#: platform/gui.cpp:116 platform/gui.cpp:123 msgctxt "file-type" msgid "DXF file (AutoCAD 2007)" msgstr "DXF dosyası (AutoCAD 2007)" -#: platform/gui.cpp:115 +#: platform/gui.cpp:117 msgctxt "file-type" msgid "HPGL file" msgstr "HPGL dosyası" -#: platform/gui.cpp:116 +#: platform/gui.cpp:118 msgctxt "file-type" msgid "G Code" msgstr "G Kodu" -#: platform/gui.cpp:125 +#: platform/gui.cpp:127 msgctxt "file-type" msgid "AutoCAD DXF and DWG files" msgstr "AutoCAD DXF ve DWG dosyaları" -#: platform/gui.cpp:129 +#: platform/gui.cpp:131 msgctxt "file-type" msgid "Comma-separated values" msgstr "Virgülle ayrılmış değerler (CSV)" -#: platform/guigtk.cpp:1324 platform/guimac.mm:1363 platform/guiwin.cpp:1639 +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" msgstr "isimsiz" -#: platform/guigtk.cpp:1335 platform/guigtk.cpp:1368 platform/guimac.mm:1321 -#: platform/guiwin.cpp:1582 +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 msgctxt "title" msgid "Save File" msgstr "Dosyayı Kaydet" -#: platform/guigtk.cpp:1336 platform/guigtk.cpp:1369 platform/guimac.mm:1304 -#: platform/guiwin.cpp:1584 +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 msgctxt "title" msgid "Open File" msgstr "Dosyayı Aç" -#: platform/guigtk.cpp:1339 platform/guigtk.cpp:1375 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" msgid "_Cancel" msgstr "_İptal" -#: platform/guigtk.cpp:1340 platform/guigtk.cpp:1373 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" msgid "_Save" msgstr "_Kaydet" -#: platform/guigtk.cpp:1341 platform/guigtk.cpp:1374 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" msgid "_Open" msgstr "_Aç" -#: solvespace.cpp:169 +#: solvespace.cpp:175 msgctxt "title" msgid "Autosave Available" msgstr "Otomatik Kaydetme Kullanılabilir" -#: solvespace.cpp:170 +#: solvespace.cpp:176 msgctxt "dialog" msgid "An autosave file is available for this sketch." msgstr "Bu çizim için otomatik kaydetme dosyası kullanılabilir." -#: solvespace.cpp:171 +#: solvespace.cpp:177 msgctxt "dialog" msgid "Do you want to load the autosave file instead?" msgstr "Bunun yerine otomatik kaydetme dosyasını yüklemek istiyor musunuz?" -#: solvespace.cpp:172 +#: solvespace.cpp:178 msgctxt "button" msgid "&Load autosave" -msgstr "&Otomatik kaydetmeyi yükle" +msgstr "&Otomatik kaydı yükle" -#: solvespace.cpp:174 +#: solvespace.cpp:180 msgctxt "button" msgid "Do&n't Load" msgstr "&Yükleme" -#: solvespace.cpp:557 +#: solvespace.cpp:640 msgctxt "title" msgid "Modified File" msgstr "Değiştirilen Dosya" -#: solvespace.cpp:559 +#: solvespace.cpp:642 #, c-format msgctxt "dialog" msgid "Do you want to save the changes you made to the sketch “%s”?" msgstr "\"%s\" çiziminde yaptığınız değişiklikleri kaydetmek istiyor musunuz?" -#: solvespace.cpp:562 +#: solvespace.cpp:645 msgctxt "dialog" msgid "Do you want to save the changes you made to the new sketch?" msgstr "Yeni çizimde yaptığınız değişiklikleri kaydetmek istiyor musunuz?" -#: solvespace.cpp:565 +#: solvespace.cpp:648 msgctxt "dialog" msgid "Your changes will be lost if you don't save them." msgstr "Bunları kaydetmezseniz değişiklikleriniz kaybolur." -#: solvespace.cpp:566 +#: solvespace.cpp:649 msgctxt "button" msgid "&Save" msgstr "&Kaydet" -#: solvespace.cpp:568 +#: solvespace.cpp:651 msgctxt "button" msgid "Do&n't Save" msgstr "K&aydetme" -#: solvespace.cpp:589 +#: solvespace.cpp:672 msgctxt "title" msgid "(new sketch)" msgstr "(yeni çizim)" -#: solvespace.cpp:596 +#: solvespace.cpp:683 msgctxt "title" msgid "Property Browser" -msgstr "Özellik Tarayıcısı" +msgstr "Özellik Penceresi" -#: solvespace.cpp:658 +#: solvespace.cpp:746 msgid "" "Constraints are currently shown, and will be exported in the toolpath. This " "is probably not what you want; hide them by clicking the link at the top of " "the text window." msgstr "" -"Sınırlamalar şu anda gösterilmektedir ve takım yolunda dışa aktarılacaktır. " -"Muhtemelen istediğiniz bu değil; metin penceresinin üst kısmındaki " +"Kısıtlamalar şu anda gösterilmektedir ve takım yolunda dışa aktarılacaktır. " +"Muhtemelen istediğiniz bu değil; Özellik Penceresinin üst kısmındaki " "bağlantıya tıklayarak bunları gizleyin." -#: solvespace.cpp:730 +#: solvespace.cpp:834 #, c-format msgid "" "Can't identify file type from file extension of filename '%s'; try .dxf or ." "dwg." msgstr "" -"Dosya türü '%s' dosya uzantısından tanımlanamıyor; .dxf veya .dwg'yi deneyin." +"'%s' dosya adının dosya uzantısından dosya türü belirlenemiyor; .dxf veya ." +"dwg'yi deneyin." -#: solvespace.cpp:778 +#: solvespace.cpp:886 msgid "Constraint must have a label, and must not be a reference dimension." -msgstr "Sınırlamanın bir etiketi olmalı ve bir referans ölçüsü olmamalıdır." +msgstr "Kısıtlamanın bir etiketi olmalı ve bir referans ölçüsü olmamalıdır." -#: solvespace.cpp:782 +#: solvespace.cpp:890 msgid "Bad selection for step dimension; select a constraint." -msgstr "" -"Adım ölçüsü için hatalı seçim; bir nokta boyunca, eksenleri koordine etmek " -"için ortogonal seçin." +msgstr "Adım ölçüsü ayarlamak için hatalı seçim; bir kısıtlama seçin." -#: solvespace.cpp:806 +#: solvespace.cpp:914 msgid "The assembly does not interfere, good." -msgstr "Montaj engel değil, iyi." +msgstr "Montajda engel oluşturan parça yok, iyi." -#: solvespace.cpp:822 +#: solvespace.cpp:930 #, c-format msgid "" "The volume of the solid model is:\n" @@ -1808,9 +1948,9 @@ msgid "" msgstr "" "Katı modelin hacmi:\n" "\n" -" % s" +" % s" -#: solvespace.cpp:831 +#: solvespace.cpp:939 #, c-format msgid "" "\n" @@ -1819,11 +1959,11 @@ msgid "" " %s" msgstr "" "\n" -"Mevcut mesh grubunun hacmi:\n" +"Mevcut ağ (kafes) grubunun hacmi:\n" "\n" " %s" -#: solvespace.cpp:836 +#: solvespace.cpp:944 msgid "" "\n" "\n" @@ -1832,10 +1972,10 @@ msgid "" msgstr "" "\n" "\n" -"Eğri yüzeyler, üçgenler olarak yaklaştırılmıştır.\n" -"Bu, tipik olarak yaklaşık 1% hataya neden olur." +"Kavisli (Eğri) yüzeyler üçgenler olarak yaklaştırılmıştır.\n" +"Bu, genel olarak yaklaşık 1% hataya neden olur." -#: solvespace.cpp:851 +#: solvespace.cpp:959 #, c-format msgid "" "The surface area of the selected faces is:\n" @@ -1845,22 +1985,22 @@ msgid "" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%." msgstr "" -"Seçilen yüzlerin yüzey alanı:\n" +"Seçili yüzlerin yüzey alanı:\n" "\n" " %s\n" "\n" -"Eğriler, parçalı doğrusal olarak yaklaştırıldı.\n" -"Bu, tipik olarak yaklaşık 1%% hataya neden olur." +"Eğriler, doğrusal parçalı olarak yaklaştırılmıştır.\n" +"Bu, genel olarak yaklaşık 1%% hataya neden olur." -#: solvespace.cpp:860 +#: solvespace.cpp:968 msgid "" "This group does not contain a correctly-formed 2d closed area. It is open, " "not coplanar, or self-intersecting." msgstr "" -"Bu grup, doğru biçimlendirilmiş 2d kapalı alan içermiyor. Açık, eş düzlemli " +"Bu grup, doğru biçimlendirilmiş 2d kapalı alan içermiyor. Açık, eş-düzlemli " "değil veya kendisiyle kesişiyor." -#: solvespace.cpp:872 +#: solvespace.cpp:980 #, c-format msgid "" "The area of the region sketched in this group is:\n" @@ -1874,10 +2014,10 @@ msgstr "" "\n" " %s\n" "\n" -"Eğriler, parçalı doğrusal olarak yaklaştırıldı.\n" -"Bu, tipik olarak yaklaşık 1%% hataya neden olur." +"Eğriler, doğrusal parçalı olarak yaklaştırılmıştır.\n" +"Bu, genel olarak yaklaşık 1%% hataya neden olur." -#: solvespace.cpp:892 +#: solvespace.cpp:1000 #, c-format msgid "" "The total length of the selected entities is:\n" @@ -1892,40 +2032,40 @@ msgstr "" " %s\n" "\n" "Eğriler, parçalı doğrusal olarak yaklaştırıldı.\n" -"Bu, tipik olarak yaklaşık 1%% hataya neden olur." +"Bu, genel olarak yaklaşık 1%% hataya neden olur." -#: solvespace.cpp:898 +#: solvespace.cpp:1006 msgid "Bad selection for perimeter; select line segments, arcs, and curves." msgstr "" -"Çevre uzunluğu için hatalı seçim; çizgi parçalarını, yayları ve eğrileri " -"seçin." +"Çevre uzunluğunu hesabı için hatalı seçim; çizgi parçalarını, yayları ve " +"eğrileri seçin." -#: solvespace.cpp:914 +#: solvespace.cpp:1022 msgid "Bad selection for trace; select a single point." -msgstr "İzleme için hatalı seçim; tek bir nokta seçin." +msgstr "Nokta izleme işlemi için hatalı seçim; tek bir nokta seçin." -#: solvespace.cpp:941 +#: solvespace.cpp:1049 #, c-format msgid "Couldn't write to '%s'" -msgstr "\"%s\" ye yazılamadı" +msgstr "\"%s\" öğesine yazılamadı" -#: solvespace.cpp:971 +#: solvespace.cpp:1079 msgid "The mesh is self-intersecting (NOT okay, invalid)." -msgstr "Mesh kendisiyle kesişiyor (TAMAM değil, geçersiz)." +msgstr "Ağ (Kafes) kendisiyle kesişiyor (İyi DEĞİL, geçersiz)." -#: solvespace.cpp:972 +#: solvespace.cpp:1080 msgid "The mesh is not self-intersecting (okay, valid)." -msgstr "Mesh kendi kendine kesişmiyor (tamam, geçerli)." +msgstr "Ağ (Kafes) kendisi ile kesişmiyor (iyi, geçerli)." -#: solvespace.cpp:974 +#: solvespace.cpp:1082 msgid "The mesh has naked edges (NOT okay, invalid)." -msgstr "Mesh'in açık kenarları var (tamam DEĞİL, geçersiz)." +msgstr "Ağın (Kafesin) açık kenarları var (iyi DEĞİL, geçersiz)." -#: solvespace.cpp:975 +#: solvespace.cpp:1083 msgid "The mesh is watertight (okay, valid)." -msgstr "Mesh çok sıkı (tamam, geçerli)" +msgstr "Ağ (Kafes) çok sıkı (iyi, geçerli)" -#: solvespace.cpp:978 +#: solvespace.cpp:1086 #, c-format msgid "" "\n" @@ -1934,9 +2074,9 @@ msgid "" msgstr "" "\n" "\n" -"Model, %d yüzeylerden %d üçgen içerir." +"Model, %d yüzeyden %d üçgen içeriyor." -#: solvespace.cpp:982 +#: solvespace.cpp:1090 #, c-format msgid "" "%s\n" @@ -1951,7 +2091,7 @@ msgstr "" "\n" "Sıfır sorunlu kenar, iyi.%s" -#: solvespace.cpp:985 +#: solvespace.cpp:1093 #, c-format msgid "" "%s\n" @@ -1966,7 +2106,7 @@ msgstr "" "\n" "%d sorunlu kenar, kötü.%s" -#: solvespace.cpp:998 +#: solvespace.cpp:1106 #, c-format msgid "" "This is SolveSpace version %s.\n" @@ -1984,132 +2124,132 @@ msgid "" msgstr "" "Bu SolveSpace'in %s sürümüdür.\n" "\n" -"Daha fazla bilgi için, http://solvespace.com/ bkz.\n" +"Daha fazla bilgi için bkz., http://solvespace.com/\n" "\n" "SolveSpace ücretsiz bir yazılımdır: GNU Genel Kamu \n" -"Lisansı (GPL) sürüm 3 veya daha sonraki hükümler altında \n" +"Lisansı (GPL) sürüm 3 veya daha sonraki hükümleri altında \n" "onu değiştirmekte ve / veya yeniden dağıtmakta özgürsünüz.\n" "\n" -"Yasaların izin verdiği ölçüde GARANTİ YOKTUR. \n" +"Lisans kuralları haricinde GARANTİ YOKTUR.\n" "Ayrıntılar için http://gnu.org/licenses/ adresini ziyaret edin.\n" "\n" "© 2008-% d Jonathan Westhues ve diğer yazarlar.\n" -#: style.cpp:166 +#: style.cpp:185 msgid "" "Can't assign style to an entity that's derived from another entity; try " "assigning a style to this entity's parent." msgstr "" -"Başka bir öğeden türetilen bir öğeye biçim atayamazsınız; bu öğenin üst " -"öğesine bir biçim atamayı deneyin." +"Başka bir öğeden türetilen bir öğeye biçim atayamazsınız; bu öğenin " +"üst öğesine bir biçim atamayı deneyin." -#: style.cpp:665 +#: style.cpp:735 msgid "Style name cannot be empty" msgstr "Biçim adı boş olamaz" -#: textscreens.cpp:741 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." -msgstr "1 defadan az tekrar edilemez." +msgstr "1 defadan az tekrarlanamaz." -#: textscreens.cpp:745 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." -msgstr "999 defadan fazla tekrar edilemez." +msgstr "999 defadan fazla tekrarlanamaz." -#: textscreens.cpp:770 +#: textscreens.cpp:866 msgid "Group name cannot be empty" msgstr "Grup adı boş olamaz" -#: textscreens.cpp:813 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." msgstr "Şeffaflık değeri sıfır ile bir arasında olmalıdır." -#: textscreens.cpp:848 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "Yarıçap sıfır veya negatif değer olamaz." #: toolbar.cpp:18 msgid "Sketch line segment" -msgstr "Çizgi parçası ile taslak çizin" +msgstr "Çizgi parçası çiz" #: toolbar.cpp:20 msgid "Sketch rectangle" -msgstr "Dikdörtgen çizin" +msgstr "Dikdörtgen çiz" #: toolbar.cpp:22 msgid "Sketch circle" -msgstr "Çember çizin" +msgstr "Daire çiz" #: toolbar.cpp:24 msgid "Sketch arc of a circle" -msgstr "Bir çember yayı çizin" +msgstr "Bir çember yayı çiz" #: toolbar.cpp:26 msgid "Sketch curves from text in a TrueType font" -msgstr "TrueType yazı tipindeki metinden eğriler çizin" +msgstr "TrueType yazı tipindeki metinden eğriler çiz" #: toolbar.cpp:28 msgid "Sketch image from a file" -msgstr "Bir dosyadan resim ekleyin" +msgstr "Bir dosyadan görüntü (resim) ekle" #: toolbar.cpp:30 msgid "Create tangent arc at selected point" -msgstr "Seçilen noktada teğet yay oluşturun" +msgstr "Seçilen noktada radyus (teğet yay) oluştur" #: toolbar.cpp:32 msgid "Sketch cubic Bezier spline" -msgstr "Kübik Bezier Eğri Çizin" +msgstr "Kübik Bezier Eğri Çiz" #: toolbar.cpp:34 msgid "Sketch datum point" -msgstr "Referans Nokta Ekleyin" +msgstr "Referans Nokta Ekle" #: toolbar.cpp:36 msgid "Toggle construction" -msgstr "Yapıyı değiştirin" +msgstr "Yapıyı değiştir" #: toolbar.cpp:38 msgid "Split lines / curves where they intersect" -msgstr "Çizgileri / eğrileri kesiştikleri yerde bölün" +msgstr "Çizgileri / eğrileri kesiştikleri yerden böl" #: toolbar.cpp:42 msgid "Constrain distance / diameter / length" -msgstr "Mesafeyi / çapı / uzunluğu sınırlandırın" +msgstr "Mesafeyi / çapı / uzunluğu kısıtla" #: toolbar.cpp:44 msgid "Constrain angle" -msgstr "Açıyı sınırlandırın" +msgstr "Açıyı kısıtla" #: toolbar.cpp:46 msgid "Constrain to be horizontal" -msgstr "Yatay olarak sınırlandırın" +msgstr "Yatay olarak kısıtla" #: toolbar.cpp:48 msgid "Constrain to be vertical" -msgstr "Dikey olarak sınırlandırın" +msgstr "Dikey olarak kısıtla" #: toolbar.cpp:50 msgid "Constrain to be parallel or tangent" -msgstr "Paralel veya teğet olarak sınırlandırın" +msgstr "Paralel veya teğet olarak kısıtla" #: toolbar.cpp:52 msgid "Constrain to be perpendicular" -msgstr "Dik olarak sınırlandırın" +msgstr "Dik olarak kısıtla" #: toolbar.cpp:54 msgid "Constrain point on line / curve / plane / point" -msgstr "Noktayı çizgi / eğri / düzlem / nokta ile çakıştırarak sınırlandırın" +msgstr "Noktayı çizgi / eğri / düzlem / nokta ile çakıştırarak kısıtla" #: toolbar.cpp:56 msgid "Constrain symmetric" -msgstr "Simetrik olarak sınırlandırın" +msgstr "Simetrik olarak kısıtla" #: toolbar.cpp:58 msgid "Constrain equal length / radius / angle" -msgstr "Eşit uzunluk / yarıçap / açı olarak sınırlandırın" +msgstr "Eşit uzunluk / yarıçap / açı olarak kısıtla" #: toolbar.cpp:60 msgid "Constrain normals in same orientation" -msgstr "Normalleri aynı yönde sınırlandırın" +msgstr "Normalleri aynı yönde kısıtla" #: toolbar.cpp:62 msgid "Other supplementary angle" @@ -2121,11 +2261,11 @@ msgstr "Ölçüyü Referans Yap / Yapma" #: toolbar.cpp:68 msgid "New group extruding active sketch" -msgstr "Etkin çizimi katılayarak yeni grup oluşturun" +msgstr "Etkin çizimi uzatıp katılayarak yeni grup oluştur" #: toolbar.cpp:70 msgid "New group rotating active sketch" -msgstr "Etkin çizimi döndürerek yeni grup oluşturun" +msgstr "Etkin çizimi tam döndürerek yeni grup oluştur" #: toolbar.cpp:72 msgid "New group helix from active sketch" @@ -2133,28 +2273,28 @@ msgstr "Etkin çizimden sarmal yeni grup oluştur" #: toolbar.cpp:74 msgid "New group revolve active sketch" -msgstr "Etkin çizimi döndürülür yeni grup oluşturun" +msgstr "Etkin çizimi kısmen döndürerek yeni grup oluştur" #: toolbar.cpp:76 msgid "New group step and repeat rotating" -msgstr "Adımlayarak ve tekrarlayarak yeni dönüş grup oluşturun" +msgstr "Adım ve tekrar değeri belirterek dairesel kopya grubu oluştur" #: toolbar.cpp:78 msgid "New group step and repeat translating" -msgstr "Adımlayarak ve tekrarlayarak yeni öteleme grup oluşturun" +msgstr "Adım ve tekrar değeri belirterek yeni doğrusal kopyalama grup oluştur" #: toolbar.cpp:80 msgid "New group in new workplane (thru given entities)" msgstr "" -"Yeni çalışma düzleminde yeni grup oluşturun (verilen öğeler aracılığıyla)" +"Yeni çalışma düzleminde yeni grup oluştur (verilen öğeler aracılığıyla)" #: toolbar.cpp:82 msgid "New group in 3d" -msgstr "3d'de yeni grup oluşturun" +msgstr "3d'de yeni grup oluştur" #: toolbar.cpp:84 msgid "New group linking / assembling file" -msgstr "Bağlantı / Montaj dosyası ile yeni grup oluşturun" +msgstr "Bağlantı / Montaj dosyası ile yeni grup oluştur" #: toolbar.cpp:88 msgid "Nearest isometric view" @@ -2162,7 +2302,7 @@ msgstr "En yakın izometrik görünüm" #: toolbar.cpp:90 msgid "Align view to active workplane" -msgstr "Görünümü etkin çalışma düzlemine hizalayın" +msgstr "Görünümü etkin çalışma düzlemine hizala" #: util.cpp:165 msgctxt "title" @@ -2179,14 +2319,12 @@ msgctxt "button" msgid "&OK" msgstr "&Tamam" -#: view.cpp:78 +#: view.cpp:127 msgid "Scale cannot be zero or negative." msgstr "Ölçek sıfır veya negatif olamaz." -#: view.cpp:90 view.cpp:99 +#: view.cpp:139 view.cpp:148 msgid "Bad format: specify x, y, z" msgstr "Kötü biçim: x, y, z'yi belirtin" -#~ msgctxt "file-type" -#~ msgid "Q3D Object file" -#~ msgstr "Q3D Object file" + diff --git a/res/locales/uk_UA.po b/res/locales/uk_UA.po index e71069a9e..24ca895e9 100644 --- a/res/locales/uk_UA.po +++ b/res/locales/uk_UA.po @@ -5,9 +5,9 @@ msgid "" msgstr "" "Project-Id-Version: SolveSpace 3.0\n" -"Report-Msgid-Bugs-To: https://github.com/solvespace/solvespace/issues\n" -"POT-Creation-Date: 2021-02-01 15:45+0200\n" -"PO-Revision-Date: 2021-04-14 01:42+0300\n" +"Report-Msgid-Bugs-To: phkahler@gmail.com\n" +"POT-Creation-Date: 2025-01-26 21:04+0200\n" +"PO-Revision-Date: 2025-01-27 12:58+0200\n" "Last-Translator: https://github.com/Symbian9\n" "Language-Team: app4soft\n" "Language: uk_UA\n" @@ -15,9 +15,9 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.4.2\n" +"X-Generator: Poedit 3.5\n" -#: clipboard.cpp:310 +#: clipboard.cpp:314 msgid "" "Cut, paste, and copy work only in a workplane.\n" "\n" @@ -27,79 +27,77 @@ msgstr "" "\n" "Активуйте одну через Креслення -> У площині." -#: clipboard.cpp:327 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." msgstr "Буфер обміну порожній; немає чого вставляти." -#: clipboard.cpp:374 +#: clipboard.cpp:378 msgid "Number of copies to paste must be at least one." msgstr "Кількість копій для вставки має бути не менше одної." -#: clipboard.cpp:390 textscreens.cpp:783 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "Масштаб не може бути нульовим." -#: clipboard.cpp:432 +#: clipboard.cpp:436 msgid "Select one point to define origin of rotation." msgstr "Оберіть одну точку для визначення центру обертання." -#: clipboard.cpp:444 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." msgstr "Оберіть дві точки для визначення вектору розміщення." -#: clipboard.cpp:454 +#: clipboard.cpp:458 msgid "" "Transformation is identity. So all copies will be exactly on top of each " "other." -msgstr "" +msgstr "Трансформація ідентична, тому усі копії будуть точно одна на одній." -#: clipboard.cpp:458 -#, fuzzy +#: clipboard.cpp:462 msgid "Too many items to paste; split this into smaller pastes." -msgstr "Забагато об'єктів для вставки; рзділіть копіювання на кілька етапів." +msgstr "" +"Забагато об'єктів для вставки; розділіть копіювання на кілька менших етапів." -#: clipboard.cpp:463 +#: clipboard.cpp:467 msgid "No workplane active." msgstr "Немає активної площини." -#: confscreen.cpp:418 +#: confscreen.cpp:410 msgid "Bad format: specify coordinates as x, y, z" -msgstr "Некоректний формат: визначте координати X, Y, Z" +msgstr "Некоректний формат: визначте координати як X, Y, Z" -#: confscreen.cpp:428 style.cpp:659 textscreens.cpp:805 +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 msgid "Bad format: specify color as r, g, b" msgstr "Некоректний формат: визначте колір як R, G, B" -#: confscreen.cpp:454 -#, fuzzy +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." msgstr "" "Значення перспективи не матиме ефекту допоки не ввімкнено Вигляд -> " -"Використовувати Перспективну проєкцію." +"Використовувати Перспективну Проєкцію." -#: confscreen.cpp:467 confscreen.cpp:477 +#: confscreen.cpp:464 confscreen.cpp:474 #, c-format msgid "Specify between 0 and %d digits after the decimal." msgstr "Визначте кількість десяткових знаків від 0 до %d." -#: confscreen.cpp:489 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" msgstr "Масштаб експорту не може бути нульовим!" -#: confscreen.cpp:501 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" msgstr "Радіус відступу різання не може бути від'ємним!" -#: confscreen.cpp:555 +#: confscreen.cpp:557 msgid "Bad value: autosave interval should be positive" msgstr "Некоректне значення: інтервал автозбереження має бути додатнім" -#: confscreen.cpp:558 -#, fuzzy +#: confscreen.cpp:560 msgid "Bad format: specify interval in integral minutes" -msgstr "Некоректний формат: визначте цілим числом інтервал у хвилинах" +msgstr "Некоректний формат: визначте інтервал цілим числом у хвилинах" #: constraint.cpp:12 msgctxt "constr-name" @@ -129,7 +127,7 @@ msgstr "відстань-тчк-грань" #: constraint.cpp:17 msgctxt "constr-name" msgid "proj-pt-pt-distance" -msgstr "проєційна-відстань-тчк-тчк" +msgstr "проєкційна-відстань-тчк-тчк" #: constraint.cpp:18 msgctxt "constr-name" @@ -168,133 +166,216 @@ msgstr "пропорція-довжин" #: constraint.cpp:25 msgctxt "constr-name" +msgid "arc-arc-length-ratio" +msgstr "пропорція-довжин-дуга-дуга" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "arc-line-length-ratio" +msgstr "пропорція-довжини-дуга-лінія" + +#: constraint.cpp:27 +msgctxt "constr-name" msgid "length-difference" msgstr "різниця-довжин" -#: constraint.cpp:26 +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "arc-arc-len-difference" +msgstr "різниця-довжин-дуга-дуга" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "arc-line-len-difference" +msgstr "різниця-довжин-дуга-лінія" + +#: constraint.cpp:30 msgctxt "constr-name" msgid "symmetric" msgstr "симетрія" -#: constraint.cpp:27 +#: constraint.cpp:31 msgctxt "constr-name" msgid "symmetric-h" msgstr "симетрія-вертикально" -#: constraint.cpp:28 +#: constraint.cpp:32 msgctxt "constr-name" msgid "symmetric-v" msgstr "симетрія-горизонтально" -#: constraint.cpp:29 +#: constraint.cpp:33 msgctxt "constr-name" msgid "symmetric-line" msgstr "симетрія-відносно-лінії" -#: constraint.cpp:30 +#: constraint.cpp:34 msgctxt "constr-name" msgid "at-midpoint" msgstr "на-середині" -#: constraint.cpp:31 +#: constraint.cpp:35 msgctxt "constr-name" msgid "horizontal" msgstr "горизонталь" -#: constraint.cpp:32 +#: constraint.cpp:36 msgctxt "constr-name" msgid "vertical" msgstr "вертикаль" -#: constraint.cpp:33 +#: constraint.cpp:37 msgctxt "constr-name" msgid "diameter" msgstr "діаметр" -#: constraint.cpp:34 +#: constraint.cpp:38 msgctxt "constr-name" msgid "pt-on-circle" msgstr "тчк-на-колі" -#: constraint.cpp:35 +#: constraint.cpp:39 msgctxt "constr-name" msgid "same-orientation" -msgstr "співнаправленість" +msgstr "співнапрямленість" -#: constraint.cpp:36 +#: constraint.cpp:40 msgctxt "constr-name" msgid "angle" msgstr "кут" -#: constraint.cpp:37 +#: constraint.cpp:41 msgctxt "constr-name" msgid "parallel" msgstr "паралель" -#: constraint.cpp:38 +#: constraint.cpp:42 msgctxt "constr-name" msgid "arc-line-tangent" msgstr "дотичні-дуга-лінія" -#: constraint.cpp:39 +#: constraint.cpp:43 msgctxt "constr-name" msgid "cubic-line-tangent" msgstr "дотичні-сплайн-лінія" -#: constraint.cpp:40 +#: constraint.cpp:44 msgctxt "constr-name" msgid "curve-curve-tangent" msgstr "дотичні-крива-крива" -#: constraint.cpp:41 +#: constraint.cpp:45 msgctxt "constr-name" msgid "perpendicular" msgstr "перпендикуляр" -#: constraint.cpp:42 +#: constraint.cpp:46 msgctxt "constr-name" msgid "eq-radius" msgstr "рівнозначні-радіуси" -#: constraint.cpp:43 +#: constraint.cpp:47 msgctxt "constr-name" msgid "eq-angle" msgstr "рівнозначні-кути" -#: constraint.cpp:44 +#: constraint.cpp:48 msgctxt "constr-name" msgid "eq-line-len-arc-len" msgstr "рівнозначні-лінія-дуга" -#: constraint.cpp:45 +#: constraint.cpp:49 msgctxt "constr-name" msgid "lock-where-dragged" msgstr "фіксоване-положення" -#: constraint.cpp:46 +#: constraint.cpp:50 msgctxt "constr-name" msgid "comment" msgstr "коментар" -#: constraint.cpp:140 +#: constraint.cpp:151 msgid "" -"The tangent arc and line segment must share an endpoint. Constrain them with " -"Constrain -> On Point before constraining tangent." +"The point you selected does not belong to the arc. The arc and line segment " +"do not share an end point.\n" +"\n" +"Select the end point of the arc at which you want it to be tangent to the " +"line." msgstr "" +"Обрана вами точка не належить до дуги. Дуга та відрізок не мають спільної " +"кінцевої точки.\n" +"\n" +"Оберіть кінцеву точку дуги, в якій ви хочете зробити дугу дотичною до лінії." #: constraint.cpp:158 msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them " -"with Constrain -> On Point before constraining tangent." +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be " +"tangent to the line." +msgstr "" +"Дотичні дуга та лінія повинні мати спільну кінцеву точку. Використайте " +"Обмежити -> На точці перед застосуванням дотичного обмеження.\n" +"\n" +"Або оберіть кінцеву точку дуги в якій ви хочете зробити її дотичною до лінії." + +#: constraint.cpp:186 +msgid "" +"The point you selected is not an end point of the cubic spline. The spline " +"and line segment do not share an end point.\n" +"\n" +"Select the end point of the spline at which you want it to be tangent to the " +"line." +msgstr "" +"Обрана вами точка не є кінцевою точкою кубічного сплайну. Сплайн та відрізок " +"не мають спільної кінцевої точки.\n" +"\n" +"Оберіть кінцеву точку сплайну в якій ви хочете зробити його дотичним до " +"лінії." + +#: constraint.cpp:193 +msgid "" +"The tangent cubic spline and line segment must share an endpoint. Constrain " +"them with Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it " +"to be tangent to the line." msgstr "" +"Дотичні сплайн та відрізок повинні мати спільну кінцеву точку. Використайте " +"Обмежити -> На точці перед застосуванням дотичного обмеження.\n" +"\n" +"Або оберіть кінцеву точку сплайну в якій ви хочете зробити його дотичним до " +"лінії." -#: constraint.cpp:183 +#: constraint.cpp:237 +msgid "" +"The points you selected are not end points of the two curves. The curves do " +"not share an end point.\n" +"\n" +"Select the end points of both curves at which you want them to be tangent to " +"each other." +msgstr "" +"Обрані вами точки не є кінцевими точками двох дуг. Дуги не поділяють кінцеву " +"точку.\n" +"\n" +"Оберіть кінцеві точки обох дуг, в яких ви бажаєте зробити їх дотичними одна " +"одній." + +#: constraint.cpp:244 msgid "" "The curves must share an endpoint. Constrain them with Constrain -> On Point " -"before constraining tangent." +"before constraining tangent.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." msgstr "" +"Дотичні дуги повинні мати спільну кінцеву точку. Використайте Обмежити -> На " +"точці перед застосуванням дотичного обмеження.\n" +"\n" +"Або оберіть кінцеві точки обох дуг в яких ви хочете зробити дуги дотичними." -#: constraint.cpp:231 +#: constraint.cpp:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply " "to:\n" @@ -307,59 +388,107 @@ msgid "" " * a plane face and a point (minimum distance)\n" " * a circle or an arc (diameter)\n" msgstr "" +"Поганий вибір для обмеження за Відстанню / Діаметром. Це обмеження можна " +"застосувати до:\n" +"\n" +" * двох точок (відстань між точками)\n" +" * відрізку прямої (довжина)\n" +" * двох точок та відрізку прямої чи нормалі (відстань між проєкціями " +"точок)\n" +" * робочої площини та точки (найменша відстань)\n" +" * відрізку прямої та точки (найменша відстань)\n" +" * грані та точки (найменша відстань)\n" +" * кола або дуги (діаметр)\n" -#: constraint.cpp:284 +#: constraint.cpp:366 msgid "" "Bad selection for on point / curve / plane constraint. This constraint can " "apply to:\n" "\n" -" * two points (points coincident)\n" +" * two or more points (points coincident)\n" " * a point and a workplane (point in plane)\n" " * a point and a line segment (point on line)\n" " * a point and a circle or arc (point on curve)\n" -" * a point and a plane face (point on face)\n" +" * a point and one to three plane faces (point on face(s))\n" msgstr "" +"Поганий вибір для обмеження На Точці / Кривій / Площині. Це обмеження можна " +"застосувати до:\n" +"\n" +" * двох або більше точок (точки збігаються)\n" +" * точки та площини (точка лежить в площині)\n" +" * точки та відрізку прямої (точка лежить на прямій)\n" +" * точки та кола чи дуги (точка лежить на дузі)\n" +" * точки та від одієї до трьох граней (точка лежить на грані(ях))\n" -#: constraint.cpp:346 +#: constraint.cpp:427 msgid "" "Bad selection for equal length / radius constraint. This constraint can " "apply to:\n" "\n" -" * two line segments (equal length)\n" +" * two or more line segments (equal length)\n" " * two line segments and two points (equal point-line distances)\n" " * a line segment and two points (equal point-line distances)\n" " * a line segment, and a point and line segment (point-line distance " "equals length)\n" -" * four line segments or normals (equal angle between A,B and C,D)\n" -" * three line segments or normals (equal angle between A,B and B,C)\n" -" * two circles or arcs (equal radius)\n" +" * two or more circles or arcs (equal radius)\n" " * a line segment and an arc (line segment length equals arc length)\n" msgstr "" +"Поганий вибір для обмеження Рівні Довжина / Радіус. Це обмеження можна " +"застосувати до:\n" +"\n" +" * двох або більше відрізків прямої (рівні довжини)\n" +" * двох відрізків та двох точок (рівні відстані від точок до відповідних " +"відрізків)\n" +" * відрізка та двох точок (рівні відстані від точок до відрізка)\n" +" * відрізка, точки, та другого відрізка (відстань від точки до другого " +"відрізка дорівнює першому)\n" +" * двох або більше кіл чи дуг (рівні радіуси)\n" +" * відрізку та дуги (довжина відрізку дорівнює довжині дуги) \n" -#: constraint.cpp:385 +#: constraint.cpp:480 msgid "" "Bad selection for length ratio constraint. This constraint can apply to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" +"Поганий вибір для обмеження Пропорція Довжин. Це обмеження можна застосувати " +"до:\n" +"\n" +" * двох відрізків прямої\n" +" * двох дуг\n" +" * дуги та відрізка прямої\n" -#: constraint.cpp:402 +#: constraint.cpp:515 msgid "" "Bad selection for length difference constraint. This constraint can apply " "to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" +"Поганий вибір для обмеження Різниця Довжин. Це обмеження можна застосувати " +"до:\n" +"\n" +" * двох відрізків\n" +" * двох дуг\n" +" * відрізка та дуги\n" -#: constraint.cpp:428 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" " * a line segment and a point (point at midpoint)\n" " * a line segment and a workplane (line's midpoint on plane)\n" msgstr "" +"Поганий вибір для обмеження До Середини. Це обмеження можна застосувати до:\n" +"\n" +" * відрізка та точки (точка на середині відрізку)\n" +" * відрізка та робочої площини (середина відрізку лежить в площині)\n" -#: constraint.cpp:486 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -370,87 +499,154 @@ msgid "" " * workplane, and two points or a line segment (symmetric about " "workplane)\n" msgstr "" +"Поганий вибір для обмеження за симетрією. Це обмеження можна застосувати " +"до:\n" +"\n" +" * двох точок або відрізку прямої (симетрія відносно початку координат " +"робочої площини)\n" +" * відрізка та двох точок або іншого відрізка (симетрія відносно " +"відрізка)\n" +" * робочої площини та двох точок або відрізка (симетрія відносно робочої " +"площини)\n" -#: constraint.cpp:500 +#: constraint.cpp:623 msgid "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." msgstr "" +"Робоча площина має бути активною коли використовується обмеження за " +"симетрією без явного вибору площини." -#: constraint.cpp:530 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." msgstr "" +"Активуйте площину (в Креслення -> У Робочій Площині) перед застосуванням " +"обмеження Горизонтально або Вертикально." -#: constraint.cpp:543 +#: constraint.cpp:679 msgid "" "Bad selection for horizontal / vertical constraint. This constraint can " "apply to:\n" "\n" -" * two points\n" -" * a line segment\n" +" * two or more points\n" +" * one or more line segments\n" msgstr "" +"Поганий вибір для обмеження Горизонтально / Вертикально. Це обмеження можна " +"застосувати до:\n" +"\n" +" * двох або більше точок\n" +" * одного або більше відрізків\n" -#: constraint.cpp:564 +#: constraint.cpp:697 msgid "" "Bad selection for same orientation constraint. This constraint can apply " "to:\n" "\n" " * two normals\n" msgstr "" +"Поганий вибір для обмеження Однакова Орієнтація. Це обмеження можна " +"застосувати до:\n" +"\n" +" * двох перпендикулярів\n" -#: constraint.cpp:614 -#, fuzzy +#: constraint.cpp:748 msgid "Must select an angle constraint." msgstr "Необхідно обрати кут." -#: constraint.cpp:627 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." msgstr "Необхідно обрати обмежувач з відповідною міткою." -#: constraint.cpp:638 +#: constraint.cpp:784 msgid "" "Bad selection for angle constraint. This constraint can apply to:\n" "\n" +"Angle between:\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" +"\n" +"Equal angles:\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" msgstr "" +"Поганий вибір для обмеження за Кутом. Це обмеження можна застосувати до:\n" +"\n" +"Кута між:\n" +" * двома відрізками\n" +" * відрізком та перпендикуляром\n" +" * двома перпендикулярами\n" +"\n" +"Рівних кутів між:\n" +" * чотирма відрізками чи перпендикулярами (рівні кути між А,Б та В,Г)\n" +" * трьома відрізками чи перпендикулярами (рівні кути між А,Б та Б,В)\n" -#: constraint.cpp:701 +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." -msgstr "" +msgstr "Обмеження дотичності дуг має відбуватися у робочій площині." -#: constraint.cpp:711 +#: constraint.cpp:887 msgid "" "Bad selection for parallel / tangent constraint. This constraint can apply " "to:\n" "\n" -" * two line segments (parallel)\n" -" * a line segment and a normal (parallel)\n" -" * two normals (parallel)\n" +" * two faces\n" +" * two or more line segments (parallel)\n" +" * one or more line segments and one or more normals (parallel)\n" +" * two or more normals (parallel)\n" " * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +" * two line segments, arcs, or beziers, that do not share an endpoint and " +"the end point(s) of the curve(s) (tangent)\n" msgstr "" - -#: constraint.cpp:729 +"Поганий вибір для обмеження Паралельно / Дотична. Це обмеження можна " +"застосувати до:\n" +"\n" +" * двох граней\n" +" * двох або більше відрізків (паралельно)\n" +" * одного або більше відрізка та одного або більше перпендикуляра " +"(паралельно)\n" +" * двох або більше перпендикулярів (паралельно)\n" +" * двох відрізків, дуг, чи кривих Безьє, що мають спільну кінцеву " +"точку(дотична)\n" +" * двох відрізків, дуг, чи кривих Безьє, що не мають спільної кінцевої " +"точки та кінцевою точкою(точками) дуги(дуг) (дотична)\n" + +#: constraint.cpp:914 msgid "" "Bad selection for perpendicular constraint. This constraint can apply to:\n" "\n" +" * two faces\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" msgstr "" +"Поганий вибір для обмеження Перпендикулярно. Це обмеження можна застосувати " +"до:\n" +"\n" +" * двох граней\n" +" * двох відрізків\n" +" * відрізка та перпендикуляра\n" +" * двох перпендикулярів\n" -#: constraint.cpp:744 +#: constraint.cpp:931 msgid "" "Bad selection for lock point where dragged constraint. This constraint can " "apply to:\n" "\n" " * a point\n" msgstr "" +"Поганий вибір для обмеження Фіксувати Точку Після Переміщення. Це обмеження " +"можна застосувати до:\n" +"\n" +" * точки\n" -#: constraint.cpp:755 +#: constraint.cpp:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "КОМЕНТАР -- ДВІЧІ КЛІКНІТЬ ДЛЯ РЕДАГУВАННЯ" + +#: constraint.cpp:952 msgid "click center of comment text" msgstr "клікніть в місце де буде центр коментаря" @@ -459,9 +655,9 @@ msgid "" "No solid model present; draw one with extrudes and revolves, or use Export " "2d View to export bare lines and curves." msgstr "" -"Вісутня об'ємна модель; створіть одну з допомогою екструдування та " -"виточування або скористайтеся функцією \"Експортувати 2D Вигляд\" для " -"еспорту лише ліній та кривих." +"Вісутня об'ємна модель; створіть модель за допомогою екструдування та " +"обертання або скористайтеся функцією \"Експортувати 2D Вигляд\" для еспорту " +"лише ліній та кривих." #: export.cpp:61 msgid "" @@ -472,27 +668,34 @@ msgid "" " * a point and two line segments (plane through point and parallel to " "lines)\n" msgstr "" +"Поганий вибір для експорту розрізу. Будьласка, оберіть:\n" +"\n" +" * нічого, з активною робочою площиною (робоча площина буде площиною " +"розрізу)\n" +" * грань (площина розрізу через грань)\n" +" * точку та два відрізки (площина розрізу через точку та паралельно " +"відрізкам)\n" -#: export.cpp:822 +#: export.cpp:818 msgid "Active group mesh is empty; nothing to export." -msgstr "Активна група не містить меш; немає чого експортувати." +msgstr "Активна група не містить сітку; немає чого експортувати." -#: exportvector.cpp:337 +#: exportvector.cpp:336 msgid "freehand lines were replaced with continuous lines" -msgstr "" +msgstr "довільні лінії було замінено на неперервні відрізки" -#: exportvector.cpp:339 +#: exportvector.cpp:338 msgid "zigzag lines were replaced with continuous lines" -msgstr "" +msgstr "zigzag лінії було замінено на неперервні відрізки" -#: exportvector.cpp:593 +#: exportvector.cpp:592 msgid "" "Some aspects of the drawing have no DXF equivalent and were not exported:\n" msgstr "" "Деякі аспекти креслення на мають відповідників у форматі DXF і не будуть " "експортовані:\n" -#: exportvector.cpp:839 +#: exportvector.cpp:838 msgid "" "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." msgstr "" @@ -509,11 +712,11 @@ msgctxt "group-name" msgid "#references" msgstr "#базові-площини" -#: file.cpp:552 +#: file.cpp:555 msgid "The file is empty. It may be corrupt." msgstr "Файл порожній. Він може бути пошкодженим." -#: file.cpp:557 +#: file.cpp:560 msgid "" "Unrecognized data in file. This file may be corrupt, or from a newer version " "of the program." @@ -521,18 +724,18 @@ msgstr "" "Нерозпізнані дані у файлі. Цей файл може бути пошкодженим або збереженим " "новою версією програми." -#: file.cpp:867 +#: file.cpp:876 msgctxt "title" msgid "Missing File" msgstr "Втрачений Файл" -#: file.cpp:868 +#: file.cpp:877 #, c-format msgctxt "dialog" msgid "The linked file “%s” is not present." msgstr "Приєднаний файл “%s” відсутній." -#: file.cpp:870 +#: file.cpp:879 msgctxt "dialog" msgid "" "Do you want to locate it manually?\n" @@ -540,18 +743,21 @@ msgid "" "If you decline, any geometry that depends on the missing file will be " "permanently removed." msgstr "" +"Шукати файл вручну?\n" +"\n" +"При відмові, уся геометрія, що залежить від втраченого файлу буде видалена." -#: file.cpp:873 +#: file.cpp:882 msgctxt "button" msgid "&Yes" msgstr "&Так" -#: file.cpp:875 +#: file.cpp:884 msgctxt "button" msgid "&No" msgstr "&Ні" -#: file.cpp:877 solvespace.cpp:569 +#: file.cpp:886 solvespace.cpp:652 msgctxt "button" msgid "&Cancel" msgstr "&Відмінити" @@ -725,295 +931,307 @@ msgid "Use &Perspective Projection" msgstr "Використовувати &Перспективну Проекцію" #: graphicswin.cpp:97 +msgid "Show E&xploded View" +msgstr "Показати С&кладальне Креслення" + +#: graphicswin.cpp:98 msgid "Dimension &Units" msgstr "Розмірні &Одиниці" -#: graphicswin.cpp:98 +#: graphicswin.cpp:99 msgid "Dimensions in &Millimeters" msgstr "Розміри у &Міліметрах" -#: graphicswin.cpp:99 +#: graphicswin.cpp:100 msgid "Dimensions in M&eters" msgstr "Розміри у &Метрах" -#: graphicswin.cpp:100 +#: graphicswin.cpp:101 msgid "Dimensions in &Inches" msgstr "Розміри у &Дюймах" #: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "Розміри в &Футах та Дюймах" + +#: graphicswin.cpp:104 msgid "Show &Toolbar" msgstr "Показати Панель &Інструментів" -#: graphicswin.cpp:103 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" msgstr "Показати Вікно Власти&востей" -#: graphicswin.cpp:105 +#: graphicswin.cpp:107 msgid "&Full Screen" msgstr "&Повний Екран" -#: graphicswin.cpp:107 +#: graphicswin.cpp:109 msgid "&New Group" msgstr "&Нова Група" -#: graphicswin.cpp:108 +#: graphicswin.cpp:110 msgid "Sketch In &3d" msgstr "Креслення у &3D" -#: graphicswin.cpp:109 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" msgstr "Креслення у Новій &Площині" -#: graphicswin.cpp:111 +#: graphicswin.cpp:113 msgid "Step &Translating" msgstr "Покрокове &Переміщення" -#: graphicswin.cpp:112 +#: graphicswin.cpp:114 msgid "Step &Rotating" msgstr "Покрокове &Обертання" -#: graphicswin.cpp:114 +#: graphicswin.cpp:116 msgid "E&xtrude" msgstr "Ви&давити" -#: graphicswin.cpp:115 +#: graphicswin.cpp:117 msgid "&Helix" msgstr "&Спіраль" -#: graphicswin.cpp:116 +#: graphicswin.cpp:118 msgid "&Lathe" msgstr "&Виточити" -#: graphicswin.cpp:117 +#: graphicswin.cpp:119 msgid "Re&volve" msgstr "&Обертати" -#: graphicswin.cpp:119 +#: graphicswin.cpp:121 msgid "Link / Assemble..." msgstr "Приєднати / Зібрати..." -#: graphicswin.cpp:120 +#: graphicswin.cpp:122 msgid "Link Recent" msgstr "Приєднати Нещодавні" -#: graphicswin.cpp:122 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "&Креслення" -#: graphicswin.cpp:123 +#: graphicswin.cpp:125 msgid "In &Workplane" msgstr "У Робочій &Площині" -#: graphicswin.cpp:124 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" msgstr "Будь-де в &3D" -#: graphicswin.cpp:126 +#: graphicswin.cpp:128 msgid "Datum &Point" msgstr "Опорна &Точка" -#: graphicswin.cpp:127 -msgid "&Workplane" +#: graphicswin.cpp:129 +msgid "Wor&kplane" msgstr "Робоча &Площина" -#: graphicswin.cpp:129 +#: graphicswin.cpp:131 msgid "Line &Segment" msgstr "&Відрізок Прямої" -#: graphicswin.cpp:130 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" msgstr "Контсрук&ційний Відрізок Прямої" -#: graphicswin.cpp:131 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "&Прямокутник" -#: graphicswin.cpp:132 +#: graphicswin.cpp:134 msgid "&Circle" msgstr "&Коло" -#: graphicswin.cpp:133 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" msgstr "&Дуга Кола" -#: graphicswin.cpp:134 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" msgstr "Кубічний Сплайн &Без'є" -#: graphicswin.cpp:136 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" msgstr "&Текст із TrueType Шрифтом" -#: graphicswin.cpp:137 -msgid "&Image" +#: graphicswin.cpp:139 +msgid "I&mage" msgstr "&Зображення" -#: graphicswin.cpp:139 +#: graphicswin.cpp:141 msgid "To&ggle Construction" msgstr "Пере&мкнути Конструктивність" -#: graphicswin.cpp:140 -msgid "Tangent &Arc at Point" +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" msgstr "Дотична &Дуга на Точці" -#: graphicswin.cpp:141 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" msgstr "Розрізати Криві на &Перетині" -#: graphicswin.cpp:143 +#: graphicswin.cpp:145 msgid "&Constrain" msgstr "&Обмежити" -#: graphicswin.cpp:144 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "&Відстань / Діаметр" -#: graphicswin.cpp:145 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" msgstr "Від&носний Розмір" -#: graphicswin.cpp:146 -msgid "A&ngle" -msgstr "К&ут" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "К&ут / Рівний Кут" -#: graphicswin.cpp:147 +#: graphicswin.cpp:149 msgid "Reference An&gle" msgstr "Відносний К&ут" -#: graphicswin.cpp:148 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" msgstr "Інший Су&міжний Кут" -#: graphicswin.cpp:149 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" msgstr "Перемкнути Від&носність Розмірів" -#: graphicswin.cpp:151 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "&Горизонтально" -#: graphicswin.cpp:152 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "&Вертикально" -#: graphicswin.cpp:154 +#: graphicswin.cpp:156 msgid "&On Point / Curve / Plane" msgstr "&На точці / Кривій / Площині" -#: graphicswin.cpp:155 -msgid "E&qual Length / Radius / Angle" -msgstr "Рі&вні Довжина / Радіус / Кут" - -#: graphicswin.cpp:156 -msgid "Length Ra&tio" -msgstr "Про&порція Довжин" - #: graphicswin.cpp:157 -msgid "Length Diff&erence" -msgstr "Рі&зниця Довжин" +msgid "E&qual Length / Radius" +msgstr "Р&івна Довжина / Радіус" #: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "Пропорція довжин" + +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Різниця довжин" + +#: graphicswin.cpp:160 msgid "At &Midpoint" msgstr "До &Середини" -#: graphicswin.cpp:159 +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "Си&метрично" -#: graphicswin.cpp:160 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "Пара&лельно / Дотична" -#: graphicswin.cpp:161 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "&Препендикулярно" -#: graphicswin.cpp:162 +#: graphicswin.cpp:164 msgid "Same Orient&ation" msgstr "Однакова Орієн&тація" -#: graphicswin.cpp:163 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "Фіксувати Точку Після &Переміщення" -#: graphicswin.cpp:165 +#: graphicswin.cpp:167 msgid "Comment" msgstr "Коментар" -#: graphicswin.cpp:167 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "&Аналізувати" -#: graphicswin.cpp:168 +#: graphicswin.cpp:170 msgid "Measure &Volume" msgstr "Обрахувати &Об'єм" -#: graphicswin.cpp:169 +#: graphicswin.cpp:171 msgid "Measure A&rea" msgstr "Обрахувати Пл&ощу" -#: graphicswin.cpp:170 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" msgstr "Обрахувати &Периметр" -#: graphicswin.cpp:171 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" msgstr "Показати &Дотичні Частини" -#: graphicswin.cpp:172 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" msgstr "Показати &Приховані Ребра" -#: graphicswin.cpp:173 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" msgstr "Показати &Центр Масс" -#: graphicswin.cpp:175 +#: graphicswin.cpp:177 msgid "Show &Underconstrained Points" msgstr "Показати &Надмірно Обмежені Точки" -#: graphicswin.cpp:177 +#: graphicswin.cpp:179 msgid "&Trace Point" msgstr "&Трасувати Точку" -#: graphicswin.cpp:178 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." msgstr "&Зупити Трасування..." -#: graphicswin.cpp:179 +#: graphicswin.cpp:181 msgid "Step &Dimension..." msgstr "Прорахувати &Розмір..." -#: graphicswin.cpp:181 +#: graphicswin.cpp:183 msgid "&Help" msgstr "&Довідка" -#: graphicswin.cpp:182 +#: graphicswin.cpp:184 msgid "&Language" msgstr "&Мова" -#: graphicswin.cpp:183 +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "&Вебсайт / Посібник" -#: graphicswin.cpp:185 +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "&До коміту на GitHub" + +#: graphicswin.cpp:188 msgid "&About" msgstr "&Про програму" -#: graphicswin.cpp:355 +#: graphicswin.cpp:362 msgid "(no recent files)" msgstr "(нємає нещодавніх файлів)" -#: graphicswin.cpp:363 +#: graphicswin.cpp:370 #, c-format msgid "File '%s' does not exist." msgstr "Файл '%s' відсутній." -#: graphicswin.cpp:725 +#: graphicswin.cpp:779 msgid "No workplane is active, so the grid will not appear." msgstr "Відсутня активна площина - сітка не відображатиметься." -#: graphicswin.cpp:740 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel " "projection.\n" @@ -1021,113 +1239,136 @@ msgid "" "For a perspective projection, modify the perspective factor in the " "configuration screen. A value around 0.3 is typical." msgstr "" +"Встановлено нульовий коефіцієнт перспективи, тому відображення завжди буде " +"паралельною проєкцією." -#: graphicswin.cpp:819 +#: graphicswin.cpp:884 msgid "" "Select a point; this point will become the center of the view on screen." -msgstr "" +msgstr "Оберіть точку. Ця точка стане центром відображення на екрані." -#: graphicswin.cpp:1114 +#: graphicswin.cpp:1193 msgid "No additional entities share endpoints with the selected entities." -msgstr "" +msgstr "Жодні сутності не мають спільних кінцевих точок з обраними сутностями." -#: graphicswin.cpp:1132 +#: graphicswin.cpp:1211 msgid "" "To use this command, select a point or other entity from an linked part, or " "make a link group the active group." msgstr "" +"Для використання цієї команди оберіть точку або іншу сутність з приєднаної " +"деталі, або зробіть приєднану групу активною." -#: graphicswin.cpp:1155 +#: graphicswin.cpp:1234 msgid "" "No workplane is active. Activate a workplane (with Sketch -> In Workplane) " "to define the plane for the snap grid." msgstr "" +"Жодної активної площини. Активуйте площину (в Креслення -> У Робочій " +"Площині) щоб визначити площину для сітки." -#: graphicswin.cpp:1162 +#: graphicswin.cpp:1241 msgid "" "Can't snap these items to grid; select points, text comments, or constraints " "with a label. To snap a line, select its endpoints." msgstr "" +"Неможливо прикріпити ці штуки до сітки; оберіть точки, текстові коментарі чи " +"обмеження з назвою. Щоб прикріпити лінію, оберіть її кінцеві точки." -#: graphicswin.cpp:1247 +#: graphicswin.cpp:1326 msgid "No workplane selected. Activating default workplane for this group." msgstr "" +"Жодної робочої площини не обрано. Активую площину за замовченням для цієї " +"групи." -#: graphicswin.cpp:1250 +#: graphicswin.cpp:1329 msgid "" "No workplane is selected, and the active group does not have a default " "workplane. Try selecting a workplane, or activating a sketch-in-new-" "workplane group." msgstr "" +"Жодної робочої площини не обрано, і активна група не має площини за " +"замовченням. Спробуйте обрати площину, або активувати групу Креслення У " +"Робочій Площині." -#: graphicswin.cpp:1271 +#: graphicswin.cpp:1350 msgid "" "Bad selection for tangent arc at point. Select a single point, or select " "nothing to set up arc parameters." msgstr "" +"Поганий вибір для дотичної дуги у точці. Оберіть одну точку, або оберіть " +"нічого щоб налаштувати дугу." -#: graphicswin.cpp:1282 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" -msgstr "" +msgstr "клікніть для встановлення точки дуги (проти годинникової стрілки)" -#: graphicswin.cpp:1283 +#: graphicswin.cpp:1362 msgid "click to place datum point" msgstr "клікніть для встановлення вихідної точки" -#: graphicswin.cpp:1284 +#: graphicswin.cpp:1363 msgid "click first point of line segment" msgstr "клікніть першу точку прямої лінії" -#: graphicswin.cpp:1286 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" msgstr "клікніть першу точку конструктивної прямої лінії" -#: graphicswin.cpp:1287 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" msgstr "клікніть першу точку кривої" -#: graphicswin.cpp:1288 +#: graphicswin.cpp:1367 msgid "click center of circle" msgstr "клікніть в місце де буде центр коментаря" -#: graphicswin.cpp:1289 +#: graphicswin.cpp:1368 msgid "click origin of workplane" msgstr "клікніть в центр відліку площини" -#: graphicswin.cpp:1290 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" msgstr "клікніть для встановлення першого кута прямокутника" -#: graphicswin.cpp:1291 +#: graphicswin.cpp:1370 msgid "click top left of text" msgstr "клікніть для встановлення верхньої лівої межі тексту" -#: graphicswin.cpp:1297 +#: graphicswin.cpp:1376 msgid "click top left of image" msgstr "клікніть для встановлення верхньої лівої межі зображення" -#: graphicswin.cpp:1309 +#: graphicswin.cpp:1402 msgid "" "No entities are selected. Select entities before trying to toggle their " "construction state." -msgstr "" +msgstr "Жодної сутності не обрано. Оберіть сутності для перключення їх стану." #: group.cpp:86 msgctxt "group-name" msgid "sketch-in-3d" msgstr "ескіз-в-3D" -#: group.cpp:142 +#: group.cpp:154 msgid "" "Bad selection for new sketch in workplane. This group can be created with:\n" "\n" " * a point (through the point, orthogonal to coordinate axes)\n" " * a point and two line segments (through the point, parallel to the " "lines)\n" +" * a point and a normal (through the point, orthogonal to the normal)\n" " * a workplane (copy of the workplane)\n" msgstr "" +"Поганий вибір для ного креслення в робочій площині. Ця група може бути " +"створена з:\n" +"\n" +" * точки (через точку, ортогонально до координатних осей)\n" +" * точки та двох відрізків (через точку, паралельно до відрізків)\n" +" * точку та перпендикуляр (через точку, ортогонально перпендикуляру)\n" +" * робочої площини (створиться копія площини)\n" -#: group.cpp:154 +#: group.cpp:170 msgid "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " "will be extruded normal to the workplane." @@ -1135,16 +1376,17 @@ msgstr "" "Активуйте робочу площину ( Ескіз -> У Площині) перед екструдуванням. Ескіз " "буде екструдовано перпендикулярно до робочої площини." -#: group.cpp:163 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" msgstr "видавлювання" -#: group.cpp:168 +#: group.cpp:184 msgid "Lathe operation can only be applied to planar sketches." msgstr "" +"Операція виточування може бути застосована тільки для пласких креслень." -#: group.cpp:179 +#: group.cpp:195 msgid "" "Bad selection for new lathe group. This group can be created with:\n" "\n" @@ -1152,17 +1394,23 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" +"Поганий вибір для нової групи проточування. Ця група може бути створена з:\n" +"\n" +" * точки та відрізка або перпендикуляра (обертається крізь точку навколо " +"осі, паралельної до відрізку / перпендикуляру)\n" +" * відрізка (обертається навколо відрізка)\n" -#: group.cpp:189 +#: group.cpp:205 msgctxt "group-name" msgid "lathe" msgstr "проточування" -#: group.cpp:194 +#: group.cpp:210 msgid "Revolve operation can only be applied to planar sketches." msgstr "" +"Операція прокручування може бути застосована тільки для пласких креслень." -#: group.cpp:205 +#: group.cpp:221 msgid "" "Bad selection for new revolve group. This group can be created with:\n" "\n" @@ -1170,17 +1418,22 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" +"Поганий вибір для нової групи прокручування. Ця група може бути створена з:\n" +"\n" +" * точки та відрізка або перпендикуляра (обертається крізь точку навколо " +"осі, паралельної до відрізку / перпендикуляру)\n" +" * відрізка (обертається навколо відрізка)\n" -#: group.cpp:217 +#: group.cpp:233 msgctxt "group-name" msgid "revolve" msgstr "прокручування" -#: group.cpp:222 +#: group.cpp:238 msgid "Helix operation can only be applied to planar sketches." msgstr "Спіраль може бути створена лише на основі площинного ескізу." -#: group.cpp:233 +#: group.cpp:249 msgid "" "Bad selection for new helix group. This group can be created with:\n" "\n" @@ -1188,13 +1441,18 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" +"Поганий вибір для нової групи проточування. Ця група може бути створена з:\n" +"\n" +" * точки та відрізка або перпендикуляра (обертається крізь точку навколо " +"осі, паралельної до відрізку / перпендикуляру)\n" +" * відрізка (обертається навколо відрізка)\n" -#: group.cpp:245 +#: group.cpp:261 msgctxt "group-name" msgid "helix" msgstr "спіраль" -#: group.cpp:258 +#: group.cpp:274 msgid "" "Bad selection for new rotation. This group can be created with:\n" "\n" @@ -1203,122 +1461,142 @@ msgid "" " * a point and a line or a normal (rotate about an axis through the " "point, and parallel to line / normal)\n" msgstr "" +"Поганий вибір для нової групи проточування. Ця група може бути створена з:\n" +"\n" +" * точки, доки закріплено в робочій площині (обертається в площині, " +"навколо цієї точки)\n" +" * точки та відрізка чи перпендикуляря (обертається навколо осі, що " +"проходить крізь цю точку, і паралельна до відрізку / перпендикуляру)\n" -#: group.cpp:271 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" msgstr "крутіння" -#: group.cpp:282 +#: group.cpp:298 msgctxt "group-name" msgid "translate" msgstr "перекладання" -#: group.cpp:400 +#: group.cpp:422 msgid "(unnamed)" msgstr "(безіменне)" -#: groupmesh.cpp:709 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" msgstr "не замкнений контур або не все в єдиному стилі!" -#: groupmesh.cpp:722 +#: groupmesh.cpp:723 msgid "points not all coplanar!" -msgstr "" +msgstr "не всі точки знаходяться в одній площині!" -#: groupmesh.cpp:724 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" msgstr "контур самоперетинається!" -#: groupmesh.cpp:726 +#: groupmesh.cpp:727 msgid "zero-length edge!" msgstr "ребро нульової довжини!" -#: modify.cpp:254 +#: importmesh.cpp:136 +msgid "Text-formated STL files are not currently supported" +msgstr "Файли STL з текстовим форматуванням наразі не підтримуються" + +#: modify.cpp:252 msgid "Must be sketching in workplane to create tangent arc." msgstr "" +"Для створення дотичної дуги має бути обраний режим креслення в робочій " +"площині." -#: modify.cpp:301 +#: modify.cpp:299 msgid "" "To create a tangent arc, select a point where two non-construction lines or " "circles in this group and workplane join." msgstr "" +"Для створення дотичної дуги оберіть точку де два відрізки або дуги " +"поєднуються." -#: modify.cpp:388 +#: modify.cpp:386 msgid "" "Couldn't round this corner. Try a smaller radius, or try creating the " "desired geometry by hand with tangency constraints." msgstr "" +"Неможливо закруглити цей кут. Спробуйте менший радіус або створіть необхідну " +"геометрію вручну задопомогою омеження Дотичності." -#: modify.cpp:597 +#: modify.cpp:595 msgid "Couldn't split this entity; lines, circles, or cubics only." msgstr "" +"Неможливо розділити цей об'єкт. Дозволено тільки для відрізків, кіл(дуг), чи " +"сплайнів." -#: modify.cpp:624 -#, fuzzy +#: modify.cpp:622 msgid "Must be sketching in workplane to split." -msgstr "Має бути накреслений у робочій площині для розділеня." +msgstr "" +"Креслення має відбуватися у робочій площині для використання розділення." -#: modify.cpp:631 +#: modify.cpp:629 msgid "" "Select two entities that intersect each other (e.g. two lines/circles/arcs " "or a line/circle/arc and a point)." msgstr "" +"Оберіть дві сутності, що перетинаються (наприклад лінії/кола/дуги або лінія/" +"коло/дуга та точка)" -#: modify.cpp:736 +#: modify.cpp:734 msgid "Can't split; no intersection found." msgstr "Неможливо розділити; відсутній перетин." -#: mouse.cpp:559 +#: mouse.cpp:558 msgid "Assign to Style" msgstr "Встановити Стиль" -#: mouse.cpp:575 +#: mouse.cpp:574 msgid "No Style" msgstr "Без Стилю" -#: mouse.cpp:578 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." msgstr "Створити Користувацький Стиль..." -#: mouse.cpp:585 +#: mouse.cpp:584 msgid "Group Info" msgstr "Параметри Групи" -#: mouse.cpp:605 +#: mouse.cpp:604 msgid "Style Info" msgstr "Параметри Стилю" -#: mouse.cpp:625 +#: mouse.cpp:624 msgid "Select Edge Chain" msgstr "Виділити Ланцюг Ребер" -#: mouse.cpp:631 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" msgstr "Перемкнути Відносність Розміру" -#: mouse.cpp:637 +#: mouse.cpp:636 msgid "Other Supplementary Angle" msgstr "Інший Суміжний Кут" -#: mouse.cpp:642 +#: mouse.cpp:641 msgid "Snap to Grid" msgstr "Прикріпити до Сітки" -#: mouse.cpp:651 +#: mouse.cpp:650 msgid "Remove Spline Point" msgstr "Видалити Точку Сплайну" -#: mouse.cpp:686 +#: mouse.cpp:685 msgid "Add Spline Point" msgstr "Додати Точку Сплайну" -#: mouse.cpp:690 +#: mouse.cpp:689 msgid "Cannot add spline point: maximum number of points reached." msgstr "" "Неможливо додати точку сплайна: перевищено максимальну кількість точок." -#: mouse.cpp:715 +#: mouse.cpp:714 msgid "Toggle Construction" msgstr "Пермкнути Конструктивність" @@ -1326,53 +1604,55 @@ msgstr "Пермкнути Конструктивність" msgid "Delete Point-Coincident Constraint" msgstr "Роз'єднати З'єднання Точок" -#: mouse.cpp:749 +#: mouse.cpp:748 msgid "Cut" msgstr "Вирізати" -#: mouse.cpp:751 +#: mouse.cpp:750 msgid "Copy" msgstr "Копіювати" -#: mouse.cpp:755 +#: mouse.cpp:754 msgid "Select All" msgstr "Виділити Усе" -#: mouse.cpp:760 +#: mouse.cpp:759 msgid "Paste" msgstr "Вставити" -#: mouse.cpp:762 +#: mouse.cpp:761 msgid "Paste Transformed..." msgstr "Вставити Трансформованим..." -#: mouse.cpp:767 +#: mouse.cpp:766 msgid "Delete" msgstr "Видалити" -#: mouse.cpp:770 +#: mouse.cpp:769 msgid "Unselect All" msgstr "Зняти Виділення з Усього" -#: mouse.cpp:777 +#: mouse.cpp:776 msgid "Unselect Hovered" msgstr "Зняти Виділення з Наведеного" -#: mouse.cpp:786 +#: mouse.cpp:785 msgid "Zoom to Fit" msgstr "Умістити на Екрані" -#: mouse.cpp:988 mouse.cpp:1275 +#: mouse.cpp:987 mouse.cpp:1276 msgid "click next point of line, or press Esc" msgstr "клікніть наступну точку лінії або натисніть Esc" -#: mouse.cpp:994 +#: mouse.cpp:993 msgid "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" +"Неможливо накреслити прямокутник у 3d; спочатку активуйте робочу площину в " +"Креслення -> У Робочій Площині." -#: mouse.cpp:1028 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" msgstr "клікніть для встановлення іншого кута прямокутника" @@ -1385,6 +1665,8 @@ msgid "" "Can't draw arc in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" +"Неможливо накреслити дугу у 3d; спочатку активуйте робочу площину в " +"Креслення -> У Робочій Площині." #: mouse.cpp:1072 msgid "click to place point" @@ -1398,12 +1680,16 @@ msgstr "клікніть наступну точку кривої або нат msgid "" "Sketching in a workplane already; sketch in 3d before creating new workplane." msgstr "" +"Вже обрано креслення у робочій площині; поверніться у креслення будь-ду в 3D " +"для створення нової робочої площини." #: mouse.cpp:1109 msgid "" "Can't draw text in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" +"Неможливо накреслити текст у 3д; спочатку активуйте робочу площину в " +"Креслення -> У Робочій Площині." #: mouse.cpp:1126 msgid "click to place bottom right of text" @@ -1414,220 +1700,232 @@ msgid "" "Can't draw image in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" +"Неможливо накреслити зображення у 3д; спочатку активуйте робочу площину в " +"Креслення -> У Робочій Площині." -#: mouse.cpp:1159 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" -msgstr "КОМЕНТАР -- ДВІЧІ КЛІКНІТЬ ДЛЯ РЕДАГУВАННЯ" - -#: platform/gui.cpp:85 platform/gui.cpp:89 solvespace.cpp:511 +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 msgctxt "file-type" msgid "SolveSpace models" -msgstr "SolveSpace модель" +msgstr "SolveSpace модел" + +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "УСІ" -#: platform/gui.cpp:90 +#: platform/gui.cpp:91 msgctxt "file-type" msgid "IDF circuit board" msgstr "IDF друкована плата" -#: platform/gui.cpp:94 +#: platform/gui.cpp:92 +msgctxt "file-type" +msgid "STL triangle mesh" +msgstr "STL трикутникова сітка" + +#: platform/gui.cpp:96 msgctxt "file-type" msgid "PNG image" msgstr "PNG зображення" -#: platform/gui.cpp:98 +#: platform/gui.cpp:100 msgctxt "file-type" msgid "STL mesh" -msgstr "STL меш" +msgstr "STL сітка" -#: platform/gui.cpp:99 +#: platform/gui.cpp:101 msgctxt "file-type" msgid "Wavefront OBJ mesh" msgstr "Wavefront OBJ меш" -#: platform/gui.cpp:100 +#: platform/gui.cpp:102 msgctxt "file-type" msgid "Three.js-compatible mesh, with viewer" msgstr "Three.js-сумісний меш, з переглядачем" -#: platform/gui.cpp:101 +#: platform/gui.cpp:103 msgctxt "file-type" msgid "Three.js-compatible mesh, mesh only" msgstr "Three.js-сумісний меш, лише меш" -#: platform/gui.cpp:102 +#: platform/gui.cpp:104 msgctxt "file-type" msgid "VRML text file" msgstr "VRML меш, текстовий формат" -#: platform/gui.cpp:106 platform/gui.cpp:113 platform/gui.cpp:120 +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 msgctxt "file-type" msgid "STEP file" msgstr "STEP файл" -#: platform/gui.cpp:110 +#: platform/gui.cpp:112 msgctxt "file-type" msgid "PDF file" msgstr "PDF файл" -#: platform/gui.cpp:111 +#: platform/gui.cpp:113 msgctxt "file-type" msgid "Encapsulated PostScript" msgstr "Encapsulated PostScript" -#: platform/gui.cpp:112 +#: platform/gui.cpp:114 msgctxt "file-type" msgid "Scalable Vector Graphics" msgstr "Scalable Vector Graphics, векторний формат" -#: platform/gui.cpp:114 platform/gui.cpp:121 +#: platform/gui.cpp:116 platform/gui.cpp:123 msgctxt "file-type" msgid "DXF file (AutoCAD 2007)" msgstr "DXF файл (AutoCAD 2007)" -#: platform/gui.cpp:115 +#: platform/gui.cpp:117 msgctxt "file-type" msgid "HPGL file" msgstr "HPGL файл" -#: platform/gui.cpp:116 +#: platform/gui.cpp:118 msgctxt "file-type" msgid "G Code" msgstr "G Code" -#: platform/gui.cpp:125 +#: platform/gui.cpp:127 msgctxt "file-type" msgid "AutoCAD DXF and DWG files" msgstr "AutoCAD DXF та DWG файли" -#: platform/gui.cpp:129 +#: platform/gui.cpp:131 msgctxt "file-type" msgid "Comma-separated values" msgstr "Comma-separated values" -#: platform/guigtk.cpp:1324 platform/guimac.mm:1363 platform/guiwin.cpp:1639 +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" msgstr "без імені" -#: platform/guigtk.cpp:1335 platform/guigtk.cpp:1368 platform/guimac.mm:1321 -#: platform/guiwin.cpp:1582 +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 msgctxt "title" msgid "Save File" msgstr "Зберегти Файл" -#: platform/guigtk.cpp:1336 platform/guigtk.cpp:1369 platform/guimac.mm:1304 -#: platform/guiwin.cpp:1584 +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 msgctxt "title" msgid "Open File" msgstr "Відкрити Файл" -#: platform/guigtk.cpp:1339 platform/guigtk.cpp:1375 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" msgid "_Cancel" msgstr "_Скасувати" -#: platform/guigtk.cpp:1340 platform/guigtk.cpp:1373 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" msgid "_Save" msgstr "_Зберегти" -#: platform/guigtk.cpp:1341 platform/guigtk.cpp:1374 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" msgid "_Open" msgstr "_Відкрити" -#: solvespace.cpp:169 +#: solvespace.cpp:175 msgctxt "title" msgid "Autosave Available" msgstr "Наявні автозбереження" -#: solvespace.cpp:170 +#: solvespace.cpp:176 msgctxt "dialog" msgid "An autosave file is available for this sketch." msgstr "Наявні автозбереження для цього креслення." -#: solvespace.cpp:171 +#: solvespace.cpp:177 msgctxt "dialog" msgid "Do you want to load the autosave file instead?" msgstr "Завантажити файл автозбереження?" -#: solvespace.cpp:172 +#: solvespace.cpp:178 msgctxt "button" msgid "&Load autosave" msgstr "&Завантажити автозбереження" -#: solvespace.cpp:174 +#: solvespace.cpp:180 msgctxt "button" msgid "Do&n't Load" msgstr "&Не Завантажувати" -#: solvespace.cpp:557 +#: solvespace.cpp:640 msgctxt "title" msgid "Modified File" msgstr "Файл Змінено" -#: solvespace.cpp:559 +#: solvespace.cpp:642 #, c-format msgctxt "dialog" msgid "Do you want to save the changes you made to the sketch “%s”?" msgstr "Чи хочете ви зберегти зміни зроблені вами у ескізі “%s”?" -#: solvespace.cpp:562 +#: solvespace.cpp:645 msgctxt "dialog" msgid "Do you want to save the changes you made to the new sketch?" msgstr "Чи хочете ви зберегти зміни зроблені вами у новому ескізі?" -#: solvespace.cpp:565 +#: solvespace.cpp:648 msgctxt "dialog" msgid "Your changes will be lost if you don't save them." msgstr "Ваші зміни буде втрачено якщо ви не збережете їх." -#: solvespace.cpp:566 +#: solvespace.cpp:649 msgctxt "button" msgid "&Save" msgstr "&Зберегти" -#: solvespace.cpp:568 +#: solvespace.cpp:651 msgctxt "button" msgid "Do&n't Save" msgstr "&Не Зберігати" -#: solvespace.cpp:589 +#: solvespace.cpp:672 msgctxt "title" msgid "(new sketch)" msgstr "(нове креслення)" -#: solvespace.cpp:596 +#: solvespace.cpp:683 msgctxt "title" msgid "Property Browser" msgstr "Браузер Властивостей" -#: solvespace.cpp:658 +#: solvespace.cpp:746 msgid "" "Constraints are currently shown, and will be exported in the toolpath. This " "is probably not what you want; hide them by clicking the link at the top of " "the text window." msgstr "" +"Зараз обмеження відображаються, і будуть експортовані в шлях інструменту. " +"Скоріш за все, це не те, чого ви домагаєтеся; cховайте їх клікнувши на " +"посилання над текстовим вікном." -#: solvespace.cpp:730 +#: solvespace.cpp:834 #, c-format msgid "" -"Can't identify file type from file extension of filename '%s'; try .dxf or ." -"dwg." +"Can't identify file type from file extension of filename '%s'; try .dxf " +"or .dwg." msgstr "" +"Неможливо визначити тип файлу з розширення '%s'; спробуйте .dxf чи .dwg." -#: solvespace.cpp:778 +#: solvespace.cpp:886 msgid "Constraint must have a label, and must not be a reference dimension." msgstr "Обмежувач має містити мітку і бути не відносним розміром." -#: solvespace.cpp:782 +#: solvespace.cpp:890 msgid "Bad selection for step dimension; select a constraint." msgstr "Поганий вибір для крокової зміни розміру; оберіть обмежувач." -#: solvespace.cpp:806 +#: solvespace.cpp:914 msgid "The assembly does not interfere, good." -msgstr "" +msgstr "Збірка не перетинається, очман." -#: solvespace.cpp:822 +#: solvespace.cpp:930 #, c-format msgid "" "The volume of the solid model is:\n" @@ -1638,7 +1936,7 @@ msgstr "" "\n" " %s" -#: solvespace.cpp:831 +#: solvespace.cpp:939 #, c-format msgid "" "\n" @@ -1651,15 +1949,19 @@ msgstr "" "\n" " %s" -#: solvespace.cpp:836 +#: solvespace.cpp:944 msgid "" "\n" "\n" "Curved surfaces have been approximated as triangles.\n" "This introduces error, typically of around 1%." msgstr "" +"\n" +"\n" +"Скруглені поверхні були апроксимовані до трикутників.\n" +"Це створює похибку, зазвичай біля 1%." -#: solvespace.cpp:851 +#: solvespace.cpp:959 #, c-format msgid "" "The surface area of the selected faces is:\n" @@ -1669,8 +1971,14 @@ msgid "" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%." msgstr "" +"Площа поверхні обраної грані:\n" +"\n" +" %s\n" +"\n" +"Криві були апроксимовані як дрібні відрізки.\n" +"Це створює похибку, зазвичай біля 1%%." -#: solvespace.cpp:860 +#: solvespace.cpp:968 msgid "" "This group does not contain a correctly-formed 2d closed area. It is open, " "not coplanar, or self-intersecting." @@ -1678,7 +1986,7 @@ msgstr "" "Ця група не місить коректно сформованого замкненої 2D площини. Вона " "відкрита, не компланарна, або ж самоперетинається." -#: solvespace.cpp:872 +#: solvespace.cpp:980 #, c-format msgid "" "The area of the region sketched in this group is:\n" @@ -1695,7 +2003,7 @@ msgstr "" "Криві наближено до ламаних ліній.\n" "Це вносить похибку, зазвичай близько 1%%." -#: solvespace.cpp:892 +#: solvespace.cpp:1000 #, c-format msgid "" "The total length of the selected entities is:\n" @@ -1712,44 +2020,47 @@ msgstr "" "Криві наближено до ламаних ліній.\n" "Це вносить похибку, зазвичай близько 1%%." -#: solvespace.cpp:898 +#: solvespace.cpp:1006 msgid "Bad selection for perimeter; select line segments, arcs, and curves." msgstr "Поганий вибір для периметру; оберіть відрізки, дуги та криві." -#: solvespace.cpp:914 +#: solvespace.cpp:1022 msgid "Bad selection for trace; select a single point." msgstr "Поганий вибір для вістежування шляху; оберіть одну точку." -#: solvespace.cpp:941 +#: solvespace.cpp:1049 #, c-format msgid "Couldn't write to '%s'" msgstr "Неможливо записати у '%s'" -#: solvespace.cpp:971 +#: solvespace.cpp:1079 msgid "The mesh is self-intersecting (NOT okay, invalid)." msgstr "Меш самоперетинається (НЕ добре, недійсний)." -#: solvespace.cpp:972 +#: solvespace.cpp:1080 msgid "The mesh is not self-intersecting (okay, valid)." msgstr "Меш самоперетинається (добре, дійсний)." -#: solvespace.cpp:974 +#: solvespace.cpp:1082 msgid "The mesh has naked edges (NOT okay, invalid)." msgstr "Меш містить оголені ребра (НЕ добре, недійсний)." -#: solvespace.cpp:975 +#: solvespace.cpp:1083 msgid "The mesh is watertight (okay, valid)." msgstr "Меш водонепроникний (добре, дійсний)." -#: solvespace.cpp:978 +#: solvespace.cpp:1086 #, c-format msgid "" "\n" "\n" "The model contains %d triangles, from %d surfaces." msgstr "" +"\n" +"\n" +"Модель складається з %d трикутників, що розташовані на %d поверхнях." -#: solvespace.cpp:982 +#: solvespace.cpp:1090 #, c-format msgid "" "%s\n" @@ -1764,7 +2075,7 @@ msgstr "" "\n" "Відсутні проблемні ребра, добре.%s" -#: solvespace.cpp:985 +#: solvespace.cpp:1093 #, c-format msgid "" "%s\n" @@ -1779,7 +2090,7 @@ msgstr "" "\n" "%d проблемних ребер, погано.%s" -#: solvespace.cpp:998 +#: solvespace.cpp:1106 #, c-format msgid "" "This is SolveSpace version %s.\n" @@ -1808,7 +2119,7 @@ msgstr "" "\n" "© 2008-%d Jonathan Westhues та інші автори.\n" -#: style.cpp:166 +#: style.cpp:185 msgid "" "Can't assign style to an entity that's derived from another entity; try " "assigning a style to this entity's parent." @@ -1816,27 +2127,27 @@ msgstr "" "Неможливо призначити стиль елементу який походить від іншого елемента; " "спробуйте призначити стиль батьківському елементу." -#: style.cpp:665 +#: style.cpp:735 msgid "Style name cannot be empty" msgstr "Стиль не може містити порожнє ім'я" -#: textscreens.cpp:741 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." msgstr "Не можливо повторити менше 1 разу." -#: textscreens.cpp:745 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." msgstr "Не можливо повторити понад 999 разів." -#: textscreens.cpp:770 +#: textscreens.cpp:866 msgid "Group name cannot be empty" msgstr "Група не може містити порожнє ім'я" -#: textscreens.cpp:813 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." msgstr "Непрозорість має бути між 0 та 1." -#: textscreens.cpp:848 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "Радіус не може бути нульовим чи від'ємним." @@ -1991,14 +2302,26 @@ msgctxt "button" msgid "&OK" msgstr "&OK" -#: view.cpp:78 +#: view.cpp:127 msgid "Scale cannot be zero or negative." msgstr "Масштаб не може бути нульовим чи від'ємним." -#: view.cpp:90 view.cpp:99 +#: view.cpp:139 view.cpp:148 msgid "Bad format: specify x, y, z" msgstr "Некоректний формат: визначте X, Y, Z" +#~ msgid "A&ngle" +#~ msgstr "К&ут" + +#~ msgid "E&qual Length / Radius / Angle" +#~ msgstr "Рі&вні Довжина / Радіус / Кут" + +#~ msgid "Length Ra&tio" +#~ msgstr "Про&порція Довжин" + +#~ msgid "Length Diff&erence" +#~ msgstr "Рі&зниця Довжин" + #~ msgid "Show Degrees of &Freedom" #~ msgstr "Показати Степені &Свободи" diff --git a/res/locales/zh_CN.po b/res/locales/zh_CN.po index 6cc88cdde..2a992b463 100644 --- a/res/locales/zh_CN.po +++ b/res/locales/zh_CN.po @@ -2,14 +2,27 @@ # Copyright (C) 2020 the PACKAGE authors # This file is distributed under the same license as the SolveSpace package. # , 2020. -# -msgid "" -msgstr "" -"Project-Id-Version: SolveSpace 3.0\n" -"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" -"POT-Creation-Date: 2021-02-01 15:45+0200\n" -"PO-Revision-Date: 2021-04-03 13:10-0400\n" -"Last-Translator: lomatus@163.com\n" +# , 2023. +# , 2025. +# +# 译词一致性 +# mesh网格 grid格线 +# text文本 comment备注 +# vertical竖直 perpendicular垂直 +# Rotate旋转 Lathe转圈 Revolve扫略 +# solid实心体 object对象 entity物件 +# plane点线面/平面 face/surface表面 plane_faces平表面 +# +# 约束的选择错误。此约束可用于:A与(B和C) +# 一点/工作面 一条线段 一个表面 一个以上 +# +msgid "" +msgstr "" +"Project-Id-Version: SolveSpace 3.2\n" +"Report-Msgid-Bugs-To: phkahler@gmail.com\n" +"POT-Creation-Date: 2025-01-26 21:04+0200\n" +"PO-Revision-Date: 2023-04-30 15:58+0800\n" +"Last-Translator: liuxilu@live.com\n" "Language-Team: none\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" @@ -17,139 +30,139 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.4.2\n" -#: clipboard.cpp:310 +#: clipboard.cpp:314 msgid "" "Cut, paste, and copy work only in a workplane.\n" "\n" "Activate one with Sketch -> In Workplane." msgstr "" -"仅在工作平面中剪切、粘贴和复制工作。\n" +"剪切、粘贴、复制在工作面内才可用。\n" "\n" -"使用\"工作平面中的草图 -+\"激活一个。" +"用\"绘图 -> 在工作面内\"来激活一个。" -#: clipboard.cpp:327 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." -msgstr "剪贴板为空;没有要粘贴的内容。" +msgstr "剪贴板为空;没有能粘贴的。" -#: clipboard.cpp:374 +#: clipboard.cpp:378 msgid "Number of copies to paste must be at least one." -msgstr "要粘贴的副本数必须至少为 1 个。" +msgstr "至少要粘贴 1 个副本。" -#: clipboard.cpp:390 textscreens.cpp:783 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "缩放不能为零。" -#: clipboard.cpp:432 +#: clipboard.cpp:436 msgid "Select one point to define origin of rotation." -msgstr "选择一个点以定义旋转原点。" +msgstr "选择一个点来定义旋转中心。" -#: clipboard.cpp:444 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." -msgstr "选择两个点来定义转换向量。" +msgstr "选择两个点来定义平移向量。" -#: clipboard.cpp:454 +#: clipboard.cpp:458 msgid "" "Transformation is identity. So all copies will be exactly on top of each " "other." -msgstr "转换就是标识,因此所有的复制在彼此之上。" +msgstr "输入的变换是不动,因此所有副本将互相重叠。" -#: clipboard.cpp:458 +#: clipboard.cpp:462 msgid "Too many items to paste; split this into smaller pastes." msgstr "要粘贴的项目太多; 请把他们拆分。" -#: clipboard.cpp:463 +#: clipboard.cpp:467 msgid "No workplane active." -msgstr "没有工作平面处于活动状态。" +msgstr "无活动工作面。" -#: confscreen.cpp:418 +#: confscreen.cpp:410 msgid "Bad format: specify coordinates as x, y, z" -msgstr "格式错误:将坐标指定为 x、y、z" +msgstr "格式错误:请用 x,y,z 指定坐标" -#: confscreen.cpp:428 style.cpp:659 textscreens.cpp:805 +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 msgid "Bad format: specify color as r, g, b" -msgstr "格式错误:将颜色指定为 r、g、b" +msgstr "格式错误:请用 r,g,b 指定颜色" -#: confscreen.cpp:454 +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." -msgstr "在启用\"视图 -= 使用透视投影\"之前,透视因子将不起作用。" +msgstr "若不启用\"查看 -> 使用透视投影\",透视因数将不起作用。" -#: confscreen.cpp:467 confscreen.cpp:477 +#: confscreen.cpp:464 confscreen.cpp:474 #, c-format msgid "Specify between 0 and %d digits after the decimal." -msgstr "在十进制之后指定 0 和 %d 数字之间。" +msgstr "请指定小数点后 0 到 %d 位。" -#: confscreen.cpp:489 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" -msgstr "输出比例不能为零!" +msgstr "导出比例不能为零!" -#: confscreen.cpp:501 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" -msgstr "刀具半径偏移不能为负数!" +msgstr "刀具半径偏移不能为负!" -#: confscreen.cpp:555 +#: confscreen.cpp:557 msgid "Bad value: autosave interval should be positive" -msgstr "坏值:自动保存间隔应为正" +msgstr "值错误:自动保存间隔应为正" -#: confscreen.cpp:558 +#: confscreen.cpp:560 msgid "Bad format: specify interval in integral minutes" -msgstr "格式错误:以整数分钟为单位指定间隔" +msgstr "格式错误:间隔应为整分钟" #: constraint.cpp:12 msgctxt "constr-name" msgid "pts-coincident" -msgstr "点约束" +msgstr "点点重合" #: constraint.cpp:13 msgctxt "constr-name" msgid "pt-pt-distance" -msgstr "点点间距" +msgstr "点点距离" #: constraint.cpp:14 msgctxt "constr-name" msgid "pt-line-distance" -msgstr "线长" +msgstr "点线距离" #: constraint.cpp:15 msgctxt "constr-name" msgid "pt-plane-distance" -msgstr "平面具体" +msgstr "点面距离" #: constraint.cpp:16 msgctxt "constr-name" msgid "pt-face-distance" -msgstr "面间距" +msgstr "点面距离" #: constraint.cpp:17 msgctxt "constr-name" msgid "proj-pt-pt-distance" -msgstr "点点距离" +msgstr "点点投影距离" #: constraint.cpp:18 msgctxt "constr-name" msgid "pt-in-plane" -msgstr "点在面" +msgstr "点在面上" #: constraint.cpp:19 msgctxt "constr-name" msgid "pt-on-line" -msgstr "点在线" +msgstr "点在直线上" #: constraint.cpp:20 msgctxt "constr-name" msgid "pt-on-face" -msgstr "点在面" +msgstr "点在表面上" #: constraint.cpp:21 msgctxt "constr-name" msgid "eq-length" -msgstr "长度相同" +msgstr "线长相等" #: constraint.cpp:22 msgctxt "constr-name" msgid "eq-length-and-pt-ln-dist" -msgstr "长度相等且点在距离" +msgstr "线长与点线距离相等" #: constraint.cpp:23 msgctxt "constr-name" @@ -159,138 +172,213 @@ msgstr "点线距离相等" #: constraint.cpp:24 msgctxt "constr-name" msgid "length-ratio" -msgstr "长度比率" +msgstr "线长比例" #: constraint.cpp:25 msgctxt "constr-name" -msgid "length-difference" -msgstr "长度不同" +msgid "arc-arc-length-ratio" +msgstr "弧长比例" #: constraint.cpp:26 msgctxt "constr-name" -msgid "symmetric" -msgstr "对称的" +msgid "arc-line-length-ratio" +msgstr "弧长与线长比例" #: constraint.cpp:27 msgctxt "constr-name" +msgid "length-difference" +msgstr "线长之差" + +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "arc-arc-len-difference" +msgstr "弧长之差" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "arc-line-len-difference" +msgstr "弧长与线长之差" + +#: constraint.cpp:30 +msgctxt "constr-name" +msgid "symmetric" +msgstr "对称" + +#: constraint.cpp:31 +msgctxt "constr-name" msgid "symmetric-h" msgstr "水平对称" -#: constraint.cpp:28 +#: constraint.cpp:32 msgctxt "constr-name" msgid "symmetric-v" -msgstr "纵向对称" +msgstr "竖直对称" -#: constraint.cpp:29 +#: constraint.cpp:33 msgctxt "constr-name" msgid "symmetric-line" -msgstr "线对称" +msgstr "关于直线对称" -#: constraint.cpp:30 +#: constraint.cpp:34 msgctxt "constr-name" msgid "at-midpoint" -msgstr "在中点" +msgstr "中点" -#: constraint.cpp:31 +#: constraint.cpp:35 msgctxt "constr-name" msgid "horizontal" -msgstr "水平约束" +msgstr "水平" -#: constraint.cpp:32 +#: constraint.cpp:36 msgctxt "constr-name" msgid "vertical" -msgstr "垂直约束" +msgstr "竖直" -#: constraint.cpp:33 +#: constraint.cpp:37 msgctxt "constr-name" msgid "diameter" -msgstr "直径约束" +msgstr "直径" -#: constraint.cpp:34 +#: constraint.cpp:38 msgctxt "constr-name" msgid "pt-on-circle" -msgstr "圆点约束" +msgstr "点在圆上" -#: constraint.cpp:35 +#: constraint.cpp:39 msgctxt "constr-name" msgid "same-orientation" -msgstr "相同原点" +msgstr "同向" -#: constraint.cpp:36 +#: constraint.cpp:40 msgctxt "constr-name" msgid "angle" -msgstr "角度约束" +msgstr "角度" -#: constraint.cpp:37 +#: constraint.cpp:41 msgctxt "constr-name" msgid "parallel" -msgstr "平行约束" +msgstr "平行" -#: constraint.cpp:38 +#: constraint.cpp:42 msgctxt "constr-name" msgid "arc-line-tangent" -msgstr "弧切线" +msgstr "弧和线相切" -#: constraint.cpp:39 +#: constraint.cpp:43 msgctxt "constr-name" msgid "cubic-line-tangent" -msgstr "立方体切线" +msgstr "三次曲线和直线相切" -#: constraint.cpp:40 +#: constraint.cpp:44 msgctxt "constr-name" msgid "curve-curve-tangent" -msgstr "曲线间切线" +msgstr "曲线相切" -#: constraint.cpp:41 +#: constraint.cpp:45 msgctxt "constr-name" msgid "perpendicular" -msgstr "垂直约束" +msgstr "垂直" -#: constraint.cpp:42 +#: constraint.cpp:46 msgctxt "constr-name" msgid "eq-radius" -msgstr "等于半径" +msgstr "半径相等" -#: constraint.cpp:43 +#: constraint.cpp:47 msgctxt "constr-name" msgid "eq-angle" -msgstr "等于角度" +msgstr "角度相等" -#: constraint.cpp:44 +#: constraint.cpp:48 msgctxt "constr-name" msgid "eq-line-len-arc-len" -msgstr "等于线长或弧长" +msgstr "线长和弧长相等" -#: constraint.cpp:45 +#: constraint.cpp:49 msgctxt "constr-name" msgid "lock-where-dragged" -msgstr "锁定位置" +msgstr "定位后锁定" -#: constraint.cpp:46 +#: constraint.cpp:50 msgctxt "constr-name" msgid "comment" msgstr "备注" -#: constraint.cpp:140 +#: constraint.cpp:151 msgid "" -"The tangent arc and line segment must share an endpoint. Constrain them with " -"Constrain -> On Point before constraining tangent." -msgstr "切线弧和线段必须共享一个端点。在约束切线之前,使用约束 -= 点约束它们。" +"The point you selected does not belong to the arc. The arc and line segment " +"do not share an end point.\n" +"\n" +"Select the end point of the arc at which you want it to be tangent to the " +"line." +msgstr "" +"您选择的点不属于弧线, 弧线与线段不共享端点\n" +"\n" +"在样条曲线上选择一个您想要与目标线相切的端点." #: constraint.cpp:158 msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them " -"with Constrain -> On Point before constraining tangent." +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be " +"tangent to the line." +msgstr "" +"要相切的弧和线段必须有共同端点。约束相切之前,用\"约束 -> 在点上\"来约束它" +"们。\n" +"\n" +"此外, 可以在曲线上选择一个您想要与目标线相切的端点." + +#: constraint.cpp:186 +msgid "" +"The point you selected is not an end point of the cubic spline. The spline " +"and line segment do not share an end point.\n" +"\n" +"Select the end point of the spline at which you want it to be tangent to the " +"line." +msgstr "" +"您选中的点不是三次样条曲线端点. 样条线和线段并不共享端点.\n" +"\n" +"在样条曲线上选择一个您想要与目标线相切的端点." + +#: constraint.cpp:193 +msgid "" +"The tangent cubic spline and line segment must share an endpoint. Constrain " +"them with Constrain -> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it " +"to be tangent to the line." +msgstr "" +"要相切的三次曲线段和线段必须有共同端点。约束相切之前,使用\"约束 -> 在点上" +"\"来约束它们。\n" +"\n" +"此外, 可以在三次样条曲线上选择一个您想要与目标线相切的端点." + +#: constraint.cpp:237 +msgid "" +"The points you selected are not end points of the two curves. The curves do " +"not share an end point.\n" +"\n" +"Select the end points of both curves at which you want them to be tangent to " +"each other." msgstr "" -"切线立方段和线段必须共享终结点。在约束切线之前,使用约束 -= 点约束它们。" +"您选择的端点不属于二个曲线, 曲线并不共有一个端点.\n" +"\n" +"在二个曲线上选择一个您想要二者相切的共有端点." -#: constraint.cpp:183 +#: constraint.cpp:244 msgid "" "The curves must share an endpoint. Constrain them with Constrain -> On Point " -"before constraining tangent." -msgstr "曲线必须共享一个终结点。在约束切线之前,使用约束 -= 点约束它们。" +"before constraining tangent.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." +msgstr "" +"要约束的曲线必须有共同端点。约束相切之前,使用\"约束 -> 在点上\"来约束它们。\n" +"\n" +"此外, 可以在二个曲线上选择相切的端点." -#: constraint.cpp:231 +#: constraint.cpp:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply " "to:\n" @@ -303,95 +391,99 @@ msgid "" " * a plane face and a point (minimum distance)\n" " * a circle or an arc (diameter)\n" msgstr "" -"距离/直径约束的选择错误。此约束可应用于:\n" +"距离/直径约束的选择错误。此约束可用于:\n" "\n" -"* 两点(点之间的距离)\n" +" * 两点(点距)\n" " * 线段(长度)\n" -" * 两个点和线段或法线(投影距离)\n" -" * 工作平面和点(最小距离)\n" -" * 线段和点(最小距离)\n" -" * 平面面和点(最小距离)\n" -" * 圆或弧(直径)\n" +" * 两点与一条线段或法线(投影距离)\n" +" * 一点与一工作面(最小距离)\n" +" * 一点与一条线段(最小距离)\n" +" * 一点与一平表面(最小距离)\n" +" * 一个圆或一条弧(直径)\n" -#: constraint.cpp:284 +#: constraint.cpp:366 msgid "" "Bad selection for on point / curve / plane constraint. This constraint can " "apply to:\n" "\n" -" * two points (points coincident)\n" +" * two or more points (points coincident)\n" " * a point and a workplane (point in plane)\n" " * a point and a line segment (point on line)\n" " * a point and a circle or arc (point on curve)\n" -" * a point and a plane face (point on face)\n" +" * a point and one to three plane faces (point on face(s))\n" msgstr "" -"点 / 曲线 / 平面约束的选定方法错误。此约束可应用于:\n" +"在点/线/面上约束的选择错误。此约束可用于:\n" "\n" -"* 两点(点重合)\n" -" * 一个点和一个工作平面(平面中点)\n" -" * 点和线段(点在线)\n" -" * 一个点和一个圆或圆(曲线上的点)\n" -" * 点和平面面(点在脸上)\n" +" * 两个以上的点(点点重合)\n" +" * 一点与一平面(点在面上)\n" +" * 一点与一条线段(点在直线上)\n" +" * 一点与一个圆或弧(点在曲线上)\n" +" * 一点与一到三个平表面(点在表面上)\n" -#: constraint.cpp:346 +#: constraint.cpp:427 msgid "" "Bad selection for equal length / radius constraint. This constraint can " "apply to:\n" "\n" -" * two line segments (equal length)\n" +" * two or more line segments (equal length)\n" " * two line segments and two points (equal point-line distances)\n" " * a line segment and two points (equal point-line distances)\n" " * a line segment, and a point and line segment (point-line distance " "equals length)\n" -" * four line segments or normals (equal angle between A,B and C,D)\n" -" * three line segments or normals (equal angle between A,B and B,C)\n" -" * two circles or arcs (equal radius)\n" +" * two or more circles or arcs (equal radius)\n" " * a line segment and an arc (line segment length equals arc length)\n" msgstr "" -"等长度/半径约束的选定方法错误。此约束可应用于:\n" +"长度/半径相等约束的选择错误。此约束可用于:\n" "\n" -"* 两个线段(相等长度)\n" -" * 两个线段和两个点(相等的点线距离)\n" -" * 线段和两个点(相等的点线距离)\n" -" * 线段和点段和线段(点线距离等于长度)\n" -" * 四条线段或法线(A、B 和 C、D 之间的等角)\n" -" * 三条线段或法线(A、B 和 B、C 之间的等角)\n" -" * 两个圆或圆(相等半径)\n" -" * 线段和圆弧(线段长度等于弧长)\n" +" * 两条以上线段(长度相等)\n" +" * 两条线段与两点(点线距离相等)\n" +" * 一条线段与两点(点线距离相等)\n" +" * 一条线段与一点和线段(点线距离等于长度)\n" +" * 一条线段和一条弧(线长等于弧长)\n" +" * 两个以上圆或弧(半径相等)\n" -#: constraint.cpp:385 +#: constraint.cpp:480 msgid "" "Bad selection for length ratio constraint. This constraint can apply to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" -"长度比率约束的选择错误。此约束可应用于:\n" +"长度比例约束的选择错误。此约束可用于:\n" "\n" -"* 两个线段\n" +" * 两条线段\n" +" * 两条弧线\n" +" * 一条弧线与一条线段\n" -#: constraint.cpp:402 +#: constraint.cpp:515 msgid "" "Bad selection for length difference constraint. This constraint can apply " "to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" -"长度差异约束的选择错误。此约束可应用于:\n" +"长度差约束选择错误。此约束可用于:\n" "\n" -"* 两个线段\n" +" * 两条线段\n" +" * 两条弧线\n" +" * 一条弧线与一条线段\n" -#: constraint.cpp:428 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" " * a line segment and a point (point at midpoint)\n" " * a line segment and a workplane (line's midpoint on plane)\n" msgstr "" -"中点约束的选定方法错误。此约束可应用于:\n" +"中点约束的选择错误。此约束可用于:\n" "\n" -"* 线段和点(点在中点)\n" -" * 线段和工作平面(平面上的线中点)\n" +" * 一条线段和一点(点在线段中点上)\n" +" * 一条线段和一工作面(线段中点在平面上)\n" -#: constraint.cpp:486 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -402,126 +494,147 @@ msgid "" " * workplane, and two points or a line segment (symmetric about " "workplane)\n" msgstr "" -"对称约束的选择错误。此约束可应用于:\n" +"对称约束的选择错误。此约束可用于:\n" "\n" -"* 两个点或线段(与工作平面的坐标轴对称)\n" -" * 线段,和两个点或线段(对称的线段)\n" -" * 工作平面和两个点或线段(工作平面对称)\n" +" * 两点或一条线段(关于工作面坐标轴对称)\n" +" * 一条线段与两点或一条线段(关于直线对称)\n" +" * 一工作面和两点或一条线段(关于工作面对称)\n" -#: constraint.cpp:500 +#: constraint.cpp:623 msgid "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." -msgstr "在没有显式对称平面约束对称时,工作平面必须处于活动状态。" +msgstr "没有明显的对称面时,约束对称必须有活动工作面。" -#: constraint.cpp:530 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." -msgstr "在应用水平或垂直约束之前,激活工作平面(使用草图 -= 在工作平面中)。" +msgstr "使用水平或竖直约束之前应激活工作面(用\"绘图 -> 在工作面内\")。" -#: constraint.cpp:543 +#: constraint.cpp:679 msgid "" "Bad selection for horizontal / vertical constraint. This constraint can " "apply to:\n" "\n" -" * two points\n" -" * a line segment\n" +" * two or more points\n" +" * one or more line segments\n" msgstr "" -"水平/垂直约束的选择错误。此约束可应用于:\n" +"水平/竖直约束的选择错误。此约束可用于:\n" "\n" -"• 两点\n" -" • 线段\n" +" * 两个以上的点\n" +" * 一条以上的线段\n" -#: constraint.cpp:564 +#: constraint.cpp:697 msgid "" "Bad selection for same orientation constraint. This constraint can apply " "to:\n" "\n" " * two normals\n" msgstr "" -"同一方向约束的选择错误。此约束可应用于:\n" +"同向约束的选择错误。此约束可用于:\n" "\n" -"• 两个法线\n" +" * 两条法线\n" -#: constraint.cpp:614 +#: constraint.cpp:748 msgid "Must select an angle constraint." msgstr "必须选择角度约束。" -#: constraint.cpp:627 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." -msgstr "必须选择具有关联标签的约束。" +msgstr "必须选择有尺寸的约束。" -#: constraint.cpp:638 +#: constraint.cpp:784 msgid "" "Bad selection for angle constraint. This constraint can apply to:\n" "\n" +"Angle between:\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" +"\n" +"Equal angles:\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" msgstr "" -"角度约束的选择错误。此约束可应用于:\n" +"角度约束的选择错误。此约束可用于:\n" "\n" -"* 两个线段\n" -" * 线段和法线\n" -" • 两个法线\n" - -#: constraint.cpp:701 +" * 两条线段\n" +" * 两条法线\n" +" * 一条线段和一条法线\n" +"之间的角度\n" +" * 四条线段或法线(AB之间与CD之间角度相等)\n" +" * 三条线段或法线(AB之间与BC之间角度相等)\n" +"之间的角度相等" + +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." -msgstr "曲线曲线切线必须应用于工作平面。" +msgstr "曲线相切必须在工作面内应用。" -#: constraint.cpp:711 +#: constraint.cpp:887 msgid "" "Bad selection for parallel / tangent constraint. This constraint can apply " "to:\n" "\n" -" * two line segments (parallel)\n" -" * a line segment and a normal (parallel)\n" -" * two normals (parallel)\n" +" * two faces\n" +" * two or more line segments (parallel)\n" +" * one or more line segments and one or more normals (parallel)\n" +" * two or more normals (parallel)\n" " * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +" * two line segments, arcs, or beziers, that do not share an endpoint and " +"the end point(s) of the curve(s) (tangent)\n" msgstr "" -"平行/切线约束的选择错误。此约束可应用于:\n" +"平行/相切约束的选择错误。此约束可用于:\n" "\n" -"* 两条线段(平行)\n" -" * 线段和法线(平行)\n" -" * 两个法线(平行)\n" -" * 共享端点的两条线段、弧线或贝塞尔(切线)\n" +" * 两个表面\n" +" * 两条以上线段(平行)\n" +" * 两条以上法线(平行)\n" +" * 一条以上线段与一条以上法线(平行)\n" +" * 有共同端点的两条弧/线段/贝塞尔曲线(相切)\n" +" * 无共同端点的两条线段/曲线/贝塞尔曲线, 及曲线的端点不共有 (相切)\n" -#: constraint.cpp:729 +#: constraint.cpp:914 msgid "" "Bad selection for perpendicular constraint. This constraint can apply to:\n" "\n" +" * two faces\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" msgstr "" -"垂直约束的选择错误。此约束可应用于:\n" +"垂直约束的选择错误。此约束可用于:\n" "\n" -"* 两个线段\n" -" * 线段和法线\n" -" • 两个法线\n" +" * 两个表面\n" +" * 两条线段\n" +" * 两条法线\n" +" * 一条线段与一条法线\n" -#: constraint.cpp:744 +#: constraint.cpp:931 msgid "" "Bad selection for lock point where dragged constraint. This constraint can " "apply to:\n" "\n" " * a point\n" msgstr "" -"拖动约束的锁点选择错误。此约束可应用于:\n" +"定位后锁定点约束的选择错误。此约束可用于:\n" "\n" -"• 一点\n" +" * 一点\n" -#: constraint.cpp:755 +#: constraint.cpp:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "新备注——双击编辑" + +#: constraint.cpp:952 msgid "click center of comment text" -msgstr "单击注释文本的中心" +msgstr "单击备注文本的中心" #: export.cpp:19 msgid "" "No solid model present; draw one with extrudes and revolves, or use Export " "2d View to export bare lines and curves." msgstr "" -"不存在实体模型;使用拉伸和旋转绘制一个,或使用导出 2d 视图导出裸线和曲线。" +"不存在实心体模型;用拉伸和扫略来绘制,或使用\"导出2D视图\"导出光杆直线和曲" +"线。" #: export.cpp:61 msgid "" @@ -532,33 +645,33 @@ msgid "" " * a point and two line segments (plane through point and parallel to " "lines)\n" msgstr "" -"导出部分的选择错误。请选择:\n" +"导出截面的选择错误。请选择:\n" "\n" -"* 无,带活动工作平面(工作平面为剖面平面)\n" -" * 脸(通过面的剖面)\n" -" * 一个点和两个线段(平面穿过点和平行线)\n" +" * 空,有活动工作面时(工作面为截面)\n" +" * 一个表面(截面穿过表面)\n" +" * 一点与两条线段(截面穿过点并与线平行)\n" -#: export.cpp:822 +#: export.cpp:818 msgid "Active group mesh is empty; nothing to export." -msgstr "活动组网格为空;没有要导出的。" +msgstr "活动组网格为空;没有能导出的。" -#: exportvector.cpp:337 +#: exportvector.cpp:336 msgid "freehand lines were replaced with continuous lines" -msgstr "徒手线替换为连续线" +msgstr "手绘样式的线已替换为连续线" -#: exportvector.cpp:339 +#: exportvector.cpp:338 msgid "zigzag lines were replaced with continuous lines" -msgstr "锯齿线替换为连续线" +msgstr "锯齿样式的线已替换为连续线" -#: exportvector.cpp:593 +#: exportvector.cpp:592 msgid "" "Some aspects of the drawing have no DXF equivalent and were not exported:\n" -msgstr "绘图的某些方面没有 DXF 等效项,并且未导出:\n" +msgstr "图纸的某些方面没有DXF等效项,并且未导出:\n" -#: exportvector.cpp:839 +#: exportvector.cpp:838 msgid "" "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." -msgstr "PDF 页面大小超过 200 英寸或 200 英寸;许多查看器可能会拒绝此文件。" +msgstr "PDF页面大小超过200x200英寸;许多查看器可能会拒绝此文件。" #: file.cpp:44 group.cpp:91 msgctxt "group-name" @@ -570,47 +683,47 @@ msgctxt "group-name" msgid "#references" msgstr "#参考" -#: file.cpp:552 +#: file.cpp:555 msgid "The file is empty. It may be corrupt." -msgstr "" +msgstr "文件为空,可能已损坏。" -#: file.cpp:557 +#: file.cpp:560 msgid "" "Unrecognized data in file. This file may be corrupt, or from a newer version " "of the program." -msgstr "未识别文件内数据。该文件可能损坏,或使用新版本应用程序尝试打开。" +msgstr "文件内数据无法识别。该文件可能损坏,或来自新版本程序。" -#: file.cpp:867 +#: file.cpp:876 msgctxt "title" msgid "Missing File" msgstr "文件丢失" -#: file.cpp:868 +#: file.cpp:877 #, c-format msgctxt "dialog" msgid "The linked file “%s” is not present." -msgstr "连接的文件不存在:\"%s\"。" +msgstr "不存在连接的文件\"%s\"。" -#: file.cpp:870 +#: file.cpp:879 msgctxt "dialog" msgid "" "Do you want to locate it manually?\n" "\n" "If you decline, any geometry that depends on the missing file will be " "permanently removed." -msgstr "您是否想手工查找?如果是,所有基于该丢失文件的几何对象将会被全部删除." +msgstr "您是否想手动查找?如果拒绝,基于丢失文件的所有几何图形将被永久删除。" -#: file.cpp:873 +#: file.cpp:882 msgctxt "button" msgid "&Yes" msgstr "是(&Y)" -#: file.cpp:875 +#: file.cpp:884 msgctxt "button" msgid "&No" msgstr "否(&N)" -#: file.cpp:877 solvespace.cpp:569 +#: file.cpp:886 solvespace.cpp:652 msgctxt "button" msgid "&Cancel" msgstr "取消(&C)" @@ -625,11 +738,11 @@ msgstr "新建(&N)" #: graphicswin.cpp:43 msgid "&Open..." -msgstr "打开(&O)" +msgstr "打开...(&O)" #: graphicswin.cpp:44 msgid "Open &Recent" -msgstr "打开最近使用(&R)" +msgstr "打开最近文件(&R)" #: graphicswin.cpp:45 msgid "&Save" @@ -637,35 +750,35 @@ msgstr "保存(&S)" #: graphicswin.cpp:46 msgid "Save &As..." -msgstr "另存为(&A)" +msgstr "另存为...(&A)" #: graphicswin.cpp:48 msgid "Export &Image..." -msgstr "导出图片(&I)" +msgstr "导出图片...(&I)" #: graphicswin.cpp:49 msgid "Export 2d &View..." -msgstr "导出2D视图(&V)" +msgstr "导出2D视图...(&V)" #: graphicswin.cpp:50 msgid "Export 2d &Section..." -msgstr "导出2D截面(&S)" +msgstr "导出2D截面...(&S)" #: graphicswin.cpp:51 msgid "Export 3d &Wireframe..." -msgstr "导出3D线框模型(&W)" +msgstr "导出3D线框...(&W)" #: graphicswin.cpp:52 msgid "Export Triangle &Mesh..." -msgstr "导出三角面模型(&M)" +msgstr "导出三角网格...(&M)" #: graphicswin.cpp:53 msgid "Export &Surfaces..." -msgstr "导出表面模型(&S)" +msgstr "导出表面...(&S)" #: graphicswin.cpp:54 msgid "Im&port..." -msgstr "导入(&P)" +msgstr "导入...(&P)" #: graphicswin.cpp:57 msgid "E&xit" @@ -677,7 +790,7 @@ msgstr "编辑(&E)" #: graphicswin.cpp:61 msgid "&Undo" -msgstr "回退(&U)" +msgstr "撤销(&U)" #: graphicswin.cpp:62 msgid "&Redo" @@ -685,31 +798,31 @@ msgstr "重做(&R)" #: graphicswin.cpp:63 msgid "Re&generate All" -msgstr "重新生成所有(&G)" +msgstr "全部重新生成(&G)" #: graphicswin.cpp:65 msgid "Snap Selection to &Grid" -msgstr "选择到轴线(&G)" +msgstr "吸附所选到格线(&G)" #: graphicswin.cpp:66 msgid "Rotate Imported &90°" -msgstr "旋转导入模型90° (&9)" +msgstr "导入的模型旋转90°(&9)" #: graphicswin.cpp:68 msgid "Cu&t" -msgstr "剪切 (&T)" +msgstr "剪切(&T)" #: graphicswin.cpp:69 msgid "&Copy" -msgstr "复制 (&C)" +msgstr "复制(&C)" #: graphicswin.cpp:70 msgid "&Paste" -msgstr "粘贴 (&P)" +msgstr "粘贴(&P)" #: graphicswin.cpp:71 msgid "Paste &Transformed..." -msgstr "粘贴移动(&T)" +msgstr "粘贴并变换...(&T)" #: graphicswin.cpp:72 msgid "&Delete" @@ -717,31 +830,31 @@ msgstr "删除(&D)" #: graphicswin.cpp:74 msgid "Select &Edge Chain" -msgstr "选择边缘约束(&E)" +msgstr "选择相连边(&E)" #: graphicswin.cpp:75 msgid "Select &All" -msgstr "选择所有(&A)" +msgstr "全选(&A)" #: graphicswin.cpp:76 msgid "&Unselect All" -msgstr "取消全选(&U)" +msgstr "全不选(&U)" #: graphicswin.cpp:78 msgid "&Line Styles..." -msgstr "线型(&L)" +msgstr "线的样式...(&L)" #: graphicswin.cpp:79 msgid "&View Projection..." -msgstr "查看投影...(&V)" +msgstr "投影视图...(&V)" #: graphicswin.cpp:81 msgid "Con&figuration..." -msgstr "配置 (&F)" +msgstr "配置...(&F)" #: graphicswin.cpp:84 msgid "&View" -msgstr "查看 (&V)" +msgstr "查看(&V)" #: graphicswin.cpp:85 msgid "Zoom &In" @@ -753,326 +866,338 @@ msgstr "缩小(&O)" #: graphicswin.cpp:87 msgid "Zoom To &Fit" -msgstr "自动缩放(&F)" +msgstr "适合窗口(&F)" #: graphicswin.cpp:89 msgid "Align View to &Workplane" -msgstr "切换视图至平面(&W)" +msgstr "视图对齐至工作面(&W)" #: graphicswin.cpp:90 msgid "Nearest &Ortho View" -msgstr "Ortho视图 (&O)" +msgstr "最接近的正交视图(&O)" #: graphicswin.cpp:91 msgid "Nearest &Isometric View" -msgstr "ISO视图 (&I)" +msgstr "最接近的等轴视图(&I)" #: graphicswin.cpp:92 msgid "&Center View At Point" -msgstr "以点为中心视图 (&C)" +msgstr "视图中心置于点(&C)" #: graphicswin.cpp:94 msgid "Show Snap &Grid" -msgstr "显示捕捉轴线 (&G)" +msgstr "显示吸附格线(&G)" #: graphicswin.cpp:95 msgid "Darken Inactive Solids" -msgstr "" +msgstr "非活动实心体变暗" #: graphicswin.cpp:96 msgid "Use &Perspective Projection" -msgstr "使用远景透视(&P)" +msgstr "使用透视投影(&P)" #: graphicswin.cpp:97 -msgid "Dimension &Units" -msgstr "标注单位(&U)" +msgid "Show E&xploded View" +msgstr "显示爆炸视图(&E)" #: graphicswin.cpp:98 -msgid "Dimensions in &Millimeters" -msgstr "标注单位 mm (&M)" +msgid "Dimension &Units" +msgstr "尺寸单位(&U)" #: graphicswin.cpp:99 -msgid "Dimensions in M&eters" -msgstr "标注单位m (&E)" +msgid "Dimensions in &Millimeters" +msgstr "毫米(&M)" #: graphicswin.cpp:100 +msgid "Dimensions in M&eters" +msgstr "米(&E)" + +#: graphicswin.cpp:101 msgid "Dimensions in &Inches" -msgstr "标准单位英寸 (&I)" +msgstr "英寸(&I)" #: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "英尺和英寸(&F)" + +#: graphicswin.cpp:104 msgid "Show &Toolbar" -msgstr "显示工具条(&T)" +msgstr "显示工具栏(&T)" -#: graphicswin.cpp:103 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" msgstr "显示属性浏览器(&W)" -#: graphicswin.cpp:105 +#: graphicswin.cpp:107 msgid "&Full Screen" msgstr "全屏(&F)" -#: graphicswin.cpp:107 +#: graphicswin.cpp:109 msgid "&New Group" -msgstr "新组合(&N)" +msgstr "新建组(&N)" -#: graphicswin.cpp:108 +#: graphicswin.cpp:110 msgid "Sketch In &3d" -msgstr "在三维内绘制(&3)" +msgstr "在三维空间绘制(&3)" -#: graphicswin.cpp:109 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" msgstr "在新工作面绘制(&W)" -#: graphicswin.cpp:111 +#: graphicswin.cpp:113 msgid "Step &Translating" -msgstr "移动(&T)" +msgstr "步进平移(&T)" -#: graphicswin.cpp:112 +#: graphicswin.cpp:114 msgid "Step &Rotating" -msgstr "旋转(&R)" +msgstr "步进旋转(&R)" -#: graphicswin.cpp:114 +#: graphicswin.cpp:116 msgid "E&xtrude" msgstr "挤出(&E)" -#: graphicswin.cpp:115 +#: graphicswin.cpp:117 msgid "&Helix" msgstr "螺旋(&H)" -#: graphicswin.cpp:116 +#: graphicswin.cpp:118 msgid "&Lathe" -msgstr "扫略(&L)" +msgstr "转圈(&L)" -#: graphicswin.cpp:117 +#: graphicswin.cpp:119 msgid "Re&volve" -msgstr "旋转(&V)" +msgstr "扫略(&V)" -#: graphicswin.cpp:119 +#: graphicswin.cpp:121 msgid "Link / Assemble..." -msgstr "链接/装配..." +msgstr "连接/装配..." -#: graphicswin.cpp:120 +#: graphicswin.cpp:122 msgid "Link Recent" msgstr "连接最近文件" -#: graphicswin.cpp:122 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "绘图(&S)" -#: graphicswin.cpp:123 +#: graphicswin.cpp:125 msgid "In &Workplane" -msgstr "在工作平面(&W)" +msgstr "在工作面内(&W)" -#: graphicswin.cpp:124 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" -msgstr "在3D的任何位置(&3)" +msgstr "在三维空间(&3)" -#: graphicswin.cpp:126 +#: graphicswin.cpp:128 msgid "Datum &Point" msgstr "基准点(&P)" -#: graphicswin.cpp:127 -msgid "&Workplane" -msgstr "工作面(&W)" - #: graphicswin.cpp:129 +msgid "Wor&kplane" +msgstr "工作面(&k)" + +#: graphicswin.cpp:131 msgid "Line &Segment" msgstr "线段(&S)" -#: graphicswin.cpp:130 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" -msgstr "构造线段(&C)" +msgstr "构造线线段(&C)" -#: graphicswin.cpp:131 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "矩形(&R)" -#: graphicswin.cpp:132 +#: graphicswin.cpp:134 msgid "&Circle" -msgstr "圆线(&C)" +msgstr "圆(&C)" -#: graphicswin.cpp:133 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" msgstr "圆弧(&A)" -#: graphicswin.cpp:134 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" -msgstr "立方体线的贝塞尔曲线(&B)" +msgstr "三次贝塞尔样条(&B)" -#: graphicswin.cpp:136 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" -msgstr "TrueTyoe字体文字(&T)" - -#: graphicswin.cpp:137 -msgid "&Image" -msgstr "图片(&I)" +msgstr "TrueType字体文本(&T)" #: graphicswin.cpp:139 +msgid "I&mage" +msgstr "图片(&m)" + +#: graphicswin.cpp:141 msgid "To&ggle Construction" -msgstr "切换构造(&G)" +msgstr "切换构造线(&G)" -#: graphicswin.cpp:140 -msgid "Tangent &Arc at Point" -msgstr "弧线切线点(&A)" +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" +msgstr "点处创建内切弧(&n)" -#: graphicswin.cpp:141 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" -msgstr "在交叉处拆分曲线(&I)" +msgstr "交点处拆分曲线(&I)" -#: graphicswin.cpp:143 +#: graphicswin.cpp:145 msgid "&Constrain" msgstr "约束(&C)" -#: graphicswin.cpp:144 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "距离/直径(&D)" -#: graphicswin.cpp:145 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" -msgstr "参考标注(&F)" +msgstr "参考尺寸(&F)" -#: graphicswin.cpp:146 -msgid "A&ngle" -msgstr "角度(&A)" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "角度(&N)" -#: graphicswin.cpp:147 +#: graphicswin.cpp:149 msgid "Reference An&gle" msgstr "参考角度(&G)" -#: graphicswin.cpp:148 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" -msgstr "其它增补角度(&U)" +msgstr "补角(&U)" -#: graphicswin.cpp:149 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" -msgstr "切换参考标注(&E)" +msgstr "切换参考尺寸(&E)" -#: graphicswin.cpp:151 +#: graphicswin.cpp:153 msgid "&Horizontal" -msgstr "水平约束(&H)" - -#: graphicswin.cpp:152 -msgid "&Vertical" -msgstr "垂直约束(&V)" +msgstr "水平(&H)" #: graphicswin.cpp:154 -msgid "&On Point / Curve / Plane" -msgstr "在点线面(&O)" - -#: graphicswin.cpp:155 -msgid "E&qual Length / Radius / Angle" -msgstr "等于/长度/半径/角度(&Q)" +msgid "&Vertical" +msgstr "竖直(&V)" #: graphicswin.cpp:156 -msgid "Length Ra&tio" -msgstr "长度比例(&T)" +msgid "&On Point / Curve / Plane" +msgstr "在点/线/面上(&O)" #: graphicswin.cpp:157 -msgid "Length Diff&erence" -msgstr "长度偏差(&E)" +msgid "E&qual Length / Radius" +msgstr "长度/半径/角度相等(&Q)" #: graphicswin.cpp:158 -msgid "At &Midpoint" -msgstr "在中点(&M)" +msgid "Length / Arc Ra&tio" +msgstr "(弧)长比例(&T)" #: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "(弧)长之差(&E)" + +#: graphicswin.cpp:160 +msgid "At &Midpoint" +msgstr "中点(&M)" + +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "对称(&Y)" -#: graphicswin.cpp:160 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" -msgstr "水平/切线(&L)" +msgstr "平行/相切(&L)" -#: graphicswin.cpp:161 +#: graphicswin.cpp:163 msgid "&Perpendicular" -msgstr "垂直的(&P)" +msgstr "垂直(&P)" -#: graphicswin.cpp:162 +#: graphicswin.cpp:164 msgid "Same Orient&ation" -msgstr "相同方向(&A)" +msgstr "同向(&A)" -#: graphicswin.cpp:163 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" -msgstr "锁定点位置(&D)" +msgstr "定位后锁定点(&D)" -#: graphicswin.cpp:165 +#: graphicswin.cpp:167 msgid "Comment" msgstr "备注" -#: graphicswin.cpp:167 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "分析(&A)" -#: graphicswin.cpp:168 +#: graphicswin.cpp:170 msgid "Measure &Volume" msgstr "测量体积(&V)" -#: graphicswin.cpp:169 +#: graphicswin.cpp:171 msgid "Measure A&rea" msgstr "测量面积(&R)" -#: graphicswin.cpp:170 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" msgstr "测量周长(&P)" -#: graphicswin.cpp:171 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" -msgstr "显示干涉零件(&I)" +msgstr "显示干涉部件(&I)" -#: graphicswin.cpp:172 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" -msgstr "显示孤立边(&N)" +msgstr "显示裸露边(&N)" -#: graphicswin.cpp:173 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" -msgstr "显示中心(&C)" +msgstr "显示质心(&C)" -#: graphicswin.cpp:175 +#: graphicswin.cpp:177 msgid "Show &Underconstrained Points" -msgstr "显示无效约束点(&U)" +msgstr "显示欠约束的点(&U)" -#: graphicswin.cpp:177 +#: graphicswin.cpp:179 msgid "&Trace Point" msgstr "跟踪点(&T)" -#: graphicswin.cpp:178 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." -msgstr "停止跟踪(&S)" +msgstr "停止跟踪...(&S)" -#: graphicswin.cpp:179 +#: graphicswin.cpp:181 msgid "Step &Dimension..." -msgstr "逐步标注(&D)" +msgstr "尺寸步进...(&D)" -#: graphicswin.cpp:181 +#: graphicswin.cpp:183 msgid "&Help" msgstr "帮助(&H)" -#: graphicswin.cpp:182 +#: graphicswin.cpp:184 msgid "&Language" msgstr "语言(&L)" -#: graphicswin.cpp:183 +#: graphicswin.cpp:185 msgid "&Website / Manual" -msgstr "网页/手册(&W)" +msgstr "网站/手册(&W)" -#: graphicswin.cpp:185 +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "转到Github commit(&G)" + +#: graphicswin.cpp:188 msgid "&About" msgstr "关于(&A)" -#: graphicswin.cpp:355 +#: graphicswin.cpp:362 msgid "(no recent files)" -msgstr "(无文件)" +msgstr "(无最近文件)" -#: graphicswin.cpp:363 +#: graphicswin.cpp:370 #, c-format msgid "File '%s' does not exist." -msgstr "文件不存在: \"%s\"。" +msgstr "不存在文件\"%s\"。" -#: graphicswin.cpp:725 +#: graphicswin.cpp:779 msgid "No workplane is active, so the grid will not appear." -msgstr "没有激活的工作面,因此无法显示轴网。" +msgstr "没有活动工作面,因此无法显示格线。" -#: graphicswin.cpp:740 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel " "projection.\n" @@ -1080,133 +1205,145 @@ msgid "" "For a perspective projection, modify the perspective factor in the " "configuration screen. A value around 0.3 is typical." msgstr "" +"透视因数已设为零,因此视图将始终为平行投影。\n" +"\n" +"要使用透视投影,请在配置界面中修改透视因数。典型值大概是0.3。" -#: graphicswin.cpp:819 +#: graphicswin.cpp:884 msgid "" "Select a point; this point will become the center of the view on screen." -msgstr "" +msgstr "选择一点; 此点将成为屏幕上视图的中心。" -#: graphicswin.cpp:1114 +#: graphicswin.cpp:1193 msgid "No additional entities share endpoints with the selected entities." -msgstr "" +msgstr "所选物件不与其他物件有共同端点。" -#: graphicswin.cpp:1132 +#: graphicswin.cpp:1211 msgid "" "To use this command, select a point or other entity from an linked part, or " "make a link group the active group." msgstr "" +"使用此命令时,选择连接来的部件上一点或其他成分,或将一个连接组设为活动组。" -#: graphicswin.cpp:1155 +#: graphicswin.cpp:1234 msgid "" "No workplane is active. Activate a workplane (with Sketch -> In Workplane) " "to define the plane for the snap grid." msgstr "" +"无活动工作面。激活一个工作面(用\"绘图 -> 在工作面内\")以定义吸附格线所在的" +"平面。" -#: graphicswin.cpp:1162 +#: graphicswin.cpp:1241 msgid "" "Can't snap these items to grid; select points, text comments, or constraints " "with a label. To snap a line, select its endpoints." msgstr "" +"不能吸附这些项目到格线;选择点、文本备注、有尺寸的约束。要吸附线请选择其端" +"点。" -#: graphicswin.cpp:1247 +#: graphicswin.cpp:1326 msgid "No workplane selected. Activating default workplane for this group." -msgstr "" +msgstr "未选择工作面。将激活该组的默认工作面。" -#: graphicswin.cpp:1250 +#: graphicswin.cpp:1329 msgid "" "No workplane is selected, and the active group does not have a default " "workplane. Try selecting a workplane, or activating a sketch-in-new-" "workplane group." msgstr "" +"未选择工作面,且活动组没有默认工作面。请选择一个工作面,或激活一个\"平面草图" +"\"组。" -#: graphicswin.cpp:1271 +#: graphicswin.cpp:1350 msgid "" "Bad selection for tangent arc at point. Select a single point, or select " "nothing to set up arc parameters." -msgstr "" +msgstr "\"点处创建内切弧\"的选择错误。选择单个点,或什么都不选来设置参数。" -#: graphicswin.cpp:1282 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" -msgstr "点击弧线的点(逆时针方向绘制)" +msgstr "点击弧上的点(逆时针绘制)" -#: graphicswin.cpp:1283 +#: graphicswin.cpp:1362 msgid "click to place datum point" msgstr "点击放置基准点" -#: graphicswin.cpp:1284 +#: graphicswin.cpp:1363 msgid "click first point of line segment" -msgstr "点击线条的起点" +msgstr "点击线段的起点" -#: graphicswin.cpp:1286 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" msgstr "点击构造线的起点" -#: graphicswin.cpp:1287 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" -msgstr "点击立方体的起点" +msgstr "点击三次曲线段的起点" -#: graphicswin.cpp:1288 +#: graphicswin.cpp:1367 msgid "click center of circle" -msgstr "点击圆弧的中心" +msgstr "点击圆心" -#: graphicswin.cpp:1289 +#: graphicswin.cpp:1368 msgid "click origin of workplane" -msgstr "点击工作面的原点" +msgstr "点击工作面原点" -#: graphicswin.cpp:1290 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" -msgstr "点击一个矩形倒角" +msgstr "点击矩形的一个角" -#: graphicswin.cpp:1291 +#: graphicswin.cpp:1370 msgid "click top left of text" -msgstr "点击文字左上角" +msgstr "点击文本左上角" -#: graphicswin.cpp:1297 +#: graphicswin.cpp:1376 msgid "click top left of image" msgstr "点击图片左上角" -#: graphicswin.cpp:1309 +#: graphicswin.cpp:1402 msgid "" "No entities are selected. Select entities before trying to toggle their " "construction state." -msgstr "为选中实体,切换构造状态前请先选中实体对象。" +msgstr "未选中物件,切换构造状态前请先选中物件。" #: group.cpp:86 msgctxt "group-name" msgid "sketch-in-3d" -msgstr "3D草图" +msgstr "三维草图" -#: group.cpp:142 +#: group.cpp:154 msgid "" "Bad selection for new sketch in workplane. This group can be created with:\n" "\n" " * a point (through the point, orthogonal to coordinate axes)\n" " * a point and two line segments (through the point, parallel to the " "lines)\n" +" * a point and a normal (through the point, orthogonal to the normal)\n" " * a workplane (copy of the workplane)\n" msgstr "" -"在新工作面内绘图选择失败,该组可以使用:\n" +"\"在新工作面绘制\"的选择错误。该组可创建以:\n" "\n" -" * 一个点(通过该点,正交至坐标轴)\n" -" * 一个点和二个线段(通过点,绘制平行线至线段)\n" -" * 一个工作面(复制工作面)\n" +" * 一点(穿过该点,与坐标轴正交)\n" +" * 一点与两条线段(穿过点,平行于线)\n" +" * 一点和一条法线(穿过点,正交于法线)\n" +" * 工作面(副本)\n" -#: group.cpp:154 +#: group.cpp:170 msgid "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " "will be extruded normal to the workplane." -msgstr "挤出前先激活工作面(草图->在工作面),该草图将由工作面的法线方向挤出。" +msgstr "挤出前先激活工作面(绘图 -> 在工作面内),草图将沿工作面法线挤出。" -#: group.cpp:163 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" msgstr "挤出" -#: group.cpp:168 +#: group.cpp:184 msgid "Lathe operation can only be applied to planar sketches." -msgstr "扫略操作仅可用于二维草图。" +msgstr "转圈操作只能用于平面草图。" -#: group.cpp:179 +#: group.cpp:195 msgid "" "Bad selection for new lathe group. This group can be created with:\n" "\n" @@ -1214,21 +1351,21 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" -"创建扫略组失败,该组可由:\n" +"新建转圈组的选择错误,该组可创建以:\n" "\n" -" * 一个点和一个线段或法线(围绕坐标轴至线或法线的平行线,通过点)\n" -" * 一个线段(围绕线段)\n" +" * 一点与一条线段或法线(围绕通过点且平行于线的轴)\n" +" * 一条线段(围绕线段)\n" -#: group.cpp:189 +#: group.cpp:205 msgctxt "group-name" msgid "lathe" -msgstr "扫略" +msgstr "转圈" -#: group.cpp:194 +#: group.cpp:210 msgid "Revolve operation can only be applied to planar sketches." -msgstr "" +msgstr "扫略操作只能用于平面草图。" -#: group.cpp:205 +#: group.cpp:221 msgid "" "Bad selection for new revolve group. This group can be created with:\n" "\n" @@ -1236,17 +1373,21 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" +"新建扫略组的选择错误。该组可创建以:\n" +"\n" +" * 一点与线段/法线(围绕通过点且平行于线的轴旋转)\n" +" * 一条线段(围绕线段旋转)\n" -#: group.cpp:217 +#: group.cpp:233 msgctxt "group-name" msgid "revolve" -msgstr "旋转" +msgstr "扫略" -#: group.cpp:222 +#: group.cpp:238 msgid "Helix operation can only be applied to planar sketches." -msgstr "" +msgstr "螺旋操作只能用于平面草图。" -#: group.cpp:233 +#: group.cpp:249 msgid "" "Bad selection for new helix group. This group can be created with:\n" "\n" @@ -1254,13 +1395,17 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" +"新建螺旋组的选择错误。该组可创建以:\n" +"\n" +" * 一点与一条线段或法线(围绕通过点且平行于线的轴旋转)\n" +" * 线段(围绕线段旋转)\n" -#: group.cpp:245 +#: group.cpp:261 msgctxt "group-name" msgid "helix" msgstr "螺旋" -#: group.cpp:258 +#: group.cpp:274 msgid "" "Bad selection for new rotation. This group can be created with:\n" "\n" @@ -1269,176 +1414,184 @@ msgid "" " * a point and a line or a normal (rotate about an axis through the " "point, and parallel to line / normal)\n" msgstr "" +"新建旋转组的选择错误。该组可创建以:\n" +"\n" +" * 一点,在工作面内新建时(在平面内围绕该点旋转)\n" +" * 一点与一条线段或法线(围绕通过点且平行于线的轴旋转)\n" -#: group.cpp:271 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" msgstr "旋转" -#: group.cpp:282 +#: group.cpp:298 msgctxt "group-name" msgid "translate" -msgstr "移动" +msgstr "平移" -#: group.cpp:400 +#: group.cpp:422 msgid "(unnamed)" msgstr "(未命名)" -#: groupmesh.cpp:709 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" -msgstr "未闭合轮廓 , 或样式不一致!" +msgstr "轮廓未闭合,或样式不一致!" -#: groupmesh.cpp:722 +#: groupmesh.cpp:723 msgid "points not all coplanar!" -msgstr "点不在相同平面!" +msgstr "并非所有点都共面!" -#: groupmesh.cpp:724 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" msgstr "轮廓自相交!" -#: groupmesh.cpp:726 +#: groupmesh.cpp:727 msgid "zero-length edge!" -msgstr "边缘长度为零!" +msgstr "边长度为零!" + +#: importmesh.cpp:136 +msgid "Text-formated STL files are not currently supported" +msgstr "目前不支持文本格式的STL文件" -#: modify.cpp:254 +#: modify.cpp:252 msgid "Must be sketching in workplane to create tangent arc." -msgstr "" +msgstr "必须在工作面内创建内切弧。" -#: modify.cpp:301 +#: modify.cpp:299 msgid "" "To create a tangent arc, select a point where two non-construction lines or " "circles in this group and workplane join." -msgstr "" +msgstr "要创建内切弧,请选择活动组和工作面中,两个非构造线或圆连接处。" -#: modify.cpp:388 +#: modify.cpp:386 msgid "" "Couldn't round this corner. Try a smaller radius, or try creating the " "desired geometry by hand with tangency constraints." -msgstr "" +msgstr "无法倒圆这个角。尝试更小的半径,或用切线约束手动创建。" -#: modify.cpp:597 +#: modify.cpp:595 msgid "Couldn't split this entity; lines, circles, or cubics only." -msgstr "" +msgstr "不能拆分此物件;仅限直线、圆、三次曲线。" -#: modify.cpp:624 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." -msgstr "" +msgstr "必须在工作面内拆分。" -#: modify.cpp:631 +#: modify.cpp:629 msgid "" "Select two entities that intersect each other (e.g. two lines/circles/arcs " "or a line/circle/arc and a point)." -msgstr "" +msgstr "选择两个相交的物件(例如两条线/圆/弧,或一条线/圆/弧与一点)。" -#: modify.cpp:736 +#: modify.cpp:734 msgid "Can't split; no intersection found." -msgstr "无法拆分;未发现较差点。" +msgstr "无法拆分;未发现交点。" -#: mouse.cpp:559 +#: mouse.cpp:558 msgid "Assign to Style" msgstr "指定样式" -#: mouse.cpp:575 +#: mouse.cpp:574 msgid "No Style" msgstr "无样式" -#: mouse.cpp:578 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." -msgstr "新组样式。" +msgstr "新建自定义样式..." -#: mouse.cpp:585 +#: mouse.cpp:584 msgid "Group Info" msgstr "组信息" -#: mouse.cpp:605 +#: mouse.cpp:604 msgid "Style Info" msgstr "样式信息" -#: mouse.cpp:625 +#: mouse.cpp:624 msgid "Select Edge Chain" -msgstr "选择边缘链" +msgstr "选择相连边" -#: mouse.cpp:631 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" -msgstr "切换参考标注" +msgstr "切换参考尺寸" -#: mouse.cpp:637 +#: mouse.cpp:636 msgid "Other Supplementary Angle" -msgstr "其它补充角度" +msgstr "补角" -#: mouse.cpp:642 +#: mouse.cpp:641 msgid "Snap to Grid" -msgstr "捕捉至轴网" +msgstr "吸附至格线" -#: mouse.cpp:651 +#: mouse.cpp:650 msgid "Remove Spline Point" -msgstr "删除样条线的点" +msgstr "删除样条点" -#: mouse.cpp:686 +#: mouse.cpp:685 msgid "Add Spline Point" -msgstr "增加样条线的点" +msgstr "增加样条点" -#: mouse.cpp:690 +#: mouse.cpp:689 msgid "Cannot add spline point: maximum number of points reached." -msgstr "无法增加样条线点:超过最大限制。" +msgstr "无法增加样条点:超过点数限制。" -#: mouse.cpp:715 +#: mouse.cpp:714 msgid "Toggle Construction" -msgstr "切换构造" +msgstr "切换构造线" #: mouse.cpp:730 msgid "Delete Point-Coincident Constraint" -msgstr "删除点一致约束" +msgstr "删除点点重合约束" -#: mouse.cpp:749 +#: mouse.cpp:748 msgid "Cut" msgstr "剪切" -#: mouse.cpp:751 +#: mouse.cpp:750 msgid "Copy" msgstr "复制" -#: mouse.cpp:755 +#: mouse.cpp:754 msgid "Select All" msgstr "全选" -#: mouse.cpp:760 +#: mouse.cpp:759 msgid "Paste" msgstr "粘贴" -#: mouse.cpp:762 +#: mouse.cpp:761 msgid "Paste Transformed..." -msgstr "粘贴移动的..." +msgstr "粘贴并变换..." -#: mouse.cpp:767 +#: mouse.cpp:766 msgid "Delete" msgstr "删除" -#: mouse.cpp:770 +#: mouse.cpp:769 msgid "Unselect All" -msgstr "取消全选" +msgstr "全不选" -#: mouse.cpp:777 +#: mouse.cpp:776 msgid "Unselect Hovered" -msgstr "取消覆盖区域的全选" +msgstr "光标下的不选" -#: mouse.cpp:786 +#: mouse.cpp:785 msgid "Zoom to Fit" -msgstr "自动缩放" +msgstr "适合窗口" -#: mouse.cpp:988 mouse.cpp:1275 +#: mouse.cpp:987 mouse.cpp:1276 msgid "click next point of line, or press Esc" -msgstr "点击下一个点或取消(ESC)" +msgstr "点击下一个点,或按Esc取消" -#: mouse.cpp:994 +#: mouse.cpp:993 msgid "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." -msgstr "无法在3D内绘制矩形; 首先,激活工作面,草图->在工作面。" +msgstr "无法在三维空间绘制矩形;请先激活工作面(用\"绘图 -> 在工作面内\")。" -#: mouse.cpp:1028 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" -msgstr "点击放置其它矩形倒角" +msgstr "点击放置矩形的另一个角" #: mouse.cpp:1048 msgid "click to set radius" @@ -1448,7 +1601,7 @@ msgstr "点击设置半径" msgid "" "Can't draw arc in 3d; first, activate a workplane with Sketch -> In " "Workplane." -msgstr "无法在3D空间内绘制弧线,可使用 草图->在工作面 激活工作面。" +msgstr "无法在三维空间绘制弧线,请激活工作面(用\"绘图 -> 在工作面内\")" #: mouse.cpp:1072 msgid "click to place point" @@ -1456,250 +1609,264 @@ msgstr "点击放置点" #: mouse.cpp:1088 msgid "click next point of cubic, or press Esc" -msgstr "点击下一个点或取消(ESC)" +msgstr "点击下一个点,或按Esc取消" #: mouse.cpp:1093 msgid "" "Sketching in a workplane already; sketch in 3d before creating new workplane." -msgstr "已经在工作面绘制;在新建工作面前在三维空间绘制。" +msgstr "已在工作面内绘制;新建工作面之前先\"在三维空间绘制\"。" #: mouse.cpp:1109 msgid "" "Can't draw text in 3d; first, activate a workplane with Sketch -> In " "Workplane." -msgstr "无法在三维空间内绘制文字,可使用 草图->在工作面 激活工作面。" +msgstr "无法在三维空间绘制文本,请激活工作面(用\"绘图 -> 在工作面内\")" #: mouse.cpp:1126 msgid "click to place bottom right of text" -msgstr "点击文字的右下角放置" +msgstr "点击放置文本的右下角" #: mouse.cpp:1132 msgid "" "Can't draw image in 3d; first, activate a workplane with Sketch -> In " "Workplane." -msgstr "无法在三维空间内绘制图片,可使用 草图->在工作面 激活工作面。" - -#: mouse.cpp:1159 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" -msgstr "新备注 - 双击编辑" +msgstr "无法在三维空间绘制图片,请激活工作面(用\"绘图 -> 在工作面内\")" -#: platform/gui.cpp:85 platform/gui.cpp:89 solvespace.cpp:511 +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 msgctxt "file-type" msgid "SolveSpace models" msgstr "SolveSpace模型" -#: platform/gui.cpp:90 +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "全部" + +#: platform/gui.cpp:91 msgctxt "file-type" msgid "IDF circuit board" -msgstr "" +msgstr "IDF电路图" + +#: platform/gui.cpp:92 +msgctxt "file-type" +msgid "STL triangle mesh" +msgstr "STL三角网格" -#: platform/gui.cpp:94 +#: platform/gui.cpp:96 msgctxt "file-type" msgid "PNG image" msgstr "PNG图片" -#: platform/gui.cpp:98 +#: platform/gui.cpp:100 msgctxt "file-type" msgid "STL mesh" msgstr "STL网格" -#: platform/gui.cpp:99 +#: platform/gui.cpp:101 msgctxt "file-type" msgid "Wavefront OBJ mesh" -msgstr "Wavefront OBJ网格" +msgstr "Wavefront obj网格" -#: platform/gui.cpp:100 +#: platform/gui.cpp:102 msgctxt "file-type" msgid "Three.js-compatible mesh, with viewer" -msgstr "Three.js-网格及查看视图" +msgstr "Three.js兼容网格,带视图" -#: platform/gui.cpp:101 +#: platform/gui.cpp:103 msgctxt "file-type" msgid "Three.js-compatible mesh, mesh only" -msgstr "Three.js-仅网格" +msgstr "Three.js兼容网格,仅网格" -#: platform/gui.cpp:102 +#: platform/gui.cpp:104 msgctxt "file-type" msgid "VRML text file" msgstr "VRML文本文件" -#: platform/gui.cpp:106 platform/gui.cpp:113 platform/gui.cpp:120 +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 msgctxt "file-type" msgid "STEP file" msgstr "STEP文件" -#: platform/gui.cpp:110 +#: platform/gui.cpp:112 msgctxt "file-type" msgid "PDF file" msgstr "PDF文件" -#: platform/gui.cpp:111 +#: platform/gui.cpp:113 msgctxt "file-type" msgid "Encapsulated PostScript" -msgstr "封装好的PostScript" +msgstr "封装的PostScript" -#: platform/gui.cpp:112 +#: platform/gui.cpp:114 msgctxt "file-type" msgid "Scalable Vector Graphics" msgstr "SVG矢量图" -#: platform/gui.cpp:114 platform/gui.cpp:121 +#: platform/gui.cpp:116 platform/gui.cpp:123 msgctxt "file-type" msgid "DXF file (AutoCAD 2007)" -msgstr "DXF文件(AutoCAD 2007)" +msgstr "DXF文件(AutoCAD 2007)" -#: platform/gui.cpp:115 +#: platform/gui.cpp:117 msgctxt "file-type" msgid "HPGL file" msgstr "HPGL文件" -#: platform/gui.cpp:116 +#: platform/gui.cpp:118 msgctxt "file-type" msgid "G Code" -msgstr "G Code" +msgstr "G代码" -#: platform/gui.cpp:125 +#: platform/gui.cpp:127 msgctxt "file-type" msgid "AutoCAD DXF and DWG files" msgstr "AutoCAD DXF/DWG文件" -#: platform/gui.cpp:129 +#: platform/gui.cpp:131 msgctxt "file-type" msgid "Comma-separated values" msgstr "逗号分隔数据" -#: platform/guigtk.cpp:1324 platform/guimac.mm:1363 platform/guiwin.cpp:1639 +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" -msgstr "未命名" +msgstr "无标题" -#: platform/guigtk.cpp:1335 platform/guigtk.cpp:1368 platform/guimac.mm:1321 -#: platform/guiwin.cpp:1582 +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 msgctxt "title" msgid "Save File" msgstr "保存文件" -#: platform/guigtk.cpp:1336 platform/guigtk.cpp:1369 platform/guimac.mm:1304 -#: platform/guiwin.cpp:1584 +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 msgctxt "title" msgid "Open File" msgstr "打开文件" -#: platform/guigtk.cpp:1339 platform/guigtk.cpp:1375 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" msgid "_Cancel" -msgstr "取消_C" +msgstr "取消(_C)" -#: platform/guigtk.cpp:1340 platform/guigtk.cpp:1373 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" msgid "_Save" -msgstr "保存_S" +msgstr "保存(_S)" -#: platform/guigtk.cpp:1341 platform/guigtk.cpp:1374 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" msgid "_Open" -msgstr "打开_O" +msgstr "打开(_O)" -#: solvespace.cpp:169 +#: solvespace.cpp:175 msgctxt "title" msgid "Autosave Available" -msgstr "" +msgstr "自动保存可用" -#: solvespace.cpp:170 +#: solvespace.cpp:176 msgctxt "dialog" msgid "An autosave file is available for this sketch." -msgstr "" +msgstr "该草图有自动保存文件可用。" -#: solvespace.cpp:171 +#: solvespace.cpp:177 msgctxt "dialog" msgid "Do you want to load the autosave file instead?" -msgstr "" +msgstr "是否加载自动保存文件?" -#: solvespace.cpp:172 +#: solvespace.cpp:178 msgctxt "button" msgid "&Load autosave" -msgstr "" +msgstr "加载(&L)" -#: solvespace.cpp:174 +#: solvespace.cpp:180 msgctxt "button" msgid "Do&n't Load" -msgstr "" +msgstr "不加载(&N)" -#: solvespace.cpp:557 +#: solvespace.cpp:640 msgctxt "title" msgid "Modified File" -msgstr "" +msgstr "文件已修改" -#: solvespace.cpp:559 +#: solvespace.cpp:642 #, c-format msgctxt "dialog" msgid "Do you want to save the changes you made to the sketch “%s”?" msgstr "" +"是否保存您对草图\n" +"“%s”\n" +"的修改?" -#: solvespace.cpp:562 +#: solvespace.cpp:645 msgctxt "dialog" msgid "Do you want to save the changes you made to the new sketch?" -msgstr "" +msgstr "是否保存您对新草图的修改?" -#: solvespace.cpp:565 +#: solvespace.cpp:648 msgctxt "dialog" msgid "Your changes will be lost if you don't save them." -msgstr "" +msgstr "如果不保存,您的修改将会丢失。" -#: solvespace.cpp:566 +#: solvespace.cpp:649 msgctxt "button" msgid "&Save" -msgstr "" +msgstr "保存(&S)" -#: solvespace.cpp:568 +#: solvespace.cpp:651 msgctxt "button" msgid "Do&n't Save" -msgstr "" +msgstr "不保存(&N)" -#: solvespace.cpp:589 +#: solvespace.cpp:672 msgctxt "title" msgid "(new sketch)" -msgstr "" +msgstr "(新草图)" -#: solvespace.cpp:596 +#: solvespace.cpp:683 msgctxt "title" msgid "Property Browser" -msgstr "" +msgstr "属性浏览器" -#: solvespace.cpp:658 +#: solvespace.cpp:746 msgid "" "Constraints are currently shown, and will be exported in the toolpath. This " "is probably not what you want; hide them by clicking the link at the top of " "the text window." msgstr "" +"当前显示约束,并将导出至刀具路径。这可能不是你想要的;点击文本窗口顶部的按钮" +"来隐藏它们。" -#: solvespace.cpp:730 +#: solvespace.cpp:834 #, c-format msgid "" "Can't identify file type from file extension of filename '%s'; try .dxf or ." "dwg." -msgstr "" +msgstr "无法从'%s'的文件扩展名识别文件类型,请尝试.dxf或.dwg。" -#: solvespace.cpp:778 +#: solvespace.cpp:886 msgid "Constraint must have a label, and must not be a reference dimension." -msgstr "" +msgstr "约束必须有尺寸,且不能是参考尺寸。" -#: solvespace.cpp:782 +#: solvespace.cpp:890 msgid "Bad selection for step dimension; select a constraint." -msgstr "" +msgstr "尺寸步进选择错误,请选择约束。" -#: solvespace.cpp:806 +#: solvespace.cpp:914 msgid "The assembly does not interfere, good." -msgstr "" +msgstr "装配无干涉,好。" -#: solvespace.cpp:822 +#: solvespace.cpp:930 #, c-format msgid "" "The volume of the solid model is:\n" "\n" " %s" msgstr "" +"实心体模型的体积是:\n" +"\n" +" %s" -#: solvespace.cpp:831 +#: solvespace.cpp:939 #, c-format msgid "" "\n" @@ -1707,16 +1874,23 @@ msgid "" "\n" " %s" msgstr "" +"\n" +"当前组的网格体积是:\n" +"\n" +" %s" -#: solvespace.cpp:836 +#: solvespace.cpp:944 msgid "" "\n" "\n" "Curved surfaces have been approximated as triangles.\n" "This introduces error, typically of around 1%." msgstr "" +"\n" +"\n" +"曲面已近似为三角形。这会引入误差,通常约为1%。" -#: solvespace.cpp:851 +#: solvespace.cpp:959 #, c-format msgid "" "The surface area of the selected faces is:\n" @@ -1726,14 +1900,19 @@ msgid "" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%." msgstr "" +"所选表面的表面积为:\n" +"\n" +" %s\n" +"\n" +"曲线已近似为线性分段。这会引入误差,通常约为1%%。" -#: solvespace.cpp:860 +#: solvespace.cpp:968 msgid "" "This group does not contain a correctly-formed 2d closed area. It is open, " "not coplanar, or self-intersecting." -msgstr "" +msgstr "此组不包含正确形式的二维封闭区域。它可能是开放的,或不共面,或自相交。" -#: solvespace.cpp:872 +#: solvespace.cpp:980 #, c-format msgid "" "The area of the region sketched in this group is:\n" @@ -1743,8 +1922,13 @@ msgid "" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%." msgstr "" +"该组中所画区域的面积为:\n" +"\n" +" %s\n" +"\n" +"曲线已近似为线性分段。这会引入误差,通常约为1%%。" -#: solvespace.cpp:892 +#: solvespace.cpp:1000 #, c-format msgid "" "The total length of the selected entities is:\n" @@ -1754,45 +1938,53 @@ msgid "" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%." msgstr "" +"所选物件的总长度为:\n" +"\n" +" %s\n" +"\n" +"曲线已近似为线性分段。这会引入误差,通常约为1%%。" -#: solvespace.cpp:898 +#: solvespace.cpp:1006 msgid "Bad selection for perimeter; select line segments, arcs, and curves." -msgstr "" +msgstr "周长选择错误;请选择线段、弧线、曲线。" -#: solvespace.cpp:914 +#: solvespace.cpp:1022 msgid "Bad selection for trace; select a single point." -msgstr "" +msgstr "跟踪选择不当;请选择单个点。" -#: solvespace.cpp:941 +#: solvespace.cpp:1049 #, c-format msgid "Couldn't write to '%s'" -msgstr "" +msgstr "无法写入'%s'" -#: solvespace.cpp:971 +#: solvespace.cpp:1079 msgid "The mesh is self-intersecting (NOT okay, invalid)." -msgstr "" +msgstr "网格是自相交的(不行,无效)" -#: solvespace.cpp:972 +#: solvespace.cpp:1080 msgid "The mesh is not self-intersecting (okay, valid)." -msgstr "" +msgstr "网格不是自相交的(行,有效)" -#: solvespace.cpp:974 +#: solvespace.cpp:1082 msgid "The mesh has naked edges (NOT okay, invalid)." -msgstr "" +msgstr "网格有裸露边(不行,无效)" -#: solvespace.cpp:975 +#: solvespace.cpp:1083 msgid "The mesh is watertight (okay, valid)." -msgstr "" +msgstr "网格是水密的(行,有效)" -#: solvespace.cpp:978 +#: solvespace.cpp:1086 #, c-format msgid "" "\n" "\n" "The model contains %d triangles, from %d surfaces." msgstr "" +"\n" +"\n" +"该模型包含%d个表面上的%d个三角形。" -#: solvespace.cpp:982 +#: solvespace.cpp:1090 #, c-format msgid "" "%s\n" @@ -1801,8 +1993,13 @@ msgid "" "\n" "Zero problematic edges, good.%s" msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"没有问题边,好。%s" -#: solvespace.cpp:985 +#: solvespace.cpp:1093 #, c-format msgid "" "%s\n" @@ -1811,8 +2008,13 @@ msgid "" "\n" "%d problematic edges, bad.%s" msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"%d条边有问题,不好。%s" -#: solvespace.cpp:998 +#: solvespace.cpp:1106 #, c-format msgid "" "This is SolveSpace version %s.\n" @@ -1828,80 +2030,93 @@ msgid "" "\n" "© 2008-%d Jonathan Westhues and other authors.\n" msgstr "" +"当前SolveSpace版本 %s\n" +"\n" +"更多信息请见 https://solvespace.com/\n" +"\n" +"SolveSpace是自由软件:您可以自由修改或\n" +"重新发行,但需遵循GNU通用公共许可协议\n" +"(GNU General Public License, GPL) 第3版\n" +"或后续版本。\n" +"\n" +"在法律允许范围内不包含质保,详见\n" +"https://gnu.org/licenses/\n" +"\n" +"© 2008-%d Jonathan Westhues等作者\n" -#: style.cpp:166 +#: style.cpp:185 msgid "" "Can't assign style to an entity that's derived from another entity; try " "assigning a style to this entity's parent." -msgstr "无法将样式分配给派生自其他实体的实体;尝试将样式分配给此实体的父级。" +msgstr "无法将样式分配给派生自其他物件的物件;尝试将样式分配给此物件的父级。" -#: style.cpp:665 +#: style.cpp:735 msgid "Style name cannot be empty" msgstr "样式名称不能为空" -#: textscreens.cpp:741 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." -msgstr "不能重复少于 1 次。" +msgstr "不能重复 1 次以下。" -#: textscreens.cpp:745 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." -msgstr "重复不超过 999 次。" +msgstr "不能重复 999 次以上。" -#: textscreens.cpp:770 +#: textscreens.cpp:866 msgid "Group name cannot be empty" -msgstr "组名称不能为空" +msgstr "组名不能为空" -#: textscreens.cpp:813 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." -msgstr "不透明度必须在零和 1 之间。" +msgstr "不透明度必须在 0 到 1 之间。" -#: textscreens.cpp:848 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "半径偏移不能为负数。" #: toolbar.cpp:18 msgid "Sketch line segment" -msgstr "草图线段" +msgstr "绘制线段" #: toolbar.cpp:20 msgid "Sketch rectangle" -msgstr "草图矩形" +msgstr "绘制矩形" #: toolbar.cpp:22 msgid "Sketch circle" -msgstr "草图圆" +msgstr "绘制圆" #: toolbar.cpp:24 msgid "Sketch arc of a circle" -msgstr "圆的草图弧线" +msgstr "绘制圆弧" #: toolbar.cpp:26 msgid "Sketch curves from text in a TrueType font" -msgstr "从 TrueType 字体中的文本中绘制草图曲线" +msgstr "绘制TrueType字体文本曲线" #: toolbar.cpp:28 msgid "Sketch image from a file" -msgstr "从文件中绘制图像" +msgstr "绘制图像文件" #: toolbar.cpp:30 msgid "Create tangent arc at selected point" -msgstr "在选定点创建切线弧" +msgstr "选定点处创建内切弧" #: toolbar.cpp:32 msgid "Sketch cubic Bezier spline" -msgstr "草图立方贝塞尔样条" +msgstr "绘制三次贝塞尔样条" #: toolbar.cpp:34 msgid "Sketch datum point" -msgstr "草图基准点" +msgstr "绘制基准点" #: toolbar.cpp:36 msgid "Toggle construction" -msgstr "切换结构" +msgstr "切换构造线" #: toolbar.cpp:38 msgid "Split lines / curves where they intersect" -msgstr "相交的分割线/曲线" +msgstr "交点处拆分(曲)线" #: toolbar.cpp:42 msgid "Constrain distance / diameter / length" @@ -1917,83 +2132,83 @@ msgstr "约束为水平" #: toolbar.cpp:48 msgid "Constrain to be vertical" -msgstr "约束为垂直" +msgstr "约束为竖直" #: toolbar.cpp:50 msgid "Constrain to be parallel or tangent" -msgstr "约束为平行或切线" +msgstr "约束为平行或相切" #: toolbar.cpp:52 msgid "Constrain to be perpendicular" -msgstr "约束至垂直" +msgstr "约束为垂直" #: toolbar.cpp:54 msgid "Constrain point on line / curve / plane / point" -msgstr "约束点至线/曲线/平面/点" +msgstr "约束点在直线/曲线/面/点上" #: toolbar.cpp:56 msgid "Constrain symmetric" -msgstr "对称约束" +msgstr "约束为对称" #: toolbar.cpp:58 msgid "Constrain equal length / radius / angle" -msgstr "约束长/半径/角度相等" +msgstr "约束长度/半径/角度相等" #: toolbar.cpp:60 msgid "Constrain normals in same orientation" -msgstr "约束法线在同原点" +msgstr "约束法线同向" #: toolbar.cpp:62 msgid "Other supplementary angle" -msgstr "其它补充角度" +msgstr "补角" #: toolbar.cpp:64 msgid "Toggle reference dimension" -msgstr "切换参考标注" +msgstr "切换参考尺寸" #: toolbar.cpp:68 msgid "New group extruding active sketch" -msgstr "新组中挤出当前草图" +msgstr "新组:活动草图挤出" #: toolbar.cpp:70 msgid "New group rotating active sketch" -msgstr "新组中旋转体当前草图" +msgstr "新组:活动草图旋转" #: toolbar.cpp:72 msgid "New group helix from active sketch" -msgstr "" +msgstr "新组:活动草图螺旋" #: toolbar.cpp:74 msgid "New group revolve active sketch" -msgstr "" +msgstr "新组:活动草图扫略" #: toolbar.cpp:76 msgid "New group step and repeat rotating" -msgstr "新组中逐步重复旋转体" +msgstr "新组:步进旋转" #: toolbar.cpp:78 msgid "New group step and repeat translating" -msgstr "新组中逐步重复移动体" +msgstr "新组:步进平移" #: toolbar.cpp:80 msgid "New group in new workplane (thru given entities)" -msgstr "在新工作平面创建组(通过指定对象)" +msgstr "新组:在新工作面绘制(由给定物件指定)" #: toolbar.cpp:82 msgid "New group in 3d" -msgstr "在3D中新建组" +msgstr "新组:在三维空间绘制" #: toolbar.cpp:84 msgid "New group linking / assembling file" -msgstr "新组 连接/装配文件" +msgstr "新组:连接/装配文件" #: toolbar.cpp:88 msgid "Nearest isometric view" -msgstr "ISO视图" +msgstr "最接近的等轴视图" #: toolbar.cpp:90 msgid "Align view to active workplane" -msgstr "切换视图至当前工作面" +msgstr "视图对齐至工作面" #: util.cpp:165 msgctxt "title" @@ -2008,15 +2223,160 @@ msgstr "消息" #: util.cpp:170 msgctxt "button" msgid "&OK" -msgstr "&OK" +msgstr "好(&O)" -#: view.cpp:78 +#: view.cpp:127 msgid "Scale cannot be zero or negative." msgstr "缩放不能为零。" -#: view.cpp:90 view.cpp:99 +#: view.cpp:139 view.cpp:148 msgid "Bad format: specify x, y, z" -msgstr "格式错误: 需指定 x, y, z" +msgstr "格式错误:请指定 x,y,z" + +#~ msgid "" +#~ "Bad selection for on point / curve / plane constraint. This constraint " +#~ "can apply to:\n" +#~ "\n" +#~ " * two points (points coincident)\n" +#~ " * a point and a workplane (point in plane)\n" +#~ " * a point and a line segment (point on line)\n" +#~ " * a point and a circle or arc (point on curve)\n" +#~ " * a point and a plane face (point on face)\n" +#~ msgstr "" +#~ "点 / 曲线 / 平面约束的选定方法错误。此约束可应用于:\n" +#~ "\n" +#~ "* 两点(点重合)\n" +#~ " * 一个点和一个工作面(平面中点)\n" +#~ " * 点和线段(点在线)\n" +#~ " * 一个点和一个圆或圆(曲线上的点)\n" +#~ " * 点和平面面(点在脸上)\n" + +#~ msgid "" +#~ "Bad selection for equal length / radius constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (equal length)\n" +#~ " * two line segments and two points (equal point-line distances)\n" +#~ " * a line segment and two points (equal point-line distances)\n" +#~ " * a line segment, and a point and line segment (point-line distance " +#~ "equals length)\n" +#~ " * four line segments or normals (equal angle between A,B and C,D)\n" +#~ " * three line segments or normals (equal angle between A,B and B,C)\n" +#~ " * two circles or arcs (equal radius)\n" +#~ " * a line segment and an arc (line segment length equals arc length)\n" +#~ msgstr "" +#~ "等长度/半径约束的选定方法错误。此约束可应用于:\n" +#~ "\n" +#~ "* 两个线段(相等长度)\n" +#~ " * 两个线段和两个点(相等的点线距离)\n" +#~ " * 线段和两个点(相等的点线距离)\n" +#~ " * 线段和点段和线段(点线距离等于长度)\n" +#~ " * 四条线段或法线(A、B 和 C、D 之间的等角)\n" +#~ " * 三条线段或法线(A、B 和 B、C 之间的等角)\n" +#~ " * 两个圆或圆(相等半径)\n" +#~ " * 线段和圆弧(线段长度等于弧长)\n" + +#~ msgid "" +#~ "Bad selection for horizontal / vertical constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two points\n" +#~ " * a line segment\n" +#~ msgstr "" +#~ "水平/竖直约束的选择错误。此约束可应用于:\n" +#~ "\n" +#~ " * 两点\n" +#~ " * 线段\n" + +#~ msgid "" +#~ "Bad selection for angle constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" +#~ msgstr "" +#~ "角度约束的选择错误。此约束可应用于:\n" +#~ "\n" +#~ "* 两个线段\n" +#~ " * 线段和法线\n" +#~ " * 两个法线\n" + +#~ msgid "" +#~ "Bad selection for parallel / tangent constraint. This constraint can " +#~ "apply to:\n" +#~ "\n" +#~ " * two line segments (parallel)\n" +#~ " * a line segment and a normal (parallel)\n" +#~ " * two normals (parallel)\n" +#~ " * two line segments, arcs, or beziers, that share an endpoint " +#~ "(tangent)\n" +#~ msgstr "" +#~ "平行/切线约束的选择错误。此约束可应用于:\n" +#~ "\n" +#~ "* 两条线段(平行)\n" +#~ " * 线段和法线(平行)\n" +#~ " * 两个法线(平行)\n" +#~ " * 有共同端点的两条线段、弧线或贝塞尔(切线)\n" + +#~ msgid "" +#~ "Bad selection for perpendicular constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ " * a line segment and a normal\n" +#~ " * two normals\n" +#~ msgstr "" +#~ "垂直约束的选择错误。此约束可应用于:\n" +#~ "\n" +#~ "* 两个线段\n" +#~ " * 线段和法线\n" +#~ " * 两个法线\n" + +#~ msgid "A&ngle" +#~ msgstr "角度(&A)" + +#~ msgid "E&qual Length / Radius / Angle" +#~ msgstr "等于/长度/半径/角度(&Q)" + +#~ msgid "" +#~ "Bad selection for length ratio constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ msgstr "" +#~ "长度比例约束的选择错误。此约束可应用于:\n" +#~ "\n" +#~ "* 两个线段\n" + +#~ msgid "" +#~ "Bad selection for length difference constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ msgstr "" +#~ "长度差异约束的选择错误。此约束可应用于:\n" +#~ "\n" +#~ "* 两个线段\n" + +#~ msgid "Length Ra&tio" +#~ msgstr "长度比例(&T)" + +#~ msgid "Length Diff&erence" +#~ msgstr "长度偏差(&E)" + +#~ msgid "" +#~ "Bad selection for new sketch in workplane. This group can be created " +#~ "with:\n" +#~ "\n" +#~ " * a point (through the point, orthogonal to coordinate axes)\n" +#~ " * a point and two line segments (through the point, parallel to the " +#~ "lines)\n" +#~ " * a workplane (copy of the workplane)\n" +#~ msgstr "" +#~ "在新工作面内绘图选择失败,该组可以使用:\n" +#~ "\n" +#~ " * 一个点(通过该点,正交至坐标轴)\n" +#~ " * 一个点和二个线段(通过点,绘制平行线至线段)\n" +#~ " * 一个工作面(复制工作面)\n" #~ msgctxt "file-type" #~ msgid "Q3D Object file" diff --git a/res/messages.pot b/res/messages.pot index 534faa40f..4e51fda2f 100644 --- a/res/messages.pot +++ b/res/messages.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: SolveSpace 3.0\n" -"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" -"POT-Creation-Date: 2021-02-01 15:45+0200\n" +"Project-Id-Version: SolveSpace 3.1\n" +"Report-Msgid-Bugs-To: phkahler@gmail.com\n" +"POT-Creation-Date: 2025-01-26 21:04+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,76 +17,76 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: clipboard.cpp:310 +#: clipboard.cpp:314 msgid "" "Cut, paste, and copy work only in a workplane.\n" "\n" "Activate one with Sketch -> In Workplane." msgstr "" -#: clipboard.cpp:327 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." msgstr "" -#: clipboard.cpp:374 +#: clipboard.cpp:378 msgid "Number of copies to paste must be at least one." msgstr "" -#: clipboard.cpp:390 textscreens.cpp:783 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "" -#: clipboard.cpp:432 +#: clipboard.cpp:436 msgid "Select one point to define origin of rotation." msgstr "" -#: clipboard.cpp:444 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." msgstr "" -#: clipboard.cpp:454 +#: clipboard.cpp:458 msgid "Transformation is identity. So all copies will be exactly on top of each other." msgstr "" -#: clipboard.cpp:458 +#: clipboard.cpp:462 msgid "Too many items to paste; split this into smaller pastes." msgstr "" -#: clipboard.cpp:463 +#: clipboard.cpp:467 msgid "No workplane active." msgstr "" -#: confscreen.cpp:418 +#: confscreen.cpp:410 msgid "Bad format: specify coordinates as x, y, z" msgstr "" -#: confscreen.cpp:428 style.cpp:659 textscreens.cpp:805 +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 msgid "Bad format: specify color as r, g, b" msgstr "" -#: confscreen.cpp:454 +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use Perspective Projection." msgstr "" -#: confscreen.cpp:467 confscreen.cpp:477 +#: confscreen.cpp:464 confscreen.cpp:474 #, c-format msgid "Specify between 0 and %d digits after the decimal." msgstr "" -#: confscreen.cpp:489 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" msgstr "" -#: confscreen.cpp:501 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" msgstr "" -#: confscreen.cpp:555 +#: confscreen.cpp:557 msgid "Bad value: autosave interval should be positive" msgstr "" -#: confscreen.cpp:558 +#: confscreen.cpp:560 msgid "Bad format: specify interval in integral minutes" msgstr "" @@ -157,133 +157,184 @@ msgstr "" #: constraint.cpp:25 msgctxt "constr-name" -msgid "length-difference" +msgid "arc-arc-length-ratio" msgstr "" #: constraint.cpp:26 msgctxt "constr-name" -msgid "symmetric" +msgid "arc-line-length-ratio" msgstr "" #: constraint.cpp:27 msgctxt "constr-name" -msgid "symmetric-h" +msgid "length-difference" msgstr "" #: constraint.cpp:28 msgctxt "constr-name" -msgid "symmetric-v" +msgid "arc-arc-len-difference" msgstr "" #: constraint.cpp:29 msgctxt "constr-name" -msgid "symmetric-line" +msgid "arc-line-len-difference" msgstr "" #: constraint.cpp:30 msgctxt "constr-name" -msgid "at-midpoint" +msgid "symmetric" msgstr "" #: constraint.cpp:31 msgctxt "constr-name" -msgid "horizontal" +msgid "symmetric-h" msgstr "" #: constraint.cpp:32 msgctxt "constr-name" -msgid "vertical" +msgid "symmetric-v" msgstr "" #: constraint.cpp:33 msgctxt "constr-name" -msgid "diameter" +msgid "symmetric-line" msgstr "" #: constraint.cpp:34 msgctxt "constr-name" -msgid "pt-on-circle" +msgid "at-midpoint" msgstr "" #: constraint.cpp:35 msgctxt "constr-name" -msgid "same-orientation" +msgid "horizontal" msgstr "" #: constraint.cpp:36 msgctxt "constr-name" -msgid "angle" +msgid "vertical" msgstr "" #: constraint.cpp:37 msgctxt "constr-name" -msgid "parallel" +msgid "diameter" msgstr "" #: constraint.cpp:38 msgctxt "constr-name" -msgid "arc-line-tangent" +msgid "pt-on-circle" msgstr "" #: constraint.cpp:39 msgctxt "constr-name" -msgid "cubic-line-tangent" +msgid "same-orientation" msgstr "" #: constraint.cpp:40 msgctxt "constr-name" -msgid "curve-curve-tangent" +msgid "angle" msgstr "" #: constraint.cpp:41 msgctxt "constr-name" -msgid "perpendicular" +msgid "parallel" msgstr "" #: constraint.cpp:42 msgctxt "constr-name" -msgid "eq-radius" +msgid "arc-line-tangent" msgstr "" #: constraint.cpp:43 msgctxt "constr-name" -msgid "eq-angle" +msgid "cubic-line-tangent" msgstr "" #: constraint.cpp:44 msgctxt "constr-name" -msgid "eq-line-len-arc-len" +msgid "curve-curve-tangent" msgstr "" #: constraint.cpp:45 msgctxt "constr-name" -msgid "lock-where-dragged" +msgid "perpendicular" msgstr "" #: constraint.cpp:46 msgctxt "constr-name" +msgid "eq-radius" +msgstr "" + +#: constraint.cpp:47 +msgctxt "constr-name" +msgid "eq-angle" +msgstr "" + +#: constraint.cpp:48 +msgctxt "constr-name" +msgid "eq-line-len-arc-len" +msgstr "" + +#: constraint.cpp:49 +msgctxt "constr-name" +msgid "lock-where-dragged" +msgstr "" + +#: constraint.cpp:50 +msgctxt "constr-name" msgid "comment" msgstr "" -#: constraint.cpp:140 +#: constraint.cpp:151 msgid "" -"The tangent arc and line segment must share an endpoint. Constrain them with Constrain -> On " -"Point before constraining tangent." +"The point you selected does not belong to the arc. The arc and line segment do not share an end " +"point.\n" +"\n" +"Select the end point of the arc at which you want it to be tangent to the line." msgstr "" #: constraint.cpp:158 msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them with Constrain -> On " -"Point before constraining tangent." +"The tangent arc and line segment must share an endpoint. Constrain them with Constrain -> On " +"Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the arc at which you want it to be tangent to the line." +msgstr "" + +#: constraint.cpp:186 +msgid "" +"The point you selected is not an end point of the cubic spline. The spline and line segment do " +"not share an end point.\n" +"\n" +"Select the end point of the spline at which you want it to be tangent to the line." +msgstr "" + +#: constraint.cpp:193 +msgid "" +"The tangent cubic spline and line segment must share an endpoint. Constrain them with Constrain -" +"> On Point before constraining tangent.\n" +"\n" +"Alternatively select the end point of the cubic spline at which you want it to be tangent to the " +"line." msgstr "" -#: constraint.cpp:183 +#: constraint.cpp:237 +msgid "" +"The points you selected are not end points of the two curves. The curves do not share an end " +"point.\n" +"\n" +"Select the end points of both curves at which you want them to be tangent to each other." +msgstr "" + +#: constraint.cpp:244 msgid "" "The curves must share an endpoint. Constrain them with Constrain -> On Point before constraining " -"tangent." +"tangent.\n" +"\n" +"Alternatively select the end points of both curves at which you want the curves to be tangent." msgstr "" -#: constraint.cpp:231 +#: constraint.cpp:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply to:\n" "\n" @@ -296,46 +347,48 @@ msgid "" " * a circle or an arc (diameter)\n" msgstr "" -#: constraint.cpp:284 +#: constraint.cpp:366 msgid "" "Bad selection for on point / curve / plane constraint. This constraint can apply to:\n" "\n" -" * two points (points coincident)\n" +" * two or more points (points coincident)\n" " * a point and a workplane (point in plane)\n" " * a point and a line segment (point on line)\n" " * a point and a circle or arc (point on curve)\n" -" * a point and a plane face (point on face)\n" +" * a point and one to three plane faces (point on face(s))\n" msgstr "" -#: constraint.cpp:346 +#: constraint.cpp:427 msgid "" "Bad selection for equal length / radius constraint. This constraint can apply to:\n" "\n" -" * two line segments (equal length)\n" +" * two or more line segments (equal length)\n" " * two line segments and two points (equal point-line distances)\n" " * a line segment and two points (equal point-line distances)\n" " * a line segment, and a point and line segment (point-line distance equals length)\n" -" * four line segments or normals (equal angle between A,B and C,D)\n" -" * three line segments or normals (equal angle between A,B and B,C)\n" -" * two circles or arcs (equal radius)\n" +" * two or more circles or arcs (equal radius)\n" " * a line segment and an arc (line segment length equals arc length)\n" msgstr "" -#: constraint.cpp:385 +#: constraint.cpp:480 msgid "" "Bad selection for length ratio constraint. This constraint can apply to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" -#: constraint.cpp:402 +#: constraint.cpp:515 msgid "" "Bad selection for length difference constraint. This constraint can apply to:\n" "\n" " * two line segments\n" +" * two arcs\n" +" * one arc and one line segment\n" msgstr "" -#: constraint.cpp:428 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" @@ -343,7 +396,7 @@ msgid "" " * a line segment and a workplane (line's midpoint on plane)\n" msgstr "" -#: constraint.cpp:486 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -352,79 +405,92 @@ msgid "" " * workplane, and two points or a line segment (symmetric about workplane)\n" msgstr "" -#: constraint.cpp:500 +#: constraint.cpp:623 msgid "A workplane must be active when constraining symmetric without an explicit symmetry plane." msgstr "" -#: constraint.cpp:530 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a horizontal or vertical " "constraint." msgstr "" -#: constraint.cpp:543 +#: constraint.cpp:679 msgid "" "Bad selection for horizontal / vertical constraint. This constraint can apply to:\n" "\n" -" * two points\n" -" * a line segment\n" +" * two or more points\n" +" * one or more line segments\n" msgstr "" -#: constraint.cpp:564 +#: constraint.cpp:697 msgid "" "Bad selection for same orientation constraint. This constraint can apply to:\n" "\n" " * two normals\n" msgstr "" -#: constraint.cpp:614 +#: constraint.cpp:748 msgid "Must select an angle constraint." msgstr "" -#: constraint.cpp:627 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." msgstr "" -#: constraint.cpp:638 +#: constraint.cpp:784 msgid "" "Bad selection for angle constraint. This constraint can apply to:\n" "\n" +"Angle between:\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" +"\n" +"Equal angles:\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" msgstr "" -#: constraint.cpp:701 +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." msgstr "" -#: constraint.cpp:711 +#: constraint.cpp:887 msgid "" "Bad selection for parallel / tangent constraint. This constraint can apply to:\n" "\n" -" * two line segments (parallel)\n" -" * a line segment and a normal (parallel)\n" -" * two normals (parallel)\n" +" * two faces\n" +" * two or more line segments (parallel)\n" +" * one or more line segments and one or more normals (parallel)\n" +" * two or more normals (parallel)\n" " * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +" * two line segments, arcs, or beziers, that do not share an endpoint and the end point(s) of " +"the curve(s) (tangent)\n" msgstr "" -#: constraint.cpp:729 +#: constraint.cpp:914 msgid "" "Bad selection for perpendicular constraint. This constraint can apply to:\n" "\n" +" * two faces\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n" msgstr "" -#: constraint.cpp:744 +#: constraint.cpp:931 msgid "" "Bad selection for lock point where dragged constraint. This constraint can apply to:\n" "\n" " * a point\n" msgstr "" -#: constraint.cpp:755 +#: constraint.cpp:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "" + +#: constraint.cpp:952 msgid "click center of comment text" msgstr "" @@ -443,23 +509,23 @@ msgid "" " * a point and two line segments (plane through point and parallel to lines)\n" msgstr "" -#: export.cpp:822 +#: export.cpp:818 msgid "Active group mesh is empty; nothing to export." msgstr "" -#: exportvector.cpp:337 +#: exportvector.cpp:336 msgid "freehand lines were replaced with continuous lines" msgstr "" -#: exportvector.cpp:339 +#: exportvector.cpp:338 msgid "zigzag lines were replaced with continuous lines" msgstr "" -#: exportvector.cpp:593 +#: exportvector.cpp:592 msgid "Some aspects of the drawing have no DXF equivalent and were not exported:\n" msgstr "" -#: exportvector.cpp:839 +#: exportvector.cpp:838 msgid "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." msgstr "" @@ -473,26 +539,26 @@ msgctxt "group-name" msgid "#references" msgstr "" -#: file.cpp:552 +#: file.cpp:555 msgid "The file is empty. It may be corrupt." msgstr "" -#: file.cpp:557 +#: file.cpp:560 msgid "Unrecognized data in file. This file may be corrupt, or from a newer version of the program." msgstr "" -#: file.cpp:867 +#: file.cpp:876 msgctxt "title" msgid "Missing File" msgstr "" -#: file.cpp:868 +#: file.cpp:877 #, c-format msgctxt "dialog" msgid "The linked file “%s” is not present." msgstr "" -#: file.cpp:870 +#: file.cpp:879 msgctxt "dialog" msgid "" "Do you want to locate it manually?\n" @@ -500,17 +566,17 @@ msgid "" "If you decline, any geometry that depends on the missing file will be permanently removed." msgstr "" -#: file.cpp:873 +#: file.cpp:882 msgctxt "button" msgid "&Yes" msgstr "" -#: file.cpp:875 +#: file.cpp:884 msgctxt "button" msgid "&No" msgstr "" -#: file.cpp:877 solvespace.cpp:569 +#: file.cpp:886 solvespace.cpp:652 msgctxt "button" msgid "&Cancel" msgstr "" @@ -684,295 +750,307 @@ msgid "Use &Perspective Projection" msgstr "" #: graphicswin.cpp:97 -msgid "Dimension &Units" +msgid "Show E&xploded View" msgstr "" #: graphicswin.cpp:98 -msgid "Dimensions in &Millimeters" +msgid "Dimension &Units" msgstr "" #: graphicswin.cpp:99 -msgid "Dimensions in M&eters" +msgid "Dimensions in &Millimeters" msgstr "" #: graphicswin.cpp:100 +msgid "Dimensions in M&eters" +msgstr "" + +#: graphicswin.cpp:101 msgid "Dimensions in &Inches" msgstr "" #: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "" + +#: graphicswin.cpp:104 msgid "Show &Toolbar" msgstr "" -#: graphicswin.cpp:103 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" msgstr "" -#: graphicswin.cpp:105 +#: graphicswin.cpp:107 msgid "&Full Screen" msgstr "" -#: graphicswin.cpp:107 +#: graphicswin.cpp:109 msgid "&New Group" msgstr "" -#: graphicswin.cpp:108 +#: graphicswin.cpp:110 msgid "Sketch In &3d" msgstr "" -#: graphicswin.cpp:109 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" msgstr "" -#: graphicswin.cpp:111 +#: graphicswin.cpp:113 msgid "Step &Translating" msgstr "" -#: graphicswin.cpp:112 +#: graphicswin.cpp:114 msgid "Step &Rotating" msgstr "" -#: graphicswin.cpp:114 +#: graphicswin.cpp:116 msgid "E&xtrude" msgstr "" -#: graphicswin.cpp:115 +#: graphicswin.cpp:117 msgid "&Helix" msgstr "" -#: graphicswin.cpp:116 +#: graphicswin.cpp:118 msgid "&Lathe" msgstr "" -#: graphicswin.cpp:117 +#: graphicswin.cpp:119 msgid "Re&volve" msgstr "" -#: graphicswin.cpp:119 +#: graphicswin.cpp:121 msgid "Link / Assemble..." msgstr "" -#: graphicswin.cpp:120 +#: graphicswin.cpp:122 msgid "Link Recent" msgstr "" -#: graphicswin.cpp:122 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "" -#: graphicswin.cpp:123 +#: graphicswin.cpp:125 msgid "In &Workplane" msgstr "" -#: graphicswin.cpp:124 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" msgstr "" -#: graphicswin.cpp:126 +#: graphicswin.cpp:128 msgid "Datum &Point" msgstr "" -#: graphicswin.cpp:127 -msgid "&Workplane" +#: graphicswin.cpp:129 +msgid "Wor&kplane" msgstr "" -#: graphicswin.cpp:129 +#: graphicswin.cpp:131 msgid "Line &Segment" msgstr "" -#: graphicswin.cpp:130 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" msgstr "" -#: graphicswin.cpp:131 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "" -#: graphicswin.cpp:132 +#: graphicswin.cpp:134 msgid "&Circle" msgstr "" -#: graphicswin.cpp:133 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" msgstr "" -#: graphicswin.cpp:134 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" msgstr "" -#: graphicswin.cpp:136 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" msgstr "" -#: graphicswin.cpp:137 -msgid "&Image" +#: graphicswin.cpp:139 +msgid "I&mage" msgstr "" -#: graphicswin.cpp:139 +#: graphicswin.cpp:141 msgid "To&ggle Construction" msgstr "" -#: graphicswin.cpp:140 -msgid "Tangent &Arc at Point" +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" msgstr "" -#: graphicswin.cpp:141 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" msgstr "" -#: graphicswin.cpp:143 +#: graphicswin.cpp:145 msgid "&Constrain" msgstr "" -#: graphicswin.cpp:144 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "" -#: graphicswin.cpp:145 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" msgstr "" -#: graphicswin.cpp:146 -msgid "A&ngle" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" msgstr "" -#: graphicswin.cpp:147 +#: graphicswin.cpp:149 msgid "Reference An&gle" msgstr "" -#: graphicswin.cpp:148 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" msgstr "" -#: graphicswin.cpp:149 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" msgstr "" -#: graphicswin.cpp:151 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "" -#: graphicswin.cpp:152 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "" -#: graphicswin.cpp:154 +#: graphicswin.cpp:156 msgid "&On Point / Curve / Plane" msgstr "" -#: graphicswin.cpp:155 -msgid "E&qual Length / Radius / Angle" +#: graphicswin.cpp:157 +msgid "E&qual Length / Radius" msgstr "" -#: graphicswin.cpp:156 -msgid "Length Ra&tio" +#: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" msgstr "" -#: graphicswin.cpp:157 -msgid "Length Diff&erence" +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" msgstr "" -#: graphicswin.cpp:158 +#: graphicswin.cpp:160 msgid "At &Midpoint" msgstr "" -#: graphicswin.cpp:159 +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "" -#: graphicswin.cpp:160 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "" -#: graphicswin.cpp:161 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "" -#: graphicswin.cpp:162 +#: graphicswin.cpp:164 msgid "Same Orient&ation" msgstr "" -#: graphicswin.cpp:163 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "" -#: graphicswin.cpp:165 +#: graphicswin.cpp:167 msgid "Comment" msgstr "" -#: graphicswin.cpp:167 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "" -#: graphicswin.cpp:168 +#: graphicswin.cpp:170 msgid "Measure &Volume" msgstr "" -#: graphicswin.cpp:169 +#: graphicswin.cpp:171 msgid "Measure A&rea" msgstr "" -#: graphicswin.cpp:170 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" msgstr "" -#: graphicswin.cpp:171 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" msgstr "" -#: graphicswin.cpp:172 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" msgstr "" -#: graphicswin.cpp:173 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" msgstr "" -#: graphicswin.cpp:175 +#: graphicswin.cpp:177 msgid "Show &Underconstrained Points" msgstr "" -#: graphicswin.cpp:177 +#: graphicswin.cpp:179 msgid "&Trace Point" msgstr "" -#: graphicswin.cpp:178 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." msgstr "" -#: graphicswin.cpp:179 +#: graphicswin.cpp:181 msgid "Step &Dimension..." msgstr "" -#: graphicswin.cpp:181 +#: graphicswin.cpp:183 msgid "&Help" msgstr "" -#: graphicswin.cpp:182 +#: graphicswin.cpp:184 msgid "&Language" msgstr "" -#: graphicswin.cpp:183 +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "" -#: graphicswin.cpp:185 +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "" + +#: graphicswin.cpp:188 msgid "&About" msgstr "" -#: graphicswin.cpp:355 +#: graphicswin.cpp:362 msgid "(no recent files)" msgstr "" -#: graphicswin.cpp:363 +#: graphicswin.cpp:370 #, c-format msgid "File '%s' does not exist." msgstr "" -#: graphicswin.cpp:725 +#: graphicswin.cpp:779 msgid "No workplane is active, so the grid will not appear." msgstr "" -#: graphicswin.cpp:740 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel projection.\n" "\n" @@ -980,89 +1058,89 @@ msgid "" "around 0.3 is typical." msgstr "" -#: graphicswin.cpp:819 +#: graphicswin.cpp:884 msgid "Select a point; this point will become the center of the view on screen." msgstr "" -#: graphicswin.cpp:1114 +#: graphicswin.cpp:1193 msgid "No additional entities share endpoints with the selected entities." msgstr "" -#: graphicswin.cpp:1132 +#: graphicswin.cpp:1211 msgid "" "To use this command, select a point or other entity from an linked part, or make a link group the " "active group." msgstr "" -#: graphicswin.cpp:1155 +#: graphicswin.cpp:1234 msgid "" "No workplane is active. Activate a workplane (with Sketch -> In Workplane) to define the plane " "for the snap grid." msgstr "" -#: graphicswin.cpp:1162 +#: graphicswin.cpp:1241 msgid "" "Can't snap these items to grid; select points, text comments, or constraints with a label. To " "snap a line, select its endpoints." msgstr "" -#: graphicswin.cpp:1247 +#: graphicswin.cpp:1326 msgid "No workplane selected. Activating default workplane for this group." msgstr "" -#: graphicswin.cpp:1250 +#: graphicswin.cpp:1329 msgid "" "No workplane is selected, and the active group does not have a default workplane. Try selecting a " "workplane, or activating a sketch-in-new-workplane group." msgstr "" -#: graphicswin.cpp:1271 +#: graphicswin.cpp:1350 msgid "" "Bad selection for tangent arc at point. Select a single point, or select nothing to set up arc " "parameters." msgstr "" -#: graphicswin.cpp:1282 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" msgstr "" -#: graphicswin.cpp:1283 +#: graphicswin.cpp:1362 msgid "click to place datum point" msgstr "" -#: graphicswin.cpp:1284 +#: graphicswin.cpp:1363 msgid "click first point of line segment" msgstr "" -#: graphicswin.cpp:1286 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" msgstr "" -#: graphicswin.cpp:1287 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" msgstr "" -#: graphicswin.cpp:1288 +#: graphicswin.cpp:1367 msgid "click center of circle" msgstr "" -#: graphicswin.cpp:1289 +#: graphicswin.cpp:1368 msgid "click origin of workplane" msgstr "" -#: graphicswin.cpp:1290 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" msgstr "" -#: graphicswin.cpp:1291 +#: graphicswin.cpp:1370 msgid "click top left of text" msgstr "" -#: graphicswin.cpp:1297 +#: graphicswin.cpp:1376 msgid "click top left of image" msgstr "" -#: graphicswin.cpp:1309 +#: graphicswin.cpp:1402 msgid "No entities are selected. Select entities before trying to toggle their construction state." msgstr "" @@ -1071,31 +1149,32 @@ msgctxt "group-name" msgid "sketch-in-3d" msgstr "" -#: group.cpp:142 +#: group.cpp:154 msgid "" "Bad selection for new sketch in workplane. This group can be created with:\n" "\n" " * a point (through the point, orthogonal to coordinate axes)\n" " * a point and two line segments (through the point, parallel to the lines)\n" +" * a point and a normal (through the point, orthogonal to the normal)\n" " * a workplane (copy of the workplane)\n" msgstr "" -#: group.cpp:154 +#: group.cpp:170 msgid "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch will be extruded " "normal to the workplane." msgstr "" -#: group.cpp:163 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" msgstr "" -#: group.cpp:168 +#: group.cpp:184 msgid "Lathe operation can only be applied to planar sketches." msgstr "" -#: group.cpp:179 +#: group.cpp:195 msgid "" "Bad selection for new lathe group. This group can be created with:\n" "\n" @@ -1104,16 +1183,16 @@ msgid "" " * a line segment (revolved about line segment)\n" msgstr "" -#: group.cpp:189 +#: group.cpp:205 msgctxt "group-name" msgid "lathe" msgstr "" -#: group.cpp:194 +#: group.cpp:210 msgid "Revolve operation can only be applied to planar sketches." msgstr "" -#: group.cpp:205 +#: group.cpp:221 msgid "" "Bad selection for new revolve group. This group can be created with:\n" "\n" @@ -1122,16 +1201,16 @@ msgid "" " * a line segment (revolved about line segment)\n" msgstr "" -#: group.cpp:217 +#: group.cpp:233 msgctxt "group-name" msgid "revolve" msgstr "" -#: group.cpp:222 +#: group.cpp:238 msgid "Helix operation can only be applied to planar sketches." msgstr "" -#: group.cpp:233 +#: group.cpp:249 msgid "" "Bad selection for new helix group. This group can be created with:\n" "\n" @@ -1140,12 +1219,12 @@ msgid "" " * a line segment (revolved about line segment)\n" msgstr "" -#: group.cpp:245 +#: group.cpp:261 msgctxt "group-name" msgid "helix" msgstr "" -#: group.cpp:258 +#: group.cpp:274 msgid "" "Bad selection for new rotation. This group can be created with:\n" "\n" @@ -1154,119 +1233,123 @@ msgid "" "line / normal)\n" msgstr "" -#: group.cpp:271 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" msgstr "" -#: group.cpp:282 +#: group.cpp:298 msgctxt "group-name" msgid "translate" msgstr "" -#: group.cpp:400 +#: group.cpp:422 msgid "(unnamed)" msgstr "" -#: groupmesh.cpp:709 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" msgstr "" -#: groupmesh.cpp:722 +#: groupmesh.cpp:723 msgid "points not all coplanar!" msgstr "" -#: groupmesh.cpp:724 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" msgstr "" -#: groupmesh.cpp:726 +#: groupmesh.cpp:727 msgid "zero-length edge!" msgstr "" -#: modify.cpp:254 +#: importmesh.cpp:136 +msgid "Text-formated STL files are not currently supported" +msgstr "" + +#: modify.cpp:252 msgid "Must be sketching in workplane to create tangent arc." msgstr "" -#: modify.cpp:301 +#: modify.cpp:299 msgid "" "To create a tangent arc, select a point where two non-construction lines or circles in this group " "and workplane join." msgstr "" -#: modify.cpp:388 +#: modify.cpp:386 msgid "" "Couldn't round this corner. Try a smaller radius, or try creating the desired geometry by hand " "with tangency constraints." msgstr "" -#: modify.cpp:597 +#: modify.cpp:595 msgid "Couldn't split this entity; lines, circles, or cubics only." msgstr "" -#: modify.cpp:624 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." msgstr "" -#: modify.cpp:631 +#: modify.cpp:629 msgid "" "Select two entities that intersect each other (e.g. two lines/circles/arcs or a line/circle/arc " "and a point)." msgstr "" -#: modify.cpp:736 +#: modify.cpp:734 msgid "Can't split; no intersection found." msgstr "" -#: mouse.cpp:559 +#: mouse.cpp:558 msgid "Assign to Style" msgstr "" -#: mouse.cpp:575 +#: mouse.cpp:574 msgid "No Style" msgstr "" -#: mouse.cpp:578 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." msgstr "" -#: mouse.cpp:585 +#: mouse.cpp:584 msgid "Group Info" msgstr "" -#: mouse.cpp:605 +#: mouse.cpp:604 msgid "Style Info" msgstr "" -#: mouse.cpp:625 +#: mouse.cpp:624 msgid "Select Edge Chain" msgstr "" -#: mouse.cpp:631 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" msgstr "" -#: mouse.cpp:637 +#: mouse.cpp:636 msgid "Other Supplementary Angle" msgstr "" -#: mouse.cpp:642 +#: mouse.cpp:641 msgid "Snap to Grid" msgstr "" -#: mouse.cpp:651 +#: mouse.cpp:650 msgid "Remove Spline Point" msgstr "" -#: mouse.cpp:686 +#: mouse.cpp:685 msgid "Add Spline Point" msgstr "" -#: mouse.cpp:690 +#: mouse.cpp:689 msgid "Cannot add spline point: maximum number of points reached." msgstr "" -#: mouse.cpp:715 +#: mouse.cpp:714 msgid "Toggle Construction" msgstr "" @@ -1274,51 +1357,51 @@ msgstr "" msgid "Delete Point-Coincident Constraint" msgstr "" -#: mouse.cpp:749 +#: mouse.cpp:748 msgid "Cut" msgstr "" -#: mouse.cpp:751 +#: mouse.cpp:750 msgid "Copy" msgstr "" -#: mouse.cpp:755 +#: mouse.cpp:754 msgid "Select All" msgstr "" -#: mouse.cpp:760 +#: mouse.cpp:759 msgid "Paste" msgstr "" -#: mouse.cpp:762 +#: mouse.cpp:761 msgid "Paste Transformed..." msgstr "" -#: mouse.cpp:767 +#: mouse.cpp:766 msgid "Delete" msgstr "" -#: mouse.cpp:770 +#: mouse.cpp:769 msgid "Unselect All" msgstr "" -#: mouse.cpp:777 +#: mouse.cpp:776 msgid "Unselect Hovered" msgstr "" -#: mouse.cpp:786 +#: mouse.cpp:785 msgid "Zoom to Fit" msgstr "" -#: mouse.cpp:988 mouse.cpp:1275 +#: mouse.cpp:987 mouse.cpp:1276 msgid "click next point of line, or press Esc" msgstr "" -#: mouse.cpp:994 +#: mouse.cpp:993 msgid "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In Workplane." msgstr "" -#: mouse.cpp:1028 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" msgstr "" @@ -1354,216 +1437,222 @@ msgstr "" msgid "Can't draw image in 3d; first, activate a workplane with Sketch -> In Workplane." msgstr "" -#: mouse.cpp:1159 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 +msgctxt "file-type" +msgid "SolveSpace models" msgstr "" -#: platform/gui.cpp:85 platform/gui.cpp:89 solvespace.cpp:511 +#: platform/gui.cpp:89 msgctxt "file-type" -msgid "SolveSpace models" +msgid "ALL" msgstr "" -#: platform/gui.cpp:90 +#: platform/gui.cpp:91 msgctxt "file-type" msgid "IDF circuit board" msgstr "" -#: platform/gui.cpp:94 +#: platform/gui.cpp:92 +msgctxt "file-type" +msgid "STL triangle mesh" +msgstr "" + +#: platform/gui.cpp:96 msgctxt "file-type" msgid "PNG image" msgstr "" -#: platform/gui.cpp:98 +#: platform/gui.cpp:100 msgctxt "file-type" msgid "STL mesh" msgstr "" -#: platform/gui.cpp:99 +#: platform/gui.cpp:101 msgctxt "file-type" msgid "Wavefront OBJ mesh" msgstr "" -#: platform/gui.cpp:100 +#: platform/gui.cpp:102 msgctxt "file-type" msgid "Three.js-compatible mesh, with viewer" msgstr "" -#: platform/gui.cpp:101 +#: platform/gui.cpp:103 msgctxt "file-type" msgid "Three.js-compatible mesh, mesh only" msgstr "" -#: platform/gui.cpp:102 +#: platform/gui.cpp:104 msgctxt "file-type" msgid "VRML text file" msgstr "" -#: platform/gui.cpp:106 platform/gui.cpp:113 platform/gui.cpp:120 +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 msgctxt "file-type" msgid "STEP file" msgstr "" -#: platform/gui.cpp:110 +#: platform/gui.cpp:112 msgctxt "file-type" msgid "PDF file" msgstr "" -#: platform/gui.cpp:111 +#: platform/gui.cpp:113 msgctxt "file-type" msgid "Encapsulated PostScript" msgstr "" -#: platform/gui.cpp:112 +#: platform/gui.cpp:114 msgctxt "file-type" msgid "Scalable Vector Graphics" msgstr "" -#: platform/gui.cpp:114 platform/gui.cpp:121 +#: platform/gui.cpp:116 platform/gui.cpp:123 msgctxt "file-type" msgid "DXF file (AutoCAD 2007)" msgstr "" -#: platform/gui.cpp:115 +#: platform/gui.cpp:117 msgctxt "file-type" msgid "HPGL file" msgstr "" -#: platform/gui.cpp:116 +#: platform/gui.cpp:118 msgctxt "file-type" msgid "G Code" msgstr "" -#: platform/gui.cpp:125 +#: platform/gui.cpp:127 msgctxt "file-type" msgid "AutoCAD DXF and DWG files" msgstr "" -#: platform/gui.cpp:129 +#: platform/gui.cpp:131 msgctxt "file-type" msgid "Comma-separated values" msgstr "" -#: platform/guigtk.cpp:1324 platform/guimac.mm:1363 platform/guiwin.cpp:1639 +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" msgstr "" -#: platform/guigtk.cpp:1335 platform/guigtk.cpp:1368 platform/guimac.mm:1321 -#: platform/guiwin.cpp:1582 +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 msgctxt "title" msgid "Save File" msgstr "" -#: platform/guigtk.cpp:1336 platform/guigtk.cpp:1369 platform/guimac.mm:1304 -#: platform/guiwin.cpp:1584 +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 msgctxt "title" msgid "Open File" msgstr "" -#: platform/guigtk.cpp:1339 platform/guigtk.cpp:1375 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" msgid "_Cancel" msgstr "" -#: platform/guigtk.cpp:1340 platform/guigtk.cpp:1373 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" msgid "_Save" msgstr "" -#: platform/guigtk.cpp:1341 platform/guigtk.cpp:1374 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" msgid "_Open" msgstr "" -#: solvespace.cpp:169 +#: solvespace.cpp:175 msgctxt "title" msgid "Autosave Available" msgstr "" -#: solvespace.cpp:170 +#: solvespace.cpp:176 msgctxt "dialog" msgid "An autosave file is available for this sketch." msgstr "" -#: solvespace.cpp:171 +#: solvespace.cpp:177 msgctxt "dialog" msgid "Do you want to load the autosave file instead?" msgstr "" -#: solvespace.cpp:172 +#: solvespace.cpp:178 msgctxt "button" msgid "&Load autosave" msgstr "" -#: solvespace.cpp:174 +#: solvespace.cpp:180 msgctxt "button" msgid "Do&n't Load" msgstr "" -#: solvespace.cpp:557 +#: solvespace.cpp:640 msgctxt "title" msgid "Modified File" msgstr "" -#: solvespace.cpp:559 +#: solvespace.cpp:642 #, c-format msgctxt "dialog" msgid "Do you want to save the changes you made to the sketch “%s”?" msgstr "" -#: solvespace.cpp:562 +#: solvespace.cpp:645 msgctxt "dialog" msgid "Do you want to save the changes you made to the new sketch?" msgstr "" -#: solvespace.cpp:565 +#: solvespace.cpp:648 msgctxt "dialog" msgid "Your changes will be lost if you don't save them." msgstr "" -#: solvespace.cpp:566 +#: solvespace.cpp:649 msgctxt "button" msgid "&Save" msgstr "" -#: solvespace.cpp:568 +#: solvespace.cpp:651 msgctxt "button" msgid "Do&n't Save" msgstr "" -#: solvespace.cpp:589 +#: solvespace.cpp:672 msgctxt "title" msgid "(new sketch)" msgstr "" -#: solvespace.cpp:596 +#: solvespace.cpp:683 msgctxt "title" msgid "Property Browser" msgstr "" -#: solvespace.cpp:658 +#: solvespace.cpp:746 msgid "" "Constraints are currently shown, and will be exported in the toolpath. This is probably not what " "you want; hide them by clicking the link at the top of the text window." msgstr "" -#: solvespace.cpp:730 +#: solvespace.cpp:834 #, c-format msgid "Can't identify file type from file extension of filename '%s'; try .dxf or .dwg." msgstr "" -#: solvespace.cpp:778 +#: solvespace.cpp:886 msgid "Constraint must have a label, and must not be a reference dimension." msgstr "" -#: solvespace.cpp:782 +#: solvespace.cpp:890 msgid "Bad selection for step dimension; select a constraint." msgstr "" -#: solvespace.cpp:806 +#: solvespace.cpp:914 msgid "The assembly does not interfere, good." msgstr "" -#: solvespace.cpp:822 +#: solvespace.cpp:930 #, c-format msgid "" "The volume of the solid model is:\n" @@ -1571,7 +1660,7 @@ msgid "" " %s" msgstr "" -#: solvespace.cpp:831 +#: solvespace.cpp:939 #, c-format msgid "" "\n" @@ -1580,7 +1669,7 @@ msgid "" " %s" msgstr "" -#: solvespace.cpp:836 +#: solvespace.cpp:944 msgid "" "\n" "\n" @@ -1588,7 +1677,7 @@ msgid "" "This introduces error, typically of around 1%." msgstr "" -#: solvespace.cpp:851 +#: solvespace.cpp:959 #, c-format msgid "" "The surface area of the selected faces is:\n" @@ -1599,13 +1688,13 @@ msgid "" "This introduces error, typically of around 1%%." msgstr "" -#: solvespace.cpp:860 +#: solvespace.cpp:968 msgid "" "This group does not contain a correctly-formed 2d closed area. It is open, not coplanar, or self-" "intersecting." msgstr "" -#: solvespace.cpp:872 +#: solvespace.cpp:980 #, c-format msgid "" "The area of the region sketched in this group is:\n" @@ -1616,7 +1705,7 @@ msgid "" "This introduces error, typically of around 1%%." msgstr "" -#: solvespace.cpp:892 +#: solvespace.cpp:1000 #, c-format msgid "" "The total length of the selected entities is:\n" @@ -1627,36 +1716,36 @@ msgid "" "This introduces error, typically of around 1%%." msgstr "" -#: solvespace.cpp:898 +#: solvespace.cpp:1006 msgid "Bad selection for perimeter; select line segments, arcs, and curves." msgstr "" -#: solvespace.cpp:914 +#: solvespace.cpp:1022 msgid "Bad selection for trace; select a single point." msgstr "" -#: solvespace.cpp:941 +#: solvespace.cpp:1049 #, c-format msgid "Couldn't write to '%s'" msgstr "" -#: solvespace.cpp:971 +#: solvespace.cpp:1079 msgid "The mesh is self-intersecting (NOT okay, invalid)." msgstr "" -#: solvespace.cpp:972 +#: solvespace.cpp:1080 msgid "The mesh is not self-intersecting (okay, valid)." msgstr "" -#: solvespace.cpp:974 +#: solvespace.cpp:1082 msgid "The mesh has naked edges (NOT okay, invalid)." msgstr "" -#: solvespace.cpp:975 +#: solvespace.cpp:1083 msgid "The mesh is watertight (okay, valid)." msgstr "" -#: solvespace.cpp:978 +#: solvespace.cpp:1086 #, c-format msgid "" "\n" @@ -1664,7 +1753,7 @@ msgid "" "The model contains %d triangles, from %d surfaces." msgstr "" -#: solvespace.cpp:982 +#: solvespace.cpp:1090 #, c-format msgid "" "%s\n" @@ -1674,7 +1763,7 @@ msgid "" "Zero problematic edges, good.%s" msgstr "" -#: solvespace.cpp:985 +#: solvespace.cpp:1093 #, c-format msgid "" "%s\n" @@ -1684,7 +1773,7 @@ msgid "" "%d problematic edges, bad.%s" msgstr "" -#: solvespace.cpp:998 +#: solvespace.cpp:1106 #, c-format msgid "" "This is SolveSpace version %s.\n" @@ -1701,33 +1790,33 @@ msgid "" "© 2008-%d Jonathan Westhues and other authors.\n" msgstr "" -#: style.cpp:166 +#: style.cpp:185 msgid "" "Can't assign style to an entity that's derived from another entity; try assigning a style to this " "entity's parent." msgstr "" -#: style.cpp:665 +#: style.cpp:735 msgid "Style name cannot be empty" msgstr "" -#: textscreens.cpp:741 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." msgstr "" -#: textscreens.cpp:745 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." msgstr "" -#: textscreens.cpp:770 +#: textscreens.cpp:866 msgid "Group name cannot be empty" msgstr "" -#: textscreens.cpp:813 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." msgstr "" -#: textscreens.cpp:848 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "" @@ -1882,10 +1971,10 @@ msgctxt "button" msgid "&OK" msgstr "" -#: view.cpp:78 +#: view.cpp:127 msgid "Scale cannot be zero or negative." msgstr "" -#: view.cpp:90 view.cpp:99 +#: view.cpp:139 view.cpp:148 msgid "Bad format: specify x, y, z" msgstr "" diff --git a/res/win32/versioninfo.rc.in b/res/win32/versioninfo.rc.in index e6e90056a..344a4a597 100644 --- a/res/win32/versioninfo.rc.in +++ b/res/win32/versioninfo.rc.in @@ -1,6 +1,6 @@ 1 VERSIONINFO -FILEVERSION ${solvespace_VERSION_MAJOR},${solvespace_VERSION_MINOR},0,0 -PRODUCTVERSION ${solvespace_VERSION_MAJOR},${solvespace_VERSION_MINOR},0,0 +FILEVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},0,0 +PRODUCTVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},0,0 FILEFLAGSMASK 0 FILEFLAGS 0 FILEOS VOS_NT_WINDOWS32 @@ -13,12 +13,12 @@ BEGIN BEGIN VALUE "CompanyName", "The SolveSpace authors" VALUE "ProductName", "SolveSpace" - VALUE "ProductVersion", "${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}" + VALUE "ProductVersion", "${PROJECT_VERSION}~${solvespace_GIT_HASH}" VALUE "FileDescription", "SolveSpace, a parametric 2d/3d CAD" - VALUE "FileVersion", "${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}" + VALUE "FileVersion", "${PROJECT_VERSION}~${solvespace_GIT_HASH}" VALUE "OriginalFilename", "solvespace.exe" VALUE "InternalName", "solvespace" - VALUE "LegalCopyright", "(c) 2008-2021 Jonathan Westhues and other authors" + VALUE "LegalCopyright", "(c) 2008-2025 Jonathan Westhues and other authors" END END diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5ac7b41d0..d19a43b07 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,10 +2,6 @@ include(GNUInstallDirs) # configuration -include_directories( - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR}) - set(HAVE_SPACEWARE ${SPACEWARE_FOUND}) if(NOT WIN32 OR APPLE) @@ -19,301 +15,245 @@ endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) -# platform utilities - -if(APPLE) - set(util_LIBRARIES - ${APPKIT_LIBRARY}) -endif() - -# libslvs - -set(libslvs_SOURCES - util.cpp - entity.cpp - expr.cpp - constraint.cpp - constrainteq.cpp - system.cpp - platform/platform.cpp) - -set(libslvs_HEADERS - solvespace.h - platform/platform.h) - -add_library(slvs SHARED - ${libslvs_SOURCES} - ${libslvs_HEADERS} - ${util_SOURCES} - lib.cpp) - -target_compile_definitions(slvs - PRIVATE -DLIBRARY) - -target_include_directories(slvs - PUBLIC ${CMAKE_SOURCE_DIR}/include) - -target_link_libraries(slvs - ${util_LIBRARIES} - mimalloc-static) - -add_dependencies(slvs - mimalloc-static) - -set_target_properties(slvs PROPERTIES - PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/include/slvs.h - VERSION ${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR} - SOVERSION 1) - -if(NOT WIN32) - install(TARGETS slvs - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +if(USE_QT_GUI) + set(CMAKE_AUTOMOC ON) + find_package(Qt6 COMPONENTS Core Gui OpenGLWidgets Widgets REQUIRED) + # find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED) endif() # solvespace dependencies - -include_directories( +add_library(slvs_deps INTERFACE) +target_include_directories(slvs_deps SYSTEM INTERFACE ${OPENGL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ${PNG_PNG_INCLUDE_DIR} ${FREETYPE_INCLUDE_DIRS} - ${CAIRO_INCLUDE_DIRS} ${MIMALLOC_INCLUDE_DIR} - ${OpenMP_CXX_INCLUDE_DIRS}) + ${EIGEN3_INCLUDE_DIRS}) +target_link_libraries(slvs_deps INTERFACE + dxfrw + ${ZLIB_LIBRARY} + ${PNG_LIBRARY} + ${FREETYPE_LIBRARY} + mimalloc-static) + +if(NOT USE_QT_GUI OR ENABLE_CLI) + target_include_directories(slvs_deps SYSTEM INTERFACE ${CAIRO_INCLUDE_DIRS}) + target_link_libraries(slvs_deps INTERFACE ${CAIRO_LIBRARIES}) +endif() if(Backtrace_FOUND) - include_directories( + target_include_directories(slvs_deps SYSTEM INTERFACE ${Backtrace_INCLUDE_DIRS}) + target_link_libraries(slvs_deps INTERFACE + ${Backtrace_LIBRARY}) endif() if(SPACEWARE_FOUND) - include_directories( + target_include_directories(slvs_deps SYSTEM INTERFACE ${SPACEWARE_INCLUDE_DIR}) + target_link_libraries(slvs_deps INTERFACE + ${SPACEWARE_LIBRARIES}) endif() -if(OPENGL STREQUAL 3) - set(gl_SOURCES - render/gl3shader.cpp - render/rendergl3.cpp) -elseif(OPENGL STREQUAL 1) - set(gl_SOURCES - render/rendergl1.cpp) -else() - message(FATAL_ERROR "Unsupported OpenGL version ${OPENGL}") +if(ENABLE_OPENMP) + target_link_libraries(slvs_deps INTERFACE slvs_openmp) endif() -set(platform_SOURCES - ${gl_SOURCES} - platform/entrygui.cpp) - -if(WIN32) - list(APPEND platform_SOURCES - platform/guiwin.cpp) - - set(platform_LIBRARIES - comctl32 - ${SPACEWARE_LIBRARIES}) -elseif(APPLE) - add_compile_options( - -fobjc-arc) - - list(APPEND platform_SOURCES - platform/guimac.mm) -else() - list(APPEND platform_SOURCES - platform/guigtk.cpp) - - set(platform_LIBRARIES - ${SPACEWARE_LIBRARIES}) +target_compile_options(slvs_deps + INTERFACE ${COVERAGE_FLAGS}) - foreach(pkg_config_lib GTKMM JSONC FONTCONFIG) - include_directories(${${pkg_config_lib}_INCLUDE_DIRS}) - link_directories(${${pkg_config_lib}_LIBRARY_DIRS}) - list(APPEND platform_LIBRARIES ${${pkg_config_lib}_LIBRARIES}) - endforeach() +# platform utilities +if(APPLE) + target_link_libraries(slvs_deps INTERFACE + ${APPKIT_LIBRARY}) endif() -set(every_platform_SOURCES - platform/guiwin.cpp - platform/guigtk.cpp - platform/guimac.mm) - -# solvespace library - -set(solvespace_core_HEADERS +# Solver +add_library(slvs-solver INTERFACE) +target_sources(slvs-solver INTERFACE dsc.h expr.h - polygon.h - sketch.h - solvespace.h - ui.h + handle.h + param.h platform/platform.h - render/render.h - render/gl3shader.h - srf/surface.h) - -set(solvespace_core_SOURCES - bsp.cpp - clipboard.cpp - confscreen.cpp - constraint.cpp + solvespace.h + constrainteq.cpp - describescreen.cpp - draw.cpp - drawconstraint.cpp - drawentity.cpp entity.cpp - export.cpp - exportstep.cpp - exportvector.cpp expr.cpp - file.cpp - generate.cpp - graphicswin.cpp - group.cpp - groupmesh.cpp - importdxf.cpp - importidf.cpp - mesh.cpp - modify.cpp - mouse.cpp - polyline.cpp - polygon.cpp - resource.cpp - request.cpp - style.cpp + platform/platformbase.cpp system.cpp - textscreens.cpp - textwin.cpp - toolbar.cpp - ttf.cpp - undoredo.cpp util.cpp - view.cpp - platform/platform.cpp - platform/gui.cpp - render/render.cpp - render/render2d.cpp - srf/boolean.cpp - srf/curve.cpp - srf/merge.cpp - srf/ratpoly.cpp - srf/raycast.cpp - srf/surface.cpp - srf/surfinter.cpp - srf/triangulate.cpp) - -set(solvespace_core_gl_SOURCES - solvespace.cpp) - -add_library(solvespace-core STATIC - ${util_SOURCES} - ${solvespace_core_HEADERS} - ${solvespace_core_SOURCES}) - -add_dependencies(solvespace-core - mimalloc-static) - -target_link_libraries(solvespace-core - ${OpenMP_CXX_LIBRARIES} - dxfrw - ${util_LIBRARIES} - ${ZLIB_LIBRARY} - ${PNG_LIBRARY} - ${FREETYPE_LIBRARY} - mimalloc-static) - -if(Backtrace_FOUND) - target_link_libraries(solvespace-core - ${Backtrace_LIBRARY}) -endif() - -target_compile_options(solvespace-core - PRIVATE ${COVERAGE_FLAGS}) - -# solvespace translations - -if(HAVE_GETTEXT) - set(inputs - ${solvespace_core_SOURCES} - ${solvespace_core_HEADERS} - ${every_platform_SOURCES} - ${solvespace_core_gl_SOURCES}) - - set(templ_po ${CMAKE_CURRENT_BINARY_DIR}/../res/messages.po) - - set(output_pot ${CMAKE_CURRENT_SOURCE_DIR}/../res/messages.pot) - set(output_po ${CMAKE_CURRENT_SOURCE_DIR}/../res/locales/en_US.po) - file(GLOB locale_pos ${CMAKE_CURRENT_SOURCE_DIR}/../res/locales/*.po) +) +target_include_directories(slvs-solver SYSTEM INTERFACE + ${MIMALLOC_INCLUDE_DIR} + ${EIGEN3_INCLUDE_DIRS}) +target_include_directories(slvs-solver INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}) + +# Needed for CI +file(WRITE ${CMAKE_BINARY_DIR}/version.env "\ +VERSION_MAJOR=${PROJECT_VERSION_MAJOR}\n\ +VERSION_MINOR=${PROJECT_VERSION_MINOR}\n\ +VERSION_PATCH=0\n\ +VERSION_GIT_HASH=${solvespace_GIT_HASH}\n\ +") + +if(ENABLE_GUI OR ENABLE_CLI) + set(every_platform_SOURCES + platform/guiwin.cpp + platform/guigtk.cpp + platform/guimac.mm + platform/guihtml.cpp) + + # solvespace library + + set(solvespace_core_gl_SOURCES + solvespace.cpp) + + add_library(solvespace-core STATIC + polygon.h + sketch.h + ui.h + render/render.h + srf/surface.h + + bsp.cpp + clipboard.cpp + confscreen.cpp + constraint.cpp + describescreen.cpp + draw.cpp + drawconstraint.cpp + drawentity.cpp + export.cpp + exportstep.cpp + exportvector.cpp + file.cpp + generate.cpp + graphicswin.cpp + group.cpp + groupmesh.cpp + importdxf.cpp + importidf.cpp + importmesh.cpp + mesh.cpp + modify.cpp + mouse.cpp + polyline.cpp + polygon.cpp + resource.cpp + request.cpp + style.cpp + textscreens.cpp + textwin.cpp + toolbar.cpp + ttf.cpp + undoredo.cpp + view.cpp + platform/platform.cpp + platform/gui.cpp + render/render.cpp + render/render2d.cpp + srf/boolean.cpp + srf/curve.cpp + srf/merge.cpp + srf/ratpoly.cpp + srf/raycast.cpp + srf/shell.cpp + srf/surface.cpp + srf/surfinter.cpp + srf/triangulate.cpp) + + target_include_directories(solvespace-core PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR}) + target_link_libraries(solvespace-core PRIVATE slvs-solver) + target_link_libraries(solvespace-core PUBLIC slvs_deps) + + # solvespace translations + + if(HAVE_GETTEXT) + get_target_property(solvespace_core_SOURCES solvespace-core SOURCES) + set(inputs + ${solvespace_core_SOURCES} + ${every_platform_SOURCES} + ${solvespace_core_gl_SOURCES}) + + set(templ_po ${CMAKE_CURRENT_BINARY_DIR}/../res/messages.po) + + set(output_pot ${CMAKE_CURRENT_SOURCE_DIR}/../res/messages.pot) + set(output_po ${CMAKE_CURRENT_SOURCE_DIR}/../res/locales/en_US.po) + file(GLOB locale_pos ${CMAKE_CURRENT_SOURCE_DIR}/../res/locales/*.po) - string(REPLACE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} - gen_output_pot ${output_pot}.gen) - string(REPLACE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} - gen_output_po ${output_po}.gen) - foreach(locale_po ${locale_pos}) string(REPLACE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} - gen_locale_po ${locale_po}.gen) - list(APPEND gen_locale_pos ${gen_locale_po}) - endforeach() - - add_custom_command( - OUTPUT ${gen_output_pot} - COMMAND ${XGETTEXT} - --language=C++ - --keyword --keyword=_ --keyword=N_ --keyword=C_:2,1c --keyword=CN_:2,1c - --force-po --width=100 --sort-by-file - --package-name=SolveSpace - --package-version=${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR} - "--copyright-holder=the PACKAGE authors" - --msgid-bugs-address=whitequark@whitequark.org - --from-code=utf-8 --output=${gen_output_pot} ${inputs} - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${gen_output_pot} ${output_pot} - DEPENDS ${inputs} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMENT "Extracting translations" - VERBATIM) - - file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../res/locales) - - # en_US is a bit special; we pre-fill the msgstrs from msgids, instead of (as would normally - # happen) leaving them empty. - add_custom_command( - OUTPUT ${gen_output_po} - COMMAND ${MSGINIT} - --locale=en_US --no-translator - --output=${templ_po} --input=${gen_output_pot} - COMMAND ${MSGMERGE} - --force-po --no-fuzzy-matching - --output=${gen_output_po} ${output_po} ${templ_po} - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${gen_output_po} ${output_po} - DEPENDS ${gen_output_pot} - COMMENT "Updating en_US translations" - VERBATIM) - - foreach(locale_po ${locale_pos}) + gen_output_pot ${output_pot}.gen) string(REPLACE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} - gen_locale_po ${locale_po}.gen) + gen_output_po ${output_po}.gen) + foreach(locale_po ${locale_pos}) + string(REPLACE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} + gen_locale_po ${locale_po}.gen) + list(APPEND gen_locale_pos ${gen_locale_po}) + endforeach() - get_filename_component(locale_name ${locale_po} NAME_WE) - if(locale_name STREQUAL "en_US") - continue() - endif() + add_custom_command( + OUTPUT ${gen_output_pot} + COMMAND ${XGETTEXT} + --language=C++ + --keyword --keyword=_ --keyword=N_ --keyword=C_:2,1c --keyword=CN_:2,1c + --force-po --width=100 --sort-by-file + --package-name=SolveSpace + --package-version=${PROJECT_VERSION} + "--copyright-holder=the PACKAGE authors" + --msgid-bugs-address=phkahler@gmail.com + --from-code=utf-8 --output=${gen_output_pot} ${inputs} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${gen_output_pot} ${output_pot} + DEPENDS ${inputs} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Extracting translations" + VERBATIM) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../res/locales) + + # en_US is a bit special; we pre-fill the msgstrs from msgids, instead of (as would normally + # happen) leaving them empty. add_custom_command( - OUTPUT ${gen_locale_po} + OUTPUT ${gen_output_po} + COMMAND ${MSGINIT} + --locale=en_US --no-translator + --output=${templ_po} --input=${gen_output_pot} COMMAND ${MSGMERGE} - --no-fuzzy-matching - --output=${gen_locale_po} ${locale_po} ${gen_output_pot} - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${gen_locale_po} ${locale_po} + --force-po --no-fuzzy-matching + --output=${gen_output_po} ${output_po} ${templ_po} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${gen_output_po} ${output_po} DEPENDS ${gen_output_pot} - COMMENT "Updating ${locale_name} translations" + COMMENT "Updating en_US translations" VERBATIM) - endforeach() - add_custom_target(translate_solvespace - DEPENDS ${gen_output_pot} ${gen_output_po} ${gen_locale_pos}) + foreach(locale_po ${locale_pos}) + string(REPLACE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} + gen_locale_po ${locale_po}.gen) + + get_filename_component(locale_name ${locale_po} NAME_WE) + if(locale_name STREQUAL "en_US") + continue() + endif() + + add_custom_command( + OUTPUT ${gen_locale_po} + COMMAND ${MSGMERGE} + --no-fuzzy-matching + --output=${gen_locale_po} ${locale_po} ${gen_output_pot} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${gen_locale_po} ${locale_po} + DEPENDS ${gen_output_pot} + COMMENT "Updating ${locale_name} translations" + VERBATIM) + endforeach() + + add_custom_target(translate_solvespace + DEPENDS ${gen_output_pot} ${gen_output_po} ${gen_locale_pos}) + endif() endif() # solvespace graphical executable @@ -321,56 +261,201 @@ endif() if(ENABLE_GUI) add_executable(solvespace WIN32 MACOSX_BUNDLE ${solvespace_core_gl_SOURCES} - ${platform_SOURCES} $) + if(NOT USE_QT_GUI) + target_sources(solvespace PRIVATE platform/entrygui.cpp) + endif() + add_dependencies(solvespace resources) target_link_libraries(solvespace + PRIVATE solvespace-core - ${OPENGL_LIBRARIES} - ${platform_LIBRARIES} - ${COVERAGE_LIBRARY}) + ${OPENGL_LIBRARIES}) + + # OpenGL version + if(OPENGL STREQUAL 3) + target_sources(solvespace PRIVATE + render/gl3shader.cpp + render/rendergl3.cpp) + elseif(OPENGL STREQUAL 1) + target_sources(solvespace PRIVATE + render/rendergl1.cpp) + else() + message(FATAL_ERROR "Unsupported OpenGL version ${OPENGL}") + endif() - if(MSVC) - set_target_properties(solvespace PROPERTIES - LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /INCREMENTAL:NO /OPT:REF") + # Platform-specific + if(WIN32 AND NOT USE_QT_GUI) + target_sources(solvespace PRIVATE + platform/guiwin.cpp) + + target_link_libraries(solvespace PRIVATE comctl32) elseif(APPLE) + target_compile_options(solvespace PRIVATE -fobjc-arc) + target_compile_definitions(solvespace PRIVATE GL_SILENCE_DEPRECATION) + + target_sources(solvespace PRIVATE + platform/guimac.mm) set_target_properties(solvespace PROPERTIES OUTPUT_NAME SolveSpace XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES" XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.solvespace" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + elseif(EMSCRIPTEN) + # Due to the response file hack below, we need a version of Emscripten that + # passes arguments via a response file to the `file_packager` utility, but + # this was not the case prior to version 4.0.8 of Emscripten. + # See https://github.com/emscripten-core/emscripten/pull/24107 + if(NOT EMSCRIPTEN_VERSION OR EMSCRIPTEN_VERSION VERSION_LESS "4.0.8") + message(FATAL_ERROR "The Emscripten build requires at least version 4.0.8") + endif() + + set(SHELL "${CMAKE_CURRENT_SOURCE_DIR}/platform/html/emshell.html") + # Add internal quotes to paths used in the response file to ensure that paths + # that contain spaces are handled correctly + set(RESP_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_list resources SOURCES) + foreach(resource ${resource_list}) + # Strip everything before `res/...`, and map the source path to the stripped + # path in the virtual Emscripten fileystem + file(RELATIVE_PATH target_path "${CMAKE_SOURCE_DIR}" "${resource}") + list(APPEND RESP_LINK_FLAGS "--preload-file \"${resource}\"@\"${target_path}\"") + endforeach() + + if(CMAKE_BUILD_TYPE STREQUAL Debug) + list(APPEND RESP_LINK_FLAGS + --emrun --emit-symbol-map + "-s DEMANGLE_SUPPORT=1" + "-s SAFE_HEAP=1") + endif() + + target_sources(solvespace PRIVATE + platform/guihtml.cpp) + + # Pass arguments through a response file in order to avoid exceeding the maximum + # command line length (the Emscripten tools on Windows are all wrapped by batch + # scripts, and as such are subject to the CMD length limit of 8KiB). + set(resp_file_path "${CMAKE_CURRENT_BINARY_DIR}/emscripten_flags.rsp") + file(GENERATE OUTPUT "${resp_file_path}" CONTENT "$") + + # NOTE: it would be ideal if we could portably specify that linking depends on the + # response file (and the files that are specified in it), but unfortunately there + # is no way to reliably trigger a relink like that without using LINK_DEPENDS, + # which only works with the Ninja and Makefile generators. + # This means that using e.g. the Visual Studio of Xcode generators will not work + # correctly when any of the link time dependencies changes, so the output artefact + # needs to be explicitly deleted in order to trigger a relink. + set_target_properties(solvespace PROPERTIES + SUFFIX ".html" + LINK_FLAGS "@${resp_file_path}" + LINK_DEPENDS "${SHELL};${resp_file_path};${resource_list}") + + 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) + elseif(USE_QT_GUI) + set_target_properties(solvespace PROPERTIES + OUTPUT_NAME solvespace-qt) + + target_sources(solvespace PRIVATE + platform/guiqt.cpp + platform/guiqt.h) + + target_include_directories(solvespace PRIVATE SYSTEM + ${FONTCONFIG_INCLUDE_DIRS}) + target_link_directories(solvespace PRIVATE + ${FONTCONFIG_LIBRARY_DIRS}) + target_link_libraries(solvespace PRIVATE + ${FONTCONFIG_LIBRARIES} + Qt6::Core Qt6::Gui Qt6::OpenGLWidgets Qt6::Widgets + # Qt5::Core Qt5::Gui Qt5::Widgets + ) + else() + target_sources(solvespace PRIVATE + platform/guigtk.cpp) + + target_include_directories(solvespace SYSTEM PRIVATE + ${GTKMM_INCLUDE_DIRS} + ${JSONC_INCLUDE_DIRS} + ${FONTCONFIG_INCLUDE_DIRS}) + target_link_directories(solvespace PRIVATE + ${GTKMM_LIBRARY_DIRS} + ${JSONC_LIBRARY_DIRS} + ${FONTCONFIG_LIBRARY_DIRS}) + target_link_libraries(solvespace PRIVATE + ${GTKMM_LIBRARIES} + ${JSONC_LIBRARIES} + ${FONTCONFIG_LIBRARIES}) + endif() + + if(WIN32) + if(MSVC) + set_target_properties(solvespace PROPERTIES + LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /INCREMENTAL:NO /OPT:REF") + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND MSVC_VERSION) + # Hack to fix broken build with clang (not clang-cl) on Windows with CMake >= 3.22 + # which unconditionally adds "-Xlinker /MANIFEST:EMBED" to the command line + # for non-MSVC and non-MINGW compilers, and breaks the clang build where + # manifest are specified through resource files. + # See: + # https://gitlab.kitware.com/cmake/cmake/-/merge_requests/6929 + # https://gitlab.kitware.com/cmake/cmake/-/merge_requests/6534 + # https://gitlab.kitware.com/cmake/cmake/-/issues/22611 + string(REPLACE " -Xlinker /MANIFEST:EMBED" "" + CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE}") + target_link_options(solvespace PRIVATE + "SHELL:-Xlinker /MANIFEST:NO") + endif() endif() endif() +if(ENABLE_CLI) # solvespace headless library -set(headless_SOURCES - platform/guinone.cpp - render/rendercairo.cpp) - add_library(solvespace-headless STATIC EXCLUDE_FROM_ALL ${solvespace_core_gl_SOURCES} - ${headless_SOURCES}) + platform/guinone.cpp + render/rendercairo.cpp) target_compile_definitions(solvespace-headless - PRIVATE -DHEADLESS) + PRIVATE 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 - solvespace-core - ${CAIRO_LIBRARIES}) - -target_compile_options(solvespace-headless - PRIVATE ${COVERAGE_FLAGS}) + PRIVATE + solvespace-core) # solvespace command-line executable -if(ENABLE_CLI) add_executable(solvespace-cli platform/entrycli.cpp $) @@ -390,13 +475,15 @@ endif() # solvespace unix package -if(NOT (WIN32 OR APPLE)) +if(NOT (WIN32 OR APPLE OR EMSCRIPTEN)) if(ENABLE_GUI) install(TARGETS solvespace + COMPONENT solvespace RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() if(ENABLE_CLI) install(TARGETS solvespace-cli + COMPONENT solvespace RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() endif() @@ -430,4 +517,6 @@ if(APPLE) COMMENT "Bundling executable solvespace-cli" VERBATIM) endif() -endif() \ No newline at end of file +endif() + +add_subdirectory(slvs) diff --git a/src/bsp.cpp b/src/bsp.cpp index bac5ff303..50a3bb2ed 100644 --- a/src/bsp.cpp +++ b/src/bsp.cpp @@ -8,8 +8,10 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -SBsp2 *SBsp2::Alloc() { return (SBsp2 *)AllocTemporary(sizeof(SBsp2)); } -SBsp3 *SBsp3::Alloc() { return (SBsp3 *)AllocTemporary(sizeof(SBsp3)); } +namespace SolveSpace { + +SBsp2 *SBsp2::Alloc() { return (SBsp2 *)Platform::AllocTemporary(sizeof(SBsp2)); } +SBsp3 *SBsp3::Alloc() { return (SBsp3 *)Platform::AllocTemporary(sizeof(SBsp3)); } SBsp3 *SBsp3::FromMesh(const SMesh *m) { SMesh mc = {}; @@ -150,35 +152,35 @@ class BspUtil { Vector *vneg; static BspUtil *Alloc() { - return (BspUtil *)AllocTemporary(sizeof(BspUtil)); + return (BspUtil *)Platform::AllocTemporary(sizeof(BspUtil)); } void AllocOn() { - on = (Vector *)AllocTemporary(sizeof(Vector) * 2); + on = (Vector *)Platform::AllocTemporary(sizeof(Vector) * 2); } void AllocTriangle() { - btri = (STriangle *)AllocTemporary(sizeof(STriangle)); + btri = (STriangle *)Platform::AllocTemporary(sizeof(STriangle)); } void AllocTriangles() { - btri = (STriangle *)AllocTemporary(sizeof(STriangle) * 2); + btri = (STriangle *)Platform::AllocTemporary(sizeof(STriangle) * 2); ctri = &btri[1]; } void AllocQuad() { - vpos = (Vector *)AllocTemporary(sizeof(Vector) * 4); + vpos = (Vector *)Platform::AllocTemporary(sizeof(Vector) * 4); } void AllocClassify(size_t size) { // Allocate a one big piece is faster than a small ones. - isPos = (bool *)AllocTemporary(sizeof(bool) * size * 3); + isPos = (bool *)Platform::AllocTemporary(sizeof(bool) * size * 3); isNeg = &isPos[size]; isOn = &isNeg[size]; } void AllocVertices(size_t size) { - vpos = (Vector *)AllocTemporary(sizeof(Vector) * size * 2); + vpos = (Vector *)Platform::AllocTemporary(sizeof(Vector) * size * 2); vneg = &vpos[size]; } @@ -727,3 +729,5 @@ void SBsp2::InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3) { return; } + +} // namespace SolveSpace diff --git a/src/clipboard.cpp b/src/clipboard.cpp index d43ec4582..8eea1a8ce 100644 --- a/src/clipboard.cpp +++ b/src/clipboard.cpp @@ -6,6 +6,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + void SolveSpaceUI::Clipboard::Clear() { c.Clear(); r.Clear(); @@ -93,8 +95,8 @@ void GraphicsWindow::CopySelection() { if(!e->h.isFromRequest()) continue; Request *r = SK.GetRequest(e->h.request()); if(r->type != Request::Type::DATUM_POINT) continue; - EntReqTable::GetEntityInfo((Entity::Type)0, e->extraPoints, - &req, &pts, NULL, &hasDistance); + ssassert(EntReqTable::GetEntityInfo((Entity::Type)0, e->extraPoints, + &req, &pts, NULL, &hasDistance), "No entity info"); } if(req == Request::Type::WORKPLANE) continue; @@ -138,18 +140,17 @@ void GraphicsWindow::CopySelection() { } } - Constraint *c; - for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { - if(!SS.clipboard.ContainsEntity(c->ptA) || - !SS.clipboard.ContainsEntity(c->ptB) || - !SS.clipboard.ContainsEntity(c->entityA) || - !SS.clipboard.ContainsEntity(c->entityB) || - !SS.clipboard.ContainsEntity(c->entityC) || - !SS.clipboard.ContainsEntity(c->entityD) || - c->type == Constraint::Type::COMMENT) { + for(Constraint &c : SK.constraint) { + if(!SS.clipboard.ContainsEntity(c.ptA) || + !SS.clipboard.ContainsEntity(c.ptB) || + !SS.clipboard.ContainsEntity(c.entityA) || + !SS.clipboard.ContainsEntity(c.entityB) || + !SS.clipboard.ContainsEntity(c.entityC) || + !SS.clipboard.ContainsEntity(c.entityD) || + c.type == Constraint::Type::COMMENT) { continue; } - SS.clipboard.c.Add(c); + SS.clipboard.c.Add(&c); } } @@ -250,39 +251,45 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) { case Constraint::Type::COMMENT: c.disp.offset = c.disp.offset.Plus(trans); break; - case Constraint::Type::PT_PT_DISTANCE: case Constraint::Type::PT_LINE_DISTANCE: + c.valA *= scale; + break; + case Constraint::Type::PT_PT_DISTANCE: case Constraint::Type::PROJ_PT_DISTANCE: case Constraint::Type::DIAMETER: c.valA *= fabs(scale); break; case Constraint::Type::ARC_LINE_TANGENT: { - Entity *line = SK.GetEntity(c.entityB), - *arc = SK.GetEntity(c.entityA); - if(line->type == Entity::Type::ARC_OF_CIRCLE) { - swap(line, arc); + if(scale < 0) { + // When mirroring swap the end point of the arc. The reason is that arcs are + // defined as counter-clockwise from the start point to the end point. And we + // swapped its points in mapPoint above to avoid inverting it. + c.other = !c.other; } - Constraint::ConstrainArcLineTangent(&c, line, arc); break; } case Constraint::Type::CUBIC_LINE_TANGENT: { - Entity *line = SK.GetEntity(c.entityB), - *cubic = SK.GetEntity(c.entityA); - if(line->type == Entity::Type::CUBIC) { - swap(line, cubic); - } - Constraint::ConstrainCubicLineTangent(&c, line, cubic); + // Nothing to do for CUBIC unlike the ARC_OF_CIRCLE above and below. break; } case Constraint::Type::CURVE_CURVE_TANGENT: { - Entity *eA = SK.GetEntity(c.entityA), - *eB = SK.GetEntity(c.entityB); - Constraint::ConstrainCurveCurveTangent(&c, eA, eB); + if(scale < 0) { + // When mirroring swap the end points of arcs. The reason is that arcs are + // defined as counter-clockwise from the start point to the end point. And we + // swapped their points in mapPoint above to avoid inverting them. + // CUBIC splines do not need this. + if(EntityBase::Type::ARC_OF_CIRCLE == SK.GetEntity(c.entityA)->type) { + c.other = !c.other; + } + if(EntityBase::Type::ARC_OF_CIRCLE == SK.GetEntity(c.entityB)->type) { + c.other2 = !c.other2; + } + } break; } case Constraint::Type::HORIZONTAL: case Constraint::Type::VERTICAL: - // When rotating 90 or 270 degrees, swap the vertical / horizontal constaints + // When rotating 90 or 270 degrees, swap the vertical / horizontal constraints if (EXACT(fmod(theta + (PI/2), PI) == 0)) { if(c.type == Constraint::Type::HORIZONTAL) { c.type = Constraint::Type::VERTICAL; @@ -517,3 +524,4 @@ void TextWindow::ShowPasteTransformed() { Printf(true, "(or %Fl%Ll%fcancel operation%E)", &ScreenHome); } +} // namespace SolveSpace diff --git a/src/config.h.in b/src/config.h.in index 144c46d04..187b9cbce 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -1,7 +1,8 @@ #ifndef SOLVESPACE_CONFIG_H #define SOLVESPACE_CONFIG_H -#define PACKAGE_VERSION "@solvespace_VERSION_MAJOR@.@solvespace_VERSION_MINOR@~@solvespace_GIT_HASH@" +#define PACKAGE_VERSION "@PROJECT_VERSION@~@solvespace_GIT_HASH@" +#define GIT_HASH_URL "https://github.com/solvespace/solvespace/commit/@solvespace_GIT_HASH@" /* Non-OS X *nix only */ #define UNIX_DATADIR "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_DATAROOTDIR@/solvespace" diff --git a/src/confscreen.cpp b/src/confscreen.cpp index 4a4b7b23d..7cd86fdd8 100644 --- a/src/confscreen.cpp +++ b/src/confscreen.cpp @@ -9,23 +9,7 @@ #include #endif -void TextWindow::ScreenChangeLightDirection(int link, uint32_t v) { - SS.TW.ShowEditControl(8, ssprintf("%.2f, %.2f, %.2f", CO(SS.lightDir[v]))); - SS.TW.edit.meaning = Edit::LIGHT_DIRECTION; - SS.TW.edit.i = v; -} - -void TextWindow::ScreenChangeLightIntensity(int link, uint32_t v) { - SS.TW.ShowEditControl(31, ssprintf("%.2f", SS.lightIntensity[v])); - SS.TW.edit.meaning = Edit::LIGHT_INTENSITY; - SS.TW.edit.i = v; -} - -void TextWindow::ScreenChangeLightAmbient(int link, uint32_t v) { - SS.TW.ShowEditControl(31, ssprintf("%.2f", SS.ambientIntensity)); - SS.TW.edit.meaning = Edit::LIGHT_AMBIENT; - SS.TW.edit.i = 0; -} +namespace SolveSpace { void TextWindow::ScreenChangeColor(int link, uint32_t v) { SS.TW.ShowEditControlWithColorPicker(13, SS.modelColor[v]); @@ -58,13 +42,8 @@ void TextWindow::ScreenChangeExportMaxSegments(int link, uint32_t v) { SS.TW.edit.i = 1; } -void TextWindow::ScreenChangeCameraTangent(int link, uint32_t v) { - SS.TW.ShowEditControl(3, ssprintf("%.3f", 1000*SS.cameraTangent)); - SS.TW.edit.meaning = Edit::CAMERA_TANGENT; -} - void TextWindow::ScreenChangeGridSpacing(int link, uint32_t v) { - SS.TW.ShowEditControl(3, SS.MmToString(SS.gridSpacing)); + SS.TW.ShowEditControl(3, SS.MmToString(SS.gridSpacing, true)); SS.TW.edit.meaning = Edit::GRID_SPACING; } @@ -89,10 +68,19 @@ void TextWindow::ScreenChangeExportScale(int link, uint32_t v) { } void TextWindow::ScreenChangeExportOffset(int link, uint32_t v) { - SS.TW.ShowEditControl(3, SS.MmToString(SS.exportOffset)); + SS.TW.ShowEditControl(3, SS.MmToString(SS.exportOffset, true)); SS.TW.edit.meaning = Edit::EXPORT_OFFSET; } +void TextWindow::ScreenChangeArcDimDefault(int link, uint32_t v) { + SS.arcDimDefaultDiameter = !SS.arcDimDefaultDiameter; +} + +void TextWindow::ScreenChangeShowFullFilePath(int link, uint32_t v) { + SS.showFullFilePath = !SS.showFullFilePath; + SS.UpdateWindowTitles(); +} + void TextWindow::ScreenChangeFixExportColors(int link, uint32_t v) { SS.fixExportColors = !SS.fixExportColors; } @@ -115,6 +103,10 @@ void TextWindow::ScreenChangeTurntableNav(int link, uint32_t v) { } } +void TextWindow::ScreenChangeCameraNav(int link, uint32_t v) { + SS.cameraNav = !SS.cameraNav; +} + void TextWindow::ScreenChangeImmediatelyEditDimension(int link, uint32_t v) { SS.immediatelyEditDimension = !SS.immediatelyEditDimension; SS.GW.Invalidate(/*clearPersistent=*/true); @@ -171,7 +163,7 @@ void TextWindow::ScreenChangeCanvasSize(int link, uint32_t v) { } int col = 13; if(v < 10) col = 11; - SS.TW.ShowEditControl(col, SS.MmToString(d)); + SS.TW.ShowEditControl(col, SS.MmToString(d, true)); SS.TW.edit.meaning = Edit::CANVAS_SIZE; SS.TW.edit.i = v; } @@ -181,7 +173,12 @@ void TextWindow::ScreenChangeGCodeParameter(int link, uint32_t v) { switch(link) { case 'd': SS.TW.edit.meaning = Edit::G_CODE_DEPTH; - buf += SS.MmToString(SS.gCode.depth); + buf += SS.MmToString(SS.gCode.depth, true); + break; + + case 'h': + SS.TW.edit.meaning = Edit::G_CODE_SAFE_HEIGHT; + buf += SS.MmToString(SS.gCode.safeHeight, true); break; case 's': @@ -191,12 +188,12 @@ void TextWindow::ScreenChangeGCodeParameter(int link, uint32_t v) { case 'F': SS.TW.edit.meaning = Edit::G_CODE_FEED; - buf += SS.MmToString(SS.gCode.feed); + buf += SS.MmToString(SS.gCode.feed, true); break; case 'P': SS.TW.edit.meaning = Edit::G_CODE_PLUNGE_FEED; - buf += SS.MmToString(SS.gCode.plungeFeed); + buf += SS.MmToString(SS.gCode.plungeFeed, true); break; } SS.TW.ShowEditControl(14, buf); @@ -212,6 +209,11 @@ void TextWindow::ScreenChangeFindConstraintTimeout(int link, uint32_t v) { SS.TW.edit.meaning = Edit::FIND_CONSTRAINT_TIMEOUT; } +void TextWindow::ScreenChangeAnimationSpeed(int link, uint32_t v) { + SS.TW.ShowEditControl(3, std::to_string(SS.animationSpeed)); + SS.TW.edit.meaning = Edit::ANIMATION_SPEED; +} + void TextWindow::ShowConfiguration() { int i; Printf(true, "%Ft user color (r, g, b)"); @@ -227,18 +229,6 @@ void TextWindow::ShowConfiguration() { &ScreenChangeColor, i); } - Printf(false, ""); - Printf(false, "%Ft light direction intensity"); - for(i = 0; i < 2; i++) { - Printf(false, "%Bp #%d (%2,%2,%2)%Fl%D%f%Ll[c]%E " - "%2 %Fl%D%f%Ll[c]%E", - (i & 1) ? 'd' : 'a', i, - CO(SS.lightDir[i]), i, &ScreenChangeLightDirection, - SS.lightIntensity[i], i, &ScreenChangeLightIntensity); - } - Printf(false, "%Ba ambient lighting %2 %Fl%f%Ll[c]%E", - SS.ambientIntensity, &ScreenChangeLightAmbient); - Printf(false, ""); Printf(false, "%Ft chord tolerance (in percents)%E"); Printf(false, "%Ba %@ %% %Fl%Ll%f%D[change]%E; %@ mm, %d triangles", @@ -260,11 +250,6 @@ void TextWindow::ShowConfiguration() { SS.exportMaxSegments, &ScreenChangeExportMaxSegments); - Printf(false, ""); - Printf(false, "%Ft perspective factor (0 for parallel)%E"); - Printf(false, "%Ba %# %Fl%Ll%f%D[change]%E", - SS.cameraTangent*1000, - &ScreenChangeCameraTangent, 0); Printf(false, "%Ft snap grid spacing%E"); Printf(false, "%Ba %s %Fl%Ll%f%D[change]%E", SS.MmToString(SS.gridSpacing).c_str(), @@ -368,11 +353,18 @@ void TextWindow::ShowConfiguration() { Printf(false, " %Fd%f%Ll%s enable automatic line constraints%E", &ScreenChangeAutomaticLineConstraints, SS.automaticLineConstraints ? CHECK_TRUE : CHECK_FALSE); + Printf(false, " %Fd%f%Ll%s use camera mouse navigation%E", &ScreenChangeCameraNav, + SS.cameraNav ? CHECK_TRUE : CHECK_FALSE); Printf(false, " %Fd%f%Ll%s use turntable mouse navigation%E", &ScreenChangeTurntableNav, SS.turntableNav ? CHECK_TRUE : CHECK_FALSE); Printf(false, " %Fd%f%Ll%s edit newly added dimensions%E", &ScreenChangeImmediatelyEditDimension, SS.immediatelyEditDimension ? CHECK_TRUE : CHECK_FALSE); + Printf(false, " %Fd%f%Ll%s arc default is diameter%E", + &ScreenChangeArcDimDefault, + SS.arcDimDefaultDiameter ? CHECK_TRUE : CHECK_FALSE); + Printf(false, " %Fd%f%Ll%s display the full path in the title bar%E", + &ScreenChangeShowFullFilePath, SS.showFullFilePath ? CHECK_TRUE : CHECK_FALSE); Printf(false, ""); Printf(false, "%Ft autosave interval (in minutes)%E"); Printf(false, "%Ba %d %Fl%Ll%f[change]%E", @@ -381,6 +373,10 @@ void TextWindow::ShowConfiguration() { Printf(false, "%Ft redundant constraint timeout (in ms)%E"); Printf(false, "%Ba %d %Fl%Ll%f[change]%E", SS.timeoutRedundantConstr, &ScreenChangeFindConstraintTimeout); + Printf(false, ""); + Printf(false, "%Ft animation speed (in ms; 0 to disable)%E"); + Printf(false, "%Ba %d %Fl%Ll%f[change]%E", + SS.animationSpeed, &ScreenChangeAnimationSpeed); if(canvas) { const char *gl_vendor, *gl_renderer, *gl_version; @@ -459,6 +455,11 @@ bool TextWindow::EditControlDoneForConfiguration(const std::string &s) { SS.GW.Invalidate(); break; } + case Edit::EXPLODE_DISTANCE: { + SS.explodeDistance = min(1e4, max(-1e4, SS.StringToMm(s))); + SS.MarkGroupDirty(SS.GW.activeGroup, true); + break; + } case Edit::DIGITS_AFTER_DECIMAL: { int v = atoi(s.c_str()); if(v < 0 || v > 8) { @@ -527,6 +528,11 @@ bool TextWindow::EditControlDoneForConfiguration(const std::string &s) { if(e) SS.gCode.depth = (float)SS.ExprToMm(e); break; } + case Edit::G_CODE_SAFE_HEIGHT: { + Expr *e = Expr::From(s, /*popUpError=*/true); + if(e) SS.gCode.safeHeight = (float)SS.ExprToMm(e); + break; + } case Edit::G_CODE_PASSES: { Expr *e = Expr::From(s, /*popUpError=*/true); if(e) SS.gCode.passes = (int)(e->Eval()); @@ -568,9 +574,19 @@ bool TextWindow::EditControlDoneForConfiguration(const std::string &s) { } break; } + case Edit::ANIMATION_SPEED: { + int speed = atoi(s.c_str()); + if(speed >= 0) { + SS.animationSpeed = speed; + } else { + SS.animationSpeed = 800; + } + break; + } default: return false; } return true; } +} // namespace SolveSpace diff --git a/src/constraint.cpp b/src/constraint.cpp index 435fc3acb..3075d31bb 100644 --- a/src/constraint.cpp +++ b/src/constraint.cpp @@ -6,6 +6,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + std::string Constraint::DescriptionString() const { std::string s; switch(type) { @@ -22,7 +24,11 @@ std::string Constraint::DescriptionString() const { case Type::EQ_LEN_PT_LINE_D: s = C_("constr-name", "eq-length-and-pt-ln-dist"); break; case Type::EQ_PT_LN_DISTANCES: s = C_("constr-name", "eq-pt-line-distances"); break; case Type::LENGTH_RATIO: s = C_("constr-name", "length-ratio"); break; + case Type::ARC_ARC_LEN_RATIO: s = C_("constr-name", "arc-arc-length-ratio"); break; + case Type::ARC_LINE_LEN_RATIO: s = C_("constr-name", "arc-line-length-ratio"); break; case Type::LENGTH_DIFFERENCE: s = C_("constr-name", "length-difference"); break; + case Type::ARC_ARC_DIFFERENCE: s = C_("constr-name", "arc-arc-len-difference"); break; + case Type::ARC_LINE_DIFFERENCE: s = C_("constr-name", "arc-line-len-difference"); break; case Type::SYMMETRIC: s = C_("constr-name", "symmetric"); break; case Type::SYMMETRIC_HORIZ: s = C_("constr-name", "symmetric-h"); break; case Type::SYMMETRIC_VERT: s = C_("constr-name", "symmetric-v"); break; @@ -127,7 +133,8 @@ hConstraint Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) { Entity::NO_ENTITY, Entity::NO_ENTITY, /*other=*/false, /*other2=*/false); } -bool Constraint::ConstrainArcLineTangent(Constraint *c, Entity *line, Entity *arc) { +bool Constraint::ConstrainArcLineTangent(Constraint *c, Entity *line, Entity *arc, + Entity *arcendpoint) { Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), l1 = SK.GetEntity(line->point[1])->PointGetNum(); Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(), @@ -136,16 +143,32 @@ bool Constraint::ConstrainArcLineTangent(Constraint *c, Entity *line, Entity *ar c->other = false; } else if(l0.Equals(a2) || l1.Equals(a2)) { c->other = true; + } else if(nullptr != arcendpoint) { + Vector p = arcendpoint->PointGetNum(); + if(a1.Equals(p)) { + c->other = false; + } else if(a2.Equals(p)) { + c->other = true; + } else { + Error(_("The point you selected does not belong to the arc. " + "The arc and line segment do not share an end point.\n\n" + "Select the end point of the arc at which you want " + "it to be tangent to the line.")); + return false; + } } else { Error(_("The tangent arc and line segment must share an " "endpoint. Constrain them with Constrain -> " - "On Point before constraining tangent.")); + "On Point before constraining tangent.\n\n" + "Alternatively select the end point of the arc " + "at which you want it to be tangent to the line.")); return false; } return true; } -bool Constraint::ConstrainCubicLineTangent(Constraint *c, Entity *line, Entity *cubic) { +bool Constraint::ConstrainCubicLineTangent(Constraint *c, Entity *line, Entity *cubic, + Entity *curveendpoint) { Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), l1 = SK.GetEntity(line->point[1])->PointGetNum(); Vector as = cubic->CubicGetStartNum(), @@ -155,16 +178,32 @@ bool Constraint::ConstrainCubicLineTangent(Constraint *c, Entity *line, Entity * c->other = false; } else if(l0.Equals(af) || l1.Equals(af)) { c->other = true; + } else if(nullptr != curveendpoint) { + Vector p = curveendpoint->PointGetNum(); + if(as.Equals(p)) { + c->other = false; + } else if(af.Equals(p)) { + c->other = true; + } else { + Error(_("The point you selected is not an end point of the cubic spline. " + "The spline and line segment do not share an end point.\n\n" + "Select the end point of the spline at which you want " + "it to be tangent to the line.")); + return false; + } } else { - Error(_("The tangent cubic and line segment must share an " + Error(_("The tangent cubic spline and line segment must share an " "endpoint. Constrain them with Constrain -> " - "On Point before constraining tangent.")); + "On Point before constraining tangent.\n\n" + "Alternatively select the end point of the cubic spline " + "at which you want it to be tangent to the line.")); return false; } return true; } -bool Constraint::ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *eB) { +bool Constraint::ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *eB, Entity *epA, + Entity *epB) { Vector as = eA->EndpointStart(), af = eA->EndpointFinish(), bs = eB->EndpointStart(), @@ -181,16 +220,42 @@ bool Constraint::ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *e } else if(af.Equals(bf)) { c->other = true; c->other2 = true; + } else if((nullptr != epA) && (nullptr != epB)) { + Vector pa = epA->PointGetNum(), + pb = epB->PointGetNum(); + if((as.Equals(pa) && bs.Equals(pb)) || (as.Equals(pb) && bs.Equals(pa))) { + c->other = false; + c->other2 = false; + } else if((as.Equals(pa) && bf.Equals(pb)) || (as.Equals(pb) && bf.Equals(pa))) { + c->other = false; + c->other2 = true; + } else if((af.Equals(pa) && bs.Equals(pb)) || (af.Equals(pb) && bs.Equals(pa))) { + c->other = true; + c->other2 = false; + } else if((af.Equals(pa) && bf.Equals(pb)) || (af.Equals(pb) && bf.Equals(pa))) { + c->other = true; + c->other2 = true; + } else { + Error(_("The points you selected are not end points of the two curves. " + "The curves do not share an end point.\n\n" + "Select the end points of both curves at which you want " + "them to be tangent to each other.")); + return false; + } } else { Error(_("The curves must share an endpoint. Constrain them " "with Constrain -> On Point before constraining " - "tangent.")); - return false; + "tangent.\n\n" + "Alternatively select the end points of both " + "curves at which you want the curves to be tangent.")); + return false; } return true; } void Constraint::MenuConstrain(Command id) { + std::vector newcons; + Constraint c = {}; c.group = SS.GW.activeGroup; c.workplane = SS.GW.ActiveWorkplane(); @@ -230,6 +295,12 @@ void Constraint::MenuConstrain(Command id) { } else if(gs.circlesOrArcs == 1 && gs.n == 1) { c.type = Type::DIAMETER; c.entityA = gs.entity[0]; + Entity* arc = SK.GetEntity(gs.entity[0]); + if ((arc->type == EntityBase::Type::ARC_OF_CIRCLE) + && (!SS.arcDimDefaultDiameter)) + { + c.other = true; + } } else { Error(_("Bad selection for distance / diameter constraint. This " "constraint can apply to:\n\n" @@ -259,54 +330,69 @@ void Constraint::MenuConstrain(Command id) { c.valA = 0; c.ModifyToSatisfy(); AddConstraint(&c); + newcons.push_back(c); break; } case Command::ON_ENTITY: - if(gs.points == 2 && gs.n == 2) { + if(gs.points >= 2 && gs.points == gs.n) { c.type = Type::POINTS_COINCIDENT; c.ptA = gs.point[0]; - c.ptB = gs.point[1]; + for(int k = 1; k < gs.points; k++) { + c.ptB = gs.point[k]; + newcons.push_back(c); + } } else if(gs.points == 1 && gs.workplanes == 1 && gs.n == 2) { c.type = Type::PT_IN_PLANE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; + newcons.push_back(c); } else if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) { c.type = Type::PT_ON_LINE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; + newcons.push_back(c); } else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) { c.type = Type::PT_ON_CIRCLE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; - } else if(gs.points == 1 && gs.faces == 1 && gs.n == 2) { + newcons.push_back(c); + } else if(gs.points == 1 && gs.faces >= 1 && gs.n == gs.points+gs.faces) { c.type = Type::PT_ON_FACE; c.ptA = gs.point[0]; - c.entityA = gs.face[0]; + for (int k=0; k= 2 && gs.lineSegments == gs.n) { c.type = Type::EQUAL_LENGTH_LINES; c.entityA = gs.entity[0]; - c.entityB = gs.entity[1]; + for (std::vector::size_type k = 1;k < gs.entity.size(); ++k){ + c.entityB = gs.entity[k]; + newcons.push_back(c); + } } else if(gs.lineSegments == 2 && gs.points == 2 && gs.n == 4) { c.type = Type::EQ_PT_LN_DISTANCES; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.entityB = gs.entity[1]; c.ptB = gs.point[1]; + newcons.push_back(c); } else if(gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) { // The same line segment for the distances, but different // points. @@ -315,27 +401,20 @@ void Constraint::MenuConstrain(Command id) { c.ptA = gs.point[0]; c.entityB = gs.entity[0]; c.ptB = gs.point[1]; + newcons.push_back(c); } else if(gs.lineSegments == 2 && gs.points == 1 && gs.n == 3) { c.type = Type::EQ_LEN_PT_LINE_D; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; c.ptA = gs.point[0]; - } else if(gs.vectors == 4 && gs.n == 4) { - c.type = Type::EQUAL_ANGLE; - c.entityA = gs.vector[0]; - c.entityB = gs.vector[1]; - c.entityC = gs.vector[2]; - c.entityD = gs.vector[3]; - } else if(gs.vectors == 3 && gs.n == 3) { - c.type = Type::EQUAL_ANGLE; - c.entityA = gs.vector[0]; - c.entityB = gs.vector[1]; - c.entityC = gs.vector[1]; - c.entityD = gs.vector[2]; - } else if(gs.circlesOrArcs == 2 && gs.n == 2) { + newcons.push_back(c); + } else if(gs.circlesOrArcs >= 2 && gs.circlesOrArcs == gs.n) { c.type = Type::EQUAL_RADIUS; c.entityA = gs.entity[0]; - c.entityB = gs.entity[1]; + for (std::vector::size_type k = 1;k < gs.entity.size(); ++k){ + c.entityB = gs.entity[k]; + newcons.push_back(c); + } } else if(gs.arcs == 1 && gs.lineSegments == 1 && gs.n == 2) { c.type = Type::EQUAL_LINE_ARC_LEN; if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) { @@ -345,38 +424,38 @@ void Constraint::MenuConstrain(Command id) { c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } + newcons.push_back(c); } else { Error(_("Bad selection for equal length / radius constraint. " "This constraint can apply to:\n\n" - " * two line segments (equal length)\n" + " * two or more line segments (equal length)\n" " * two line segments and two points " "(equal point-line distances)\n" " * a line segment and two points " "(equal point-line distances)\n" " * a line segment, and a point and line segment " "(point-line distance equals length)\n" - " * four line segments or normals " - "(equal angle between A,B and C,D)\n" - " * three line segments or normals " - "(equal angle between A,B and B,C)\n" - " * two circles or arcs (equal radius)\n" + " * two or more circles or arcs (equal radius)\n" " * a line segment and an arc " "(line segment length equals arc length)\n")); return; } - if(c.type == Type::EQUAL_ANGLE) { - // Infer the nearest supplementary angle from the sketch. - Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(), - b1 = SK.GetEntity(c.entityB)->VectorGetNum(), - a2 = SK.GetEntity(c.entityC)->VectorGetNum(), - b2 = SK.GetEntity(c.entityD)->VectorGetNum(); - double d1 = a1.Dot(b1), d2 = a2.Dot(b2); - - if(d1*d2 < 0) { - c.other = true; + SS.UndoRemember(); + for (auto&& nc : newcons){ + if(nc.type == Type::EQUAL_ANGLE) { + // Infer the nearest supplementary angle from the sketch. + Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(), + b1 = SK.GetEntity(c.entityB)->VectorGetNum(), + a2 = SK.GetEntity(c.entityC)->VectorGetNum(), + b2 = SK.GetEntity(c.entityD)->VectorGetNum(); + double d1 = a1.Dot(b1), d2 = a2.Dot(b2); + + if(d1*d2 < 0) { + nc.other = true; + } } + AddConstraint(&nc, /*rememberForUndo=*/false); } - AddConstraint(&c); break; case Command::RATIO: @@ -384,16 +463,34 @@ void Constraint::MenuConstrain(Command id) { c.type = Type::LENGTH_RATIO; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; + } + else if(gs.arcs == 2 && gs.n == 2) { + c.type = Type::ARC_ARC_LEN_RATIO; + c.entityA = gs.entity[0]; + c.entityB = gs.entity[1]; + } + else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) { + c.type = Type::ARC_LINE_LEN_RATIO; + if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) { + c.entityA = gs.entity[1]; + c.entityB = gs.entity[0]; + } else { + c.entityA = gs.entity[0]; + c.entityB = gs.entity[1]; + } } else { Error(_("Bad selection for length ratio constraint. This " "constraint can apply to:\n\n" - " * two line segments\n")); + " * two line segments\n" + " * two arcs\n" + " * one arc and one line segment\n")); return; } c.valA = 0; c.ModifyToSatisfy(); AddConstraint(&c); + newcons.push_back(c); break; case Command::DIFFERENCE: @@ -401,16 +498,34 @@ void Constraint::MenuConstrain(Command id) { c.type = Type::LENGTH_DIFFERENCE; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; + } + else if(gs.arcs == 2 && gs.n == 2) { + c.type = Type::ARC_ARC_DIFFERENCE; + c.entityA = gs.entity[0]; + c.entityB = gs.entity[1]; + } + else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) { + c.type = Type::ARC_LINE_DIFFERENCE; + if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) { + c.entityA = gs.entity[1]; + c.entityB = gs.entity[0]; + } else { + c.entityA = gs.entity[0]; + c.entityB = gs.entity[1]; + } } else { Error(_("Bad selection for length difference constraint. This " "constraint can apply to:\n\n" - " * two line segments\n")); + " * two line segments\n" + " * two arcs\n" + " * one arc and one line segment\n")); return; } c.valA = 0; c.ModifyToSatisfy(); AddConstraint(&c); + newcons.push_back(c); break; case Command::AT_MIDPOINT: @@ -424,12 +539,15 @@ void Constraint::MenuConstrain(Command id) { SS.UndoRemember(); DeleteAllConstraintsFor(Type::PT_ON_LINE, c.entityA, c.ptA); AddConstraint(&c, /*rememberForUndo=*/false); + newcons.push_back(c); break; } else if(gs.lineSegments == 1 && gs.workplanes == 1 && gs.n == 2) { c.type = Type::AT_MIDPOINT; int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0; c.entityA = gs.entity[i]; c.entityB = gs.entity[1-i]; + AddConstraint(&c); + newcons.push_back(c); } else { Error(_("Bad selection for at midpoint constraint. This " "constraint can apply to:\n\n" @@ -439,7 +557,7 @@ void Constraint::MenuConstrain(Command id) { "(line's midpoint on plane)\n")); return; } - AddConstraint(&c); + break; case Command::SYMMETRIC: @@ -528,41 +646,47 @@ void Constraint::MenuConstrain(Command id) { DeleteAllConstraintsFor(Type::VERTICAL, (gs.entity[0]), Entity::NO_ENTITY); AddConstraint(&c, /*rememberForUndo=*/false); + newcons.push_back(c); break; } } AddConstraint(&c); + newcons.push_back(c); break; case Command::VERTICAL: case Command::HORIZONTAL: { - hEntity ha, hb; + if(id == Command::HORIZONTAL) { + c.type = Type::HORIZONTAL; + } else { + c.type = Type::VERTICAL; + } if(c.workplane == Entity::FREE_IN_3D) { Error(_("Activate a workplane (with Sketch -> In Workplane) before " "applying a horizontal or vertical constraint.")); return; } - if(gs.lineSegments == 1 && gs.n == 1) { - c.entityA = gs.entity[0]; - Entity *e = SK.GetEntity(c.entityA); - ha = e->point[0]; - hb = e->point[1]; - } else if(gs.points == 2 && gs.n == 2) { - ha = c.ptA = gs.point[0]; - hb = c.ptB = gs.point[1]; + if(gs.lineSegments > 0 && gs.lineSegments == gs.n) { + for (auto enti : gs.entity){ + c.entityA = enti; + newcons.push_back(c); + } + } else if(gs.points >= 2 && gs.n == gs.points) { + c.ptA = gs.point[0]; + for (int k = 1; kNormalForceTo(Quaternion::From(fu, fv)); } AddConstraint(&c, /*rememberForUndo=*/false); + newcons.push_back(c); break; } @@ -640,7 +765,19 @@ void Constraint::MenuConstrain(Command id) { case Command::ANGLE: case Command::REF_ANGLE: { - if(gs.vectors == 2 && gs.n == 2) { + if(gs.vectors == 3 && gs.n == 3) { + c.type = Type::EQUAL_ANGLE; + c.entityA = gs.vector[0]; + c.entityB = gs.vector[1]; + c.entityC = gs.vector[1]; + c.entityD = gs.vector[2]; + } else if(gs.vectors == 4 && gs.n == 4) { + c.type = Type::EQUAL_ANGLE; + c.entityA = gs.vector[0]; + c.entityB = gs.vector[1]; + c.entityC = gs.vector[2]; + c.entityD = gs.vector[3]; + } else if(gs.vectors == 2 && gs.n == 2) { c.type = Type::ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; @@ -648,9 +785,15 @@ void Constraint::MenuConstrain(Command id) { } else { Error(_("Bad selection for angle constraint. This constraint " "can apply to:\n\n" + "Angle between:\n" " * two line segments\n" " * a line segment and a normal\n" - " * two normals\n")); + " * two normals\n" + "\nEqual angles:\n" + " * four line segments or normals " + "(equal angle between A,B and C,D)\n" + " * three line segments or normals " + "(equal angle between A,B and B,C)\n")); return; } @@ -679,78 +822,107 @@ void Constraint::MenuConstrain(Command id) { c.ModifyToSatisfy(); AddConstraint(&c); + newcons.push_back(c); break; } 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]; + newcons.push_back(c); + } else if(gs.vectors > 1 && gs.vectors == gs.n) { c.type = Type::PARALLEL; c.entityA = gs.vector[0]; - c.entityB = gs.vector[1]; - } else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) { + for (std::vector::size_type k = 1; k < gs.vector.size();++k ){ + c.entityB = gs.vector[k]; + newcons.push_back(c); + } + } else if(gs.lineSegments == 1 && gs.arcs == 1 && + (gs.n == 2 || (gs.points == 1 && gs.n == 3))) { Entity *line = SK.GetEntity(gs.entity[0]), *arc = SK.GetEntity(gs.entity[1]); if(line->type == Entity::Type::ARC_OF_CIRCLE) { swap(line, arc); } - if(!ConstrainArcLineTangent(&c, line, arc)) { + if(!ConstrainArcLineTangent( + &c, line, arc, (1 == gs.points) ? SK.GetEntity(gs.point[0]) : nullptr)) { return; } c.type = Type::ARC_LINE_TANGENT; c.entityA = arc->h; c.entityB = line->h; - } else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) { + newcons.push_back(c); + } else if(gs.lineSegments == 1 && gs.cubics == 1 && + (gs.n == 2 || (gs.points == 1 && gs.n == 3))) { Entity *line = SK.GetEntity(gs.entity[0]), *cubic = SK.GetEntity(gs.entity[1]); if(line->type == Entity::Type::CUBIC) { swap(line, cubic); } - if(!ConstrainCubicLineTangent(&c, line, cubic)) { + if(!ConstrainCubicLineTangent( + &c, line, cubic, (1 == gs.points) ? SK.GetEntity(gs.point[0]) : nullptr)) { return; } c.type = Type::CUBIC_LINE_TANGENT; c.entityA = cubic->h; c.entityB = line->h; - } else if(gs.cubics + gs.arcs == 2 && gs.n == 2) { + newcons.push_back(c); + } else if(gs.cubics + gs.arcs == 2 && (gs.n == 2 || (gs.points == 2 && gs.n == 4))) { if(!SS.GW.LockedInWorkplane()) { Error(_("Curve-curve tangency must apply in workplane.")); return; } Entity *eA = SK.GetEntity(gs.entity[0]), *eB = SK.GetEntity(gs.entity[1]); - if(!ConstrainCurveCurveTangent(&c, eA, eB)) { + if(!ConstrainCurveCurveTangent( + &c, eA, eB, (2 == gs.points) ? SK.GetEntity(gs.point[0]) : nullptr, + (2 == gs.points) ? SK.GetEntity(gs.point[1]) : nullptr)) { return; } c.type = Type::CURVE_CURVE_TANGENT; c.entityA = eA->h; c.entityB = eB->h; + newcons.push_back(c); } else { Error(_("Bad selection for parallel / tangent constraint. This " "constraint can apply to:\n\n" - " * two line segments (parallel)\n" - " * a line segment and a normal (parallel)\n" - " * two normals (parallel)\n" + " * two faces\n" + " * two or more line segments (parallel)\n" + " * one or more line segments and one or more normals (parallel)\n" + " * two or more normals (parallel)\n" " * two line segments, arcs, or beziers, that share " - "an endpoint (tangent)\n")); - return; + "an endpoint (tangent)\n" + " * two line segments, arcs, or beziers, that do not share " + "an endpoint and the end point(s) of the curve(s) (tangent)\n")); + return; } - AddConstraint(&c); + SS.UndoRemember(); + for (auto&& nc:newcons) + AddConstraint(&nc, /*rememberForUndo=*/false); break; 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.entityA = gs.vector[0]; c.entityB = gs.vector[1]; } else { Error(_("Bad selection for perpendicular constraint. This " "constraint can apply to:\n\n" + " * two faces\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n")); return; } AddConstraint(&c); + newcons.push_back(c); break; case Command::WHERE_DRAGGED: @@ -764,45 +936,59 @@ void Constraint::MenuConstrain(Command id) { return; } AddConstraint(&c); + newcons.push_back(c); break; case Command::COMMENT: - SS.GW.pending.operation = GraphicsWindow::Pending::COMMAND; - SS.GW.pending.command = Command::COMMENT; - SS.GW.pending.description = _("click center of comment text"); - SS.ScheduleShowTW(); + if(gs.points == 1 && gs.n == 1) { + c.type = Type::COMMENT; + c.ptA = gs.point[0]; + c.group = SS.GW.activeGroup; + c.workplane = SS.GW.ActiveWorkplane(); + c.comment = _("NEW COMMENT -- DOUBLE-CLICK TO EDIT"); + AddConstraint(&c); + newcons.push_back(c); + } else { + SS.GW.pending.operation = GraphicsWindow::Pending::COMMAND; + SS.GW.pending.command = Command::COMMENT; + SS.GW.pending.description = _("click center of comment text"); + SS.ScheduleShowTW(); + } break; default: ssassert(false, "Unexpected menu ID"); } - - for(const Constraint &cc : SK.constraint) { - if(c.h != cc.h && c.Equals(cc)) { - // Oops, we already have this exact constraint. Remove the one we just added. - SK.constraint.RemoveById(c.h); - SS.GW.ClearSelection(); - // And now select the old one, to give feedback. - SS.GW.MakeSelected(cc.h); - return; + for (auto nc:newcons){ + for(const Constraint &cc : SK.constraint) { + if(nc.h != cc.h && nc.Equals(cc)) { + // Oops, we already have this exact constraint. Remove the one we just added. + SK.constraint.RemoveById(nc.h); + SS.GW.ClearSelection(); + // And now select the old one, to give feedback. + SS.GW.MakeSelected(cc.h); + return; + } } - } - if(SK.constraint.FindByIdNoOops(c.h)) { - Constraint *constraint = SK.GetConstraint(c.h); - if(SS.TestRankForGroup(c.group) == SolveResult::REDUNDANT_OKAY && - !SK.GetGroup(SS.GW.activeGroup)->allowRedundant && - constraint->HasLabel()) { - constraint->reference = true; + if(SK.constraint.FindByIdNoOops(nc.h)) { + Constraint *constraint = SK.GetConstraint(nc.h); + if(SS.TestRankForGroup(nc.group) == SolveResult::REDUNDANT_OKAY && + !SK.GetGroup(SS.GW.activeGroup)->allowRedundant && + constraint->HasLabel()) { + constraint->reference = true; + } } - } - if((id == Command::DISTANCE_DIA || id == Command::ANGLE || - id == Command::RATIO || id == Command::DIFFERENCE) && - SS.immediatelyEditDimension) { - SS.GW.EditConstraint(c.h); + if((id == Command::DISTANCE_DIA || id == Command::ANGLE || + id == Command::RATIO || id == Command::DIFFERENCE) && + SS.immediatelyEditDimension) { + SS.GW.EditConstraint(nc.h); + } } SS.GW.ClearSelection(); } #endif /* ! LIBRARY */ + +} // namespace SolveSpace diff --git a/src/constrainteq.cpp b/src/constrainteq.cpp index 965a13d07..644e48043 100644 --- a/src/constrainteq.cpp +++ b/src/constrainteq.cpp @@ -7,6 +7,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + const hConstraint ConstraintBase::NO_CONSTRAINT = { 0 }; bool ConstraintBase::HasLabel() const { @@ -18,7 +20,11 @@ bool ConstraintBase::HasLabel() const { case Type::PROJ_PT_DISTANCE: case Type::DIAMETER: case Type::LENGTH_RATIO: + case Type::ARC_ARC_LEN_RATIO: + case Type::ARC_LINE_LEN_RATIO: case Type::LENGTH_DIFFERENCE: + case Type::ARC_ARC_DIFFERENCE: + case Type::ARC_LINE_DIFFERENCE: case Type::ANGLE: case Type::COMMENT: return true; @@ -39,7 +45,11 @@ bool ConstraintBase::IsProjectible() const { case Type::EQ_PT_LN_DISTANCES: case Type::EQUAL_ANGLE: case Type::LENGTH_RATIO: + case Type::ARC_ARC_LEN_RATIO: + case Type::ARC_LINE_LEN_RATIO: case Type::LENGTH_DIFFERENCE: + case Type::ARC_ARC_DIFFERENCE: + case Type::ARC_LINE_DIFFERENCE: case Type::SYMMETRIC: case Type::SYMMETRIC_HORIZ: case Type::SYMMETRIC_VERT: @@ -239,7 +249,7 @@ void ConstraintBase::AddEq(IdList *l, const ExprVector &v, } } -void ConstraintBase::Generate(IdList *l) { +void ConstraintBase::Generate(ParamList *l) { switch(type) { case Type::PARALLEL: case Type::CUBIC_LINE_TANGENT: @@ -334,6 +344,110 @@ void ConstraintBase::GenerateEquations(IdList *l, AddEq(l, (la->Div(lb))->Minus(exA), 0); return; } + + case Type::ARC_ARC_LEN_RATIO: { + EntityBase *arc1 = SK.GetEntity(entityA), + *arc2 = SK.GetEntity(entityB); + + // And get the arc1 radius, and the cosine of its angle + EntityBase *ao1 = SK.GetEntity(arc1->point[0]), + *as1 = SK.GetEntity(arc1->point[1]), + *af1 = SK.GetEntity(arc1->point[2]); + + ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()), + aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs()); + Expr *r1 = aof1.Magnitude(); + + ExprVector n1 = arc1->Normal()->NormalExprsN(); + ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0)); + ExprVector v1 = n1.Cross(u1); + // so in our new csys, we start at (1, 0, 0) + Expr *costheta1 = aof1.Dot(u1)->Div(r1); + Expr *sintheta1 = aof1.Dot(v1)->Div(r1); + + double thetas1, thetaf1, dtheta1; + arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1); + Expr *theta1; + if(dtheta1 < 3*PI/4) { + theta1 = costheta1->ACos(); + } else if(dtheta1 < 5*PI/4) { + // As the angle crosses pi, cos theta1 is not invertible; + // so use the sine to stop blowing up + theta1 = Expr::From(PI)->Minus(sintheta1->ASin()); + } else { + theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos()); + } + + // And get the arc2 radius, and the cosine of its angle + EntityBase *ao2 = SK.GetEntity(arc2->point[0]), + *as2 = SK.GetEntity(arc2->point[1]), + *af2 = SK.GetEntity(arc2->point[2]); + + ExprVector aos2 = (as2->PointGetExprs()).Minus(ao2->PointGetExprs()), + aof2 = (af2->PointGetExprs()).Minus(ao2->PointGetExprs()); + Expr *r2 = aof2.Magnitude(); + + ExprVector n2 = arc2->Normal()->NormalExprsN(); + ExprVector u2 = aos2.WithMagnitude(Expr::From(1.0)); + ExprVector v2 = n2.Cross(u2); + // so in our new csys, we start at (1, 0, 0) + Expr *costheta2 = aof2.Dot(u2)->Div(r2); + Expr *sintheta2 = aof2.Dot(v2)->Div(r2); + + double thetas2, thetaf2, dtheta2; + arc2->ArcGetAngles(&thetas2, &thetaf2, &dtheta2); + Expr *theta2; + if(dtheta2 < 3*PI/4) { + theta2 = costheta2->ACos(); + } else if(dtheta2 < 5*PI/4) { + // As the angle crosses pi, cos theta2 is not invertible; + // so use the sine to stop blowing up + theta2 = Expr::From(PI)->Minus(sintheta2->ASin()); + } else { + theta2 = (Expr::From(2*PI))->Minus(costheta2->ACos()); + } + // And write the equation; (r1*theta1) / ( r2*theta2) = some ratio + AddEq(l, (r1->Times(theta1))->Div(r2->Times(theta2))->Minus(exA), 0); + return; + } + + case Type::ARC_LINE_LEN_RATIO: { + EntityBase *line = SK.GetEntity(entityA), + *arc1 = SK.GetEntity(entityB); + + Expr *ll = Distance(workplane, line->point[0], line->point[1]); + + // And get the arc1 radius, and the cosine of its angle + EntityBase *ao1 = SK.GetEntity(arc1->point[0]), + *as1 = SK.GetEntity(arc1->point[1]), + *af1 = SK.GetEntity(arc1->point[2]); + + ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()), + aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs()); + Expr *r1 = aof1.Magnitude(); + ExprVector n1 = arc1->Normal()->NormalExprsN(); + ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0)); + ExprVector v1 = n1.Cross(u1); + // so in our new csys, we start at (1, 0, 0) + Expr *costheta1 = aof1.Dot(u1)->Div(r1); + Expr *sintheta1 = aof1.Dot(v1)->Div(r1); + + double thetas1, thetaf1, dtheta1; + arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1); + Expr *theta1; + if(dtheta1 < 3*PI/4) { + theta1 = costheta1->ACos(); + } else if(dtheta1 < 5*PI/4) { + // As the angle crosses pi, cos theta1 is not invertible; + // so use the sine to stop blowing up + theta1 = Expr::From(PI)->Minus(sintheta1->ASin()); + } else { + theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos()); + } + // And write the equation; (r1*theta1) / ( length) = some ratio + AddEq(l, (r1->Times(theta1))->Div(ll)->Minus(exA), 0); + return; + } case Type::LENGTH_DIFFERENCE: { EntityBase *a = SK.GetEntity(entityA); @@ -343,7 +457,111 @@ void ConstraintBase::GenerateEquations(IdList *l, AddEq(l, (la->Minus(lb))->Minus(exA), 0); return; } - + + case Type::ARC_ARC_DIFFERENCE: { + EntityBase *arc1 = SK.GetEntity(entityA), + *arc2 = SK.GetEntity(entityB); + + // And get the arc1 radius, and the cosine of its angle + EntityBase *ao1 = SK.GetEntity(arc1->point[0]), + *as1 = SK.GetEntity(arc1->point[1]), + *af1 = SK.GetEntity(arc1->point[2]); + + ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()), + aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs()); + Expr *r1 = aof1.Magnitude(); + + ExprVector n1 = arc1->Normal()->NormalExprsN(); + ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0)); + ExprVector v1 = n1.Cross(u1); + // so in our new csys, we start at (1, 0, 0) + Expr *costheta1 = aof1.Dot(u1)->Div(r1); + Expr *sintheta1 = aof1.Dot(v1)->Div(r1); + + double thetas1, thetaf1, dtheta1; + arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1); + Expr *theta1; + if(dtheta1 < 3*PI/4) { + theta1 = costheta1->ACos(); + } else if(dtheta1 < 5*PI/4) { + // As the angle crosses pi, cos theta1 is not invertible; + // so use the sine to stop blowing up + theta1 = Expr::From(PI)->Minus(sintheta1->ASin()); + } else { + theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos()); + } + + // And get the arc2 radius, and the cosine of its angle + EntityBase *ao2 = SK.GetEntity(arc2->point[0]), + *as2 = SK.GetEntity(arc2->point[1]), + *af2 = SK.GetEntity(arc2->point[2]); + + ExprVector aos2 = (as2->PointGetExprs()).Minus(ao2->PointGetExprs()), + aof2 = (af2->PointGetExprs()).Minus(ao2->PointGetExprs()); + Expr *r2 = aof2.Magnitude(); + + ExprVector n2 = arc2->Normal()->NormalExprsN(); + ExprVector u2 = aos2.WithMagnitude(Expr::From(1.0)); + ExprVector v2 = n2.Cross(u2); + // so in our new csys, we start at (1, 0, 0) + Expr *costheta2 = aof2.Dot(u2)->Div(r2); + Expr *sintheta2 = aof2.Dot(v2)->Div(r2); + + double thetas2, thetaf2, dtheta2; + arc2->ArcGetAngles(&thetas2, &thetaf2, &dtheta2); + Expr *theta2; + if(dtheta2 < 3*PI/4) { + theta2 = costheta2->ACos(); + } else if(dtheta2 < 5*PI/4) { + // As the angle crosses pi, cos theta2 is not invertible; + // so use the sine to stop blowing up + theta2 = Expr::From(PI)->Minus(sintheta2->ASin()); + } else { + theta2 = (Expr::From(2*PI))->Minus(costheta2->ACos()); + } + // And write the equation; (r1*theta1) - ( r2*theta2) = some difference + AddEq(l, (r1->Times(theta1))->Minus(r2->Times(theta2))->Minus(exA), 0); + return; + } + + case Type::ARC_LINE_DIFFERENCE: { + EntityBase *line = SK.GetEntity(entityA), + *arc1 = SK.GetEntity(entityB); + + Expr *ll = Distance(workplane, line->point[0], line->point[1]); + + // And get the arc1 radius, and the cosine of its angle + EntityBase *ao1 = SK.GetEntity(arc1->point[0]), + *as1 = SK.GetEntity(arc1->point[1]), + *af1 = SK.GetEntity(arc1->point[2]); + + ExprVector aos1 = (as1->PointGetExprs()).Minus(ao1->PointGetExprs()), + aof1 = (af1->PointGetExprs()).Minus(ao1->PointGetExprs()); + Expr *r1 = aof1.Magnitude(); + ExprVector n1 = arc1->Normal()->NormalExprsN(); + ExprVector u1 = aos1.WithMagnitude(Expr::From(1.0)); + ExprVector v1 = n1.Cross(u1); + // so in our new csys, we start at (1, 0, 0) + Expr *costheta1 = aof1.Dot(u1)->Div(r1); + Expr *sintheta1 = aof1.Dot(v1)->Div(r1); + + double thetas1, thetaf1, dtheta1; + arc1->ArcGetAngles(&thetas1, &thetaf1, &dtheta1); + Expr *theta1; + if(dtheta1 < 3*PI/4) { + theta1 = costheta1->ACos(); + } else if(dtheta1 < 5*PI/4) { + // As the angle crosses pi, cos theta1 is not invertible; + // so use the sine to stop blowing up + theta1 = Expr::From(PI)->Minus(sintheta1->ASin()); + } else { + theta1 = (Expr::From(2*PI))->Minus(costheta1->ACos()); + } + // And write the equation; (r1*theta1) - ( length) = some difference + AddEq(l, (r1->Times(theta1))->Minus(ll)->Minus(exA), 0); + return; + } + case Type::DIAMETER: { EntityBase *circle = SK.GetEntity(entityA); Expr *r = circle->CircleGetRadiusExpr(); @@ -511,7 +729,7 @@ void ConstraintBase::GenerateEquations(IdList *l, } } return; - + case Type::SYMMETRIC: if(workplane == EntityBase::FREE_IN_3D) { EntityBase *plane = SK.GetEntity(entityA); @@ -841,3 +1059,4 @@ void ConstraintBase::GenerateEquations(IdList *l, ssassert(false, "Unexpected constraint ID"); } +} // namespace SolveSpace diff --git a/src/defs.h b/src/defs.h new file mode 100644 index 000000000..94105f951 --- /dev/null +++ b/src/defs.h @@ -0,0 +1,15 @@ +#ifndef SOLVESPACE_DEFS_H +#define SOLVESPACE_DEFS_H + +namespace SolveSpace { + +static constexpr double ANGLE_COS_EPS = 1e-6; +static constexpr double LENGTH_EPS = 1e-6; +static constexpr double VERY_POSITIVE = 1e10; +static constexpr double VERY_NEGATIVE = -1e10; + +static constexpr double PI = 3.1415926535897931; + +} // namespace SolveSpace + +#endif // !SOLVESPACE_DEFS_H diff --git a/src/describescreen.cpp b/src/describescreen.cpp index 2c36d9881..02a308af8 100644 --- a/src/describescreen.cpp +++ b/src/describescreen.cpp @@ -6,6 +6,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + void TextWindow::ScreenUnselectAll(int link, uint32_t v) { GraphicsWindow::MenuEdit(Command::UNSELECT_ALL); } @@ -19,6 +21,17 @@ void TextWindow::ScreenEditTtfText(int link, uint32_t v) { SS.TW.edit.request = hr; } +void TextWindow::ScreenToggleTtfKerning(int link, uint32_t v) { + hRequest hr = { v }; + Request *r = SK.GetRequest(hr); + + SS.UndoRemember(); + r->extraPoints = !r->extraPoints; + + SS.MarkGroupDirty(r->group); + SS.ScheduleShowTW(); +} + void TextWindow::ScreenSetTtfFont(int link, uint32_t v) { int i = (int)v; if(i < 0) return; @@ -64,17 +77,36 @@ void TextWindow::ScreenConstraintShowAsRadius(int link, uint32_t v) { void TextWindow::DescribeSelection() { Printf(false, ""); +#define COSTR_NO_LINK(p) \ + SS.MmToString((p).x).c_str(), \ + SS.MmToString((p).y).c_str(), \ + SS.MmToString((p).z).c_str() +#define PT_AS_STR_NO_LINK "(%Fi%s%Fd, %Fi%s%Fd, %Fi%s%Fd)" +#define PT_AS_NUM "(%Fi%3%Fd, %Fi%3%Fd, %Fi%3%Fd)" +#define COSTR(e, p) \ + e->h, (&TextWindow::ScreenSelectEntity), (&TextWindow::ScreenHoverEntity), \ + COSTR_NO_LINK(p) +#define PT_AS_STR "%Ll%D%f%h" PT_AS_STR_NO_LINK "%E" +#define CO_LINK(e, p) e->h, (&TextWindow::ScreenSelectEntity), (&TextWindow::ScreenHoverEntity), CO(p) +#define PT_AS_NUM_LINK "%Ll%D%f%h" PT_AS_NUM "%E" + auto const &gs = SS.GW.gs; + + auto ListFaces = [&]() { + char abc = 'A'; + for(auto &fc : gs.face) { + Vector n = SK.GetEntity(fc)->FaceGetNormalNum(); + Printf(true, " plane%c normal = " PT_AS_NUM, abc, CO(n)); + Vector p = SK.GetEntity(fc)->FaceGetPointNum(); + Printf(false, " plane%c thru = " PT_AS_STR, abc, COSTR(SK.GetEntity(fc), p)); + ++abc; + } + }; + if(gs.n == 1 && (gs.points == 1 || gs.entities == 1)) { Entity *e = SK.GetEntity(gs.points == 1 ? gs.point[0] : gs.entity[0]); Vector p; -#define COSTR(p) \ - SS.MmToString((p).x).c_str(), \ - SS.MmToString((p).y).c_str(), \ - SS.MmToString((p).z).c_str() -#define PT_AS_STR "(%Fi%s%E, %Fi%s%E, %Fi%s%E)" -#define PT_AS_NUM "(%Fi%3%E, %Fi%3%E, %Fi%3%E)" switch(e->type) { case Entity::Type::POINT_IN_3D: case Entity::Type::POINT_IN_2D: @@ -82,8 +114,9 @@ void TextWindow::DescribeSelection() { case Entity::Type::POINT_N_ROT_TRANS: case Entity::Type::POINT_N_COPY: case Entity::Type::POINT_N_ROT_AA: + case Entity::Type::POINT_N_ROT_AXIS_TRANS: p = e->PointGetNum(); - Printf(false, "%FtPOINT%E at " PT_AS_STR, COSTR(p)); + Printf(false, "%FtPOINT%E at " PT_AS_STR, COSTR(e, p)); break; case Entity::Type::NORMAL_IN_3D: @@ -104,20 +137,20 @@ void TextWindow::DescribeSelection() { case Entity::Type::WORKPLANE: { p = SK.GetEntity(e->point[0])->PointGetNum(); Printf(false, "%FtWORKPLANE%E"); - Printf(true, " origin = " PT_AS_STR, COSTR(p)); + Printf(true, " origin = " PT_AS_STR, COSTR(SK.GetEntity(e->point[0]), p)); Quaternion q = e->Normal()->NormalGetNum(); p = q.RotationN(); - Printf(true, " normal = " PT_AS_NUM, CO(p)); + Printf(true, " normal = " PT_AS_NUM_LINK, CO_LINK(e->Normal(), p)); break; } case Entity::Type::LINE_SEGMENT: { Vector p0 = SK.GetEntity(e->point[0])->PointGetNum(); p = p0; Printf(false, "%FtLINE SEGMENT%E"); - Printf(true, " thru " PT_AS_STR, COSTR(p)); + Printf(true, " thru " PT_AS_STR, COSTR(SK.GetEntity(e->point[0]), p)); Vector p1 = SK.GetEntity(e->point[1])->PointGetNum(); p = p1; - Printf(false, " " PT_AS_STR, COSTR(p)); + Printf(false, " " PT_AS_STR, COSTR(SK.GetEntity(e->point[1]), p)); Printf(true, " len = %Fi%s%E", SS.MmToString((p1.Minus(p0).Magnitude())).c_str()); break; @@ -137,18 +170,18 @@ void TextWindow::DescribeSelection() { } for(int i = 0; i < pts; i++) { p = SK.GetEntity(e->point[i])->PointGetNum(); - Printf((i==0), " p%d = " PT_AS_STR, i, COSTR(p)); + Printf((i==0), " p%d = " PT_AS_STR, i, COSTR(SK.GetEntity(e->point[i]), p)); } break; case Entity::Type::ARC_OF_CIRCLE: { Printf(false, "%FtARC OF A CIRCLE%E"); p = SK.GetEntity(e->point[0])->PointGetNum(); - Printf(true, " center = " PT_AS_STR, COSTR(p)); + Printf(true, " center = " PT_AS_STR, COSTR(SK.GetEntity(e->point[0]), p)); p = SK.GetEntity(e->point[1])->PointGetNum(); - Printf(true, " endpoints = " PT_AS_STR, COSTR(p)); + Printf(true, " endpoints = " PT_AS_STR, COSTR(SK.GetEntity(e->point[1]), p)); p = SK.GetEntity(e->point[2])->PointGetNum(); - Printf(false, " " PT_AS_STR, COSTR(p)); + Printf(false, " " PT_AS_STR, COSTR(SK.GetEntity(e->point[2]), p)); double r = e->CircleGetRadiusNum(); Printf(true, " diameter = %Fi%s", SS.MmToString(r*2).c_str()); Printf(false, " radius = %Fi%s", SS.MmToString(r).c_str()); @@ -160,10 +193,11 @@ void TextWindow::DescribeSelection() { case Entity::Type::CIRCLE: { Printf(false, "%FtCIRCLE%E"); p = SK.GetEntity(e->point[0])->PointGetNum(); - Printf(true, " center = " PT_AS_STR, COSTR(p)); + Printf(true, " center = " PT_AS_STR, COSTR(SK.GetEntity(e->point[0]), p)); double r = e->CircleGetRadiusNum(); - Printf(true, " diameter = %Fi%s", SS.MmToString(r*2).c_str()); - Printf(false, " radius = %Fi%s", SS.MmToString(r).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, " circumference = %Fi%s", SS.MmToString(2*M_PI*r).c_str()); break; } case Entity::Type::FACE_NORMAL_PT: @@ -171,19 +205,24 @@ void TextWindow::DescribeSelection() { case Entity::Type::FACE_N_ROT_TRANS: case Entity::Type::FACE_N_ROT_AA: case Entity::Type::FACE_N_TRANS: - Printf(false, "%FtPLANE FACE%E"); + case Entity::Type::FACE_ROT_NORMAL_PT: + case Entity::Type::FACE_N_ROT_AXIS_TRANS: + Printf(false, "%FtPLANE FACE%E"); p = e->FaceGetNormalNum(); Printf(true, " normal = " PT_AS_NUM, CO(p)); p = e->FaceGetPointNum(); - Printf(false, " thru = " PT_AS_STR, COSTR(p)); + Printf(false, " thru = " PT_AS_STR, COSTR(e, p)); break; case Entity::Type::TTF_TEXT: { Printf(false, "%FtTRUETYPE FONT TEXT%E"); Printf(true, " font = '%Fi%s%E'", e->font.c_str()); if(e->h.isFromRequest()) { - Printf(false, " text = '%Fi%s%E' %Fl%Ll%f%D[change]%E", + Printf(true, " text = '%Fi%s%E' %Fl%Ll%f%D[change]%E", e->str.c_str(), &ScreenEditTtfText, e->h.request().v); + Printf(true, " %Fd%f%D%Ll%s apply kerning", + &ScreenToggleTtfKerning, e->h.request().v, + e->extraPoints ? CHECK_TRUE : CHECK_FALSE); Printf(true, " select new font"); SS.fonts.LoadAll(); // Not using range-for here because we use i inside the output. @@ -315,12 +354,12 @@ void TextWindow::DescribeSelection() { } else if(gs.n == 2 && gs.points == 2) { Printf(false, "%FtTWO POINTS"); Vector p0 = SK.GetEntity(gs.point[0])->PointGetNum(); - Printf(true, " at " PT_AS_STR, COSTR(p0)); + Printf(true, " at " PT_AS_STR, COSTR(SK.GetEntity(gs.point[0]), p0)); Vector p1 = SK.GetEntity(gs.point[1])->PointGetNum(); - Printf(false, " " PT_AS_STR, COSTR(p1)); + Printf(false, " " PT_AS_STR, COSTR(SK.GetEntity(gs.point[1]), p1)); Vector dv = p1.Minus(p0); Printf(true, " d = %Fi%s", SS.MmToString(dv.Magnitude()).c_str()); - Printf(false, " d(x, y, z) = " PT_AS_STR, COSTR(dv)); + Printf(false, " d(x, y, z) = " PT_AS_STR_NO_LINK, COSTR_NO_LINK(dv)); } else if(gs.n == 2 && gs.points == 1 && gs.circlesOrArcs == 1) { Entity *ec = SK.GetEntity(gs.entity[0]); if(ec->type == Entity::Type::CIRCLE) { @@ -329,9 +368,9 @@ void TextWindow::DescribeSelection() { Printf(false, "%FtPOINT AND AN ARC"); } else ssassert(false, "Unexpected entity type"); Vector p = SK.GetEntity(gs.point[0])->PointGetNum(); - Printf(true, " pt at " PT_AS_STR, COSTR(p)); + Printf(true, " pt at " PT_AS_STR, COSTR(SK.GetEntity(gs.point[0]), p)); Vector c = SK.GetEntity(ec->point[0])->PointGetNum(); - Printf(true, " center = " PT_AS_STR, COSTR(c)); + Printf(true, " center = " PT_AS_STR, COSTR(SK.GetEntity(ec->point[0]), c)); double r = ec->CircleGetRadiusNum(); Printf(false, " diameter = %Fi%s", SS.MmToString(r*2).c_str()); Printf(false, " radius = %Fi%s", SS.MmToString(r).c_str()); @@ -340,22 +379,22 @@ void TextWindow::DescribeSelection() { } else if(gs.n == 2 && gs.faces == 1 && gs.points == 1) { Printf(false, "%FtA POINT AND A PLANE FACE"); Vector pt = SK.GetEntity(gs.point[0])->PointGetNum(); - Printf(true, " point = " PT_AS_STR, COSTR(pt)); + Printf(true, " point = " PT_AS_STR, COSTR(SK.GetEntity(gs.point[0]), pt)); Vector n = SK.GetEntity(gs.face[0])->FaceGetNormalNum(); Printf(true, " plane normal = " PT_AS_NUM, CO(n)); Vector pl = SK.GetEntity(gs.face[0])->FaceGetPointNum(); - Printf(false, " plane thru = " PT_AS_STR, COSTR(pl)); + Printf(false, " plane thru = " PT_AS_STR, COSTR(SK.GetEntity(gs.face[0]), pl)); double dd = n.Dot(pl) - n.Dot(pt); Printf(true, " distance = %Fi%s", SS.MmToString(dd).c_str()); } else if(gs.n == 3 && gs.points == 2 && gs.vectors == 1) { Printf(false, "%FtTWO POINTS AND A VECTOR"); Vector p0 = SK.GetEntity(gs.point[0])->PointGetNum(); - Printf(true, " pointA = " PT_AS_STR, COSTR(p0)); + Printf(true, " pointA = " PT_AS_STR, COSTR(SK.GetEntity(gs.point[0]), p0)); Vector p1 = SK.GetEntity(gs.point[1])->PointGetNum(); - Printf(false, " pointB = " PT_AS_STR, COSTR(p1)); + Printf(false, " pointB = " PT_AS_STR, COSTR(SK.GetEntity(gs.point[1]), p1)); Vector v = SK.GetEntity(gs.vector[0])->VectorGetNum(); v = v.WithMagnitude(1); - Printf(true, " vector = " PT_AS_NUM, CO(v)); + Printf(true, " vector = " PT_AS_NUM_LINK, CO_LINK(SK.GetEntity(gs.vector[0]), v)); double d = (p1.Minus(p0)).Dot(v); Printf(true, " proj_d = %Fi%s", SS.MmToString(d).c_str()); } else if(gs.n == 2 && gs.lineSegments == 1 && gs.points == 1) { @@ -363,11 +402,11 @@ void TextWindow::DescribeSelection() { Vector lp0 = SK.GetEntity(ln->point[0])->PointGetNum(), lp1 = SK.GetEntity(ln->point[1])->PointGetNum(); Printf(false, "%FtLINE SEGMENT AND POINT%E"); - Printf(true, " ln thru " PT_AS_STR, COSTR(lp0)); - Printf(false, " " PT_AS_STR, COSTR(lp1)); + Printf(true, " ln thru " PT_AS_STR, COSTR(SK.GetEntity(ln->point[0]), lp0)); + Printf(false, " " PT_AS_STR, COSTR(SK.GetEntity(ln->point[1]), lp1)); Entity *p = SK.GetEntity(gs.point[0]); Vector pp = p->PointGetNum(); - Printf(true, " point " PT_AS_STR, COSTR(pp)); + Printf(true, " point " PT_AS_STR, COSTR(p, pp)); Printf(true, " pt-ln distance = %Fi%s%E", SS.MmToString(pp.DistanceToLine(lp0, lp1.Minus(lp0))).c_str()); hEntity wrkpl = SS.GW.ActiveWorkplane(); @@ -386,8 +425,8 @@ void TextWindow::DescribeSelection() { v0 = v0.WithMagnitude(1); v1 = v1.WithMagnitude(1); - Printf(true, " vectorA = " PT_AS_NUM, CO(v0)); - Printf(false, " vectorB = " PT_AS_NUM, CO(v1)); + Printf(true, " vectorA = " PT_AS_NUM_LINK, CO_LINK(SK.GetEntity(gs.entity[0]), v0)); + Printf(false, " vectorB = " PT_AS_NUM_LINK, CO_LINK(SK.GetEntity(gs.entity[1]), v1)); double theta = acos(v0.Dot(v1)); Printf(true, " angle = %Fi%2%E degrees", theta*180/PI); @@ -397,15 +436,10 @@ void TextWindow::DescribeSelection() { } else if(gs.n == 2 && gs.faces == 2) { Printf(false, "%FtTWO PLANE FACES"); - Vector n0 = SK.GetEntity(gs.face[0])->FaceGetNormalNum(); - Printf(true, " planeA normal = " PT_AS_NUM, CO(n0)); - Vector p0 = SK.GetEntity(gs.face[0])->FaceGetPointNum(); - Printf(false, " planeA thru = " PT_AS_STR, COSTR(p0)); + ListFaces(); + Vector n0 = SK.GetEntity(gs.face[0])->FaceGetNormalNum(); Vector n1 = SK.GetEntity(gs.face[1])->FaceGetNormalNum(); - Printf(true, " planeB normal = " PT_AS_NUM, CO(n1)); - Vector p1 = SK.GetEntity(gs.face[1])->FaceGetPointNum(); - Printf(false, " planeB thru = " PT_AS_STR, COSTR(p1)); double theta = acos(n0.Dot(n1)); Printf(true, " angle = %Fi%2%E degrees", theta*180/PI); @@ -414,17 +448,32 @@ void TextWindow::DescribeSelection() { Printf(false, " or angle = %Fi%2%E (mod 180)", theta*180/PI); if(fabs(theta) < 0.01) { - double d = (p1.Minus(p0)).Dot(n0); + Vector p0 = SK.GetEntity(gs.face[0])->FaceGetPointNum(); + Vector p1 = SK.GetEntity(gs.face[1])->FaceGetPointNum(); + + double d = (p1.Minus(p0)).Dot(n0); 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 == 3 && gs.faces == 3) { + Printf(false, "%FtTHREE PLANE FACES"); + + ListFaces(); + + // We should probably compute and show the intersection point if there is one. + } else if(gs.n == 0 && gs.constraints == 1) { Constraint *c = SK.GetConstraint(gs.constraint[0]); const std::string &desc = c->DescriptionString().c_str(); if(c->type == Constraint::Type::COMMENT) { 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()) { if(c->reference) { Printf(false, "%FtREFERENCE%E %s", desc.c_str()); @@ -537,3 +586,4 @@ void TextWindow::GoToScreen(Screen screen) { shown.screen = screen; } +} // namespace SolveSpace diff --git a/src/draw.cpp b/src/draw.cpp index ccaeb961d..65603ddad 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -7,6 +7,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + bool GraphicsWindow::Selection::Equals(Selection *b) { if(entity != b->entity) return false; if(constraint != b->constraint) return false; @@ -69,9 +71,9 @@ void GraphicsWindow::Selection::Draw(bool isHovered, Canvas *canvas) { Vector topLeft = camera.UnProjectPoint(topLeftScreen); auto it = std::unique(refs.begin(), refs.end(), - [](Vector a, Vector b) { return a.Equals(b); }); + [](const Vector &a, const Vector &b) { return a.Equals(b); }); refs.erase(it, refs.end()); - for(Vector p : refs) { + for(const Vector &p : refs) { canvas->DrawLine(topLeft, p, hcsEmphasis); } } @@ -185,15 +187,17 @@ void GraphicsWindow::MakeSelected(Selection *stog) { if(stog->entity.v != 0 && SK.GetEntity(stog->entity)->IsFace()) { // In the interest of speed for the triangle drawing code, - // only two faces may be selected at a time. - int c = 0; + // only MAX_SELECTABLE_FACES faces may be selected at a time. + unsigned int c = 0; Selection *s; selection.ClearTags(); for(s = selection.First(); s; s = selection.NextAfter(s)) { hEntity he = s->entity; if(he.v != 0 && SK.GetEntity(he)->IsFace()) { c++; - if(c >= 2) s->tag = 1; + // See also GraphicsWindow::GroupSelection "if(e->IsFace())" + // and Group::DrawMesh "case DrawMeshAs::SELECTED:" + if(c >= MAX_SELECTABLE_FACES) s->tag = 1; } } selection.RemoveTagged(); @@ -210,22 +214,36 @@ void GraphicsWindow::SelectByMarquee() { BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, VERY_NEGATIVE), Vector::From(orig.mouse.x, orig.mouse.y, VERY_POSITIVE)); - Entity *e; - for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - if(e->group != SS.GW.activeGroup) continue; - if(e->IsFace() || e->IsDistance()) continue; - if(!e->IsVisible()) continue; + for(Entity &e : SK.entity) { + if(e.group != SS.GW.activeGroup) continue; + if(e.IsFace() || e.IsDistance()) continue; + if(!e.IsVisible()) continue; bool entityHasBBox; - BBox entityBBox = e->GetOrGenerateScreenBBox(&entityHasBBox); + BBox entityBBox = e.GetOrGenerateScreenBBox(&entityHasBBox); if(entityHasBBox && entityBBox.Overlaps(marqueeBBox)) { - MakeSelected(e->h); + if(e.type == Entity::Type::LINE_SEGMENT) { + Vector p0 = SS.GW.ProjectPoint3(e.EndpointStart()); + Vector p1 = SS.GW.ProjectPoint3(e.EndpointFinish()); + if((!marqueeBBox.Contains({p0.x, p0.y}, 0)) && + (!marqueeBBox.Contains({p1.x, p1.y}, 0))) { + // The selection marquee does not contain either of the line segment end points. + // This means that either the segment is entirely outside the marquee or that + // it intersects it. Check if it does... + if(!Vector::BoundingBoxIntersectsLine(marqueeBBox.maxp, marqueeBBox.minp, p0, + p1, true)) { + // ... it does not so it is outside. + continue; + } + } + } + MakeSelected(e.h); } } } //----------------------------------------------------------------------------- -// Sort the selection according to various critieria: the entities and +// Sort the selection according to various criteria: the entities and // constraints separately, counts of certain types of entities (circles, // lines, etc.), and so on. //----------------------------------------------------------------------------- @@ -412,8 +430,8 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) { cached.projRight = projRight; cached.projUp = projUp; cached.scale = scale; - for(Entity *e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - e->screenBBoxValid = false; + for(Entity &e : SK.entity) { + e.screenBBoxValid = false; } } @@ -484,6 +502,9 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) { uint32_t v = m->FirstIntersectionWith(mp); if(v) { sel.entity.v = v; + Hover hov = {}; + hov.selection = sel; + hoverList.Add(&hov); } } } @@ -936,3 +957,5 @@ void GraphicsWindow::Invalidate(bool clearPersistent) { window->Invalidate(); } } + +} // namespace SolveSpace diff --git a/src/drawconstraint.cpp b/src/drawconstraint.cpp index 2cdb9afce..bf7d38937 100644 --- a/src/drawconstraint.cpp +++ b/src/drawconstraint.cpp @@ -8,11 +8,13 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + std::string Constraint::Label() const { std::string result; if(type == Type::ANGLE) { result = SS.DegreeToString(valA) + "°"; - } else if(type == Type::LENGTH_RATIO) { + } else if(type == Type::LENGTH_RATIO || type == Type::ARC_ARC_LEN_RATIO || type == Type::ARC_LINE_LEN_RATIO) { result = ssprintf("%.3f:1", valA); } else if(type == Type::COMMENT) { result = comment; @@ -267,7 +269,7 @@ void Constraint::DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs, const Camera &camera = canvas->GetCamera(); Entity *circ = SK.GetEntity(he); - Vector center = SK.GetEntity(circ->point[0])->PointGetNum(); + Vector center = SK.GetEntity(circ->point[0])->PointGetDrawNum(); double r = circ->CircleGetRadiusNum(); Quaternion q = circ->Normal()->NormalGetNum(); Vector u = q.RotationU(), v = q.RotationV(); @@ -291,7 +293,8 @@ void Constraint::DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs, void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs, Vector a0, Vector da, Vector b0, Vector db, - Vector offset, Vector *ref, bool trim) + Vector offset, Vector *ref, bool trim, + Vector explodeOffset) { const Camera &camera = canvas->GetCamera(); double pixels = 1.0 / camera.scale; @@ -305,6 +308,9 @@ void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs, db = db.ProjectVectorInto(workplane); } + a0 = a0.Plus(explodeOffset); + b0 = b0.Plus(explodeOffset); + Vector a1 = a0.Plus(da); Vector b1 = b0.Plus(db); @@ -445,21 +451,38 @@ void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs, } bool Constraint::IsVisible() const { - if(!SS.GW.showConstraints) return false; - Group *g = SK.GetGroup(group); - // If the group is hidden, then the constraints are hidden and not - // able to be selected. - if(!(g->visible)) return false; - // And likewise if the group is not the active group; except for comments - // with an assigned style. - if(g->h != SS.GW.activeGroup && !(type == Type::COMMENT && disp.style.v)) { + if(SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_NOSHOW) return false; + bool isDim = false; + + if(SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_SHOW_DIM) + switch(type) { + case ConstraintBase::Type::ANGLE: + case ConstraintBase::Type::DIAMETER: + case ConstraintBase::Type::PT_PT_DISTANCE: + case ConstraintBase::Type::PT_FACE_DISTANCE: + case ConstraintBase::Type::PT_LINE_DISTANCE: + case ConstraintBase::Type::PT_PLANE_DISTANCE: isDim = true; break; + default:; + } + + if(SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_SHOW_ALL || isDim ) { + Group *g = SK.GetGroup(group); + // If the group is hidden, then the constraints are hidden and not + // able to be selected. + if(!(g->visible)) return false; + // And likewise if the group is not the active group; except for comments + // with an assigned style. + if(g->h != SS.GW.activeGroup && !(type == Type::COMMENT && disp.style.v)) { + return false; + } + if(disp.style.v) { + Style *s = Style::Get(disp.style); + if(!s->visible) return false; + } + return true; } - if(disp.style.v) { - Style *s = Style::Get(disp.style); - if(!s->visible) return false; - } - return true; + return false; } bool Constraint::DoLineExtend(Canvas *canvas, Canvas::hStroke hcs, @@ -534,6 +557,15 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, DoProjectedPoint(canvas, hcs, &bp); } + if(ShouldDrawExploded()) { + // Offset A and B by the same offset so the constraint is drawn + // in the plane of one of the exploded points (rather than at an + // angle) + Vector offset = SK.GetEntity(ptA)->ExplodeOffset(); + ap = ap.Plus(offset); + bp = bp.Plus(offset); + } + Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset); if(refs) refs->push_back(ref); @@ -548,6 +580,19 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, dp = (bp.Minus(ap)), pp = SK.GetEntity(entityA)->VectorGetNum(); + if(ShouldDrawExploded()) { + // explode for whichever point is in the workplane (or the first if both are) + Entity *pt = SK.GetEntity(ptA); + if(pt->group != group) { + pt = SK.GetEntity(ptB); + } + if(pt->group == group) { + Vector offset = pt->ExplodeOffset(); + ap = ap.Plus(offset); + bp = bp.Plus(offset); + } + } + Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset); if(refs) refs->push_back(ref); @@ -564,7 +609,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, case Type::PT_FACE_DISTANCE: case Type::PT_PLANE_DISTANCE: { - Vector pt = SK.GetEntity(ptA)->PointGetNum(); + Vector pt = SK.GetEntity(ptA)->PointGetDrawNum(); Entity *enta = SK.GetEntity(entityA); Vector n, p; if(type == Type::PT_PLANE_DISTANCE) { @@ -590,7 +635,8 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, } case Type::PT_LINE_DISTANCE: { - Vector pt = SK.GetEntity(ptA)->PointGetNum(); + Entity *ptEntity = SK.GetEntity(ptA); + Vector pt = ptEntity->PointGetNum(); Entity *line = SK.GetEntity(entityA); Vector lA = SK.GetEntity(line->point[0])->PointGetNum(); Vector lB = SK.GetEntity(line->point[1])->PointGetNum(); @@ -602,6 +648,19 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, DoProjectedPoint(canvas, hcs, &pt); } + // Only explode if the point and line are in the same group (and that group is a sketch + // with explode enabled) otherwise it's too visually confusing to figure out what the + // correct projections should be. + bool shouldExplode = ShouldDrawExploded() + && ptEntity->group == group + && line->group == group; + if(shouldExplode) { + Vector explodeOffset = ptEntity->ExplodeOffset(); + pt = pt.Plus(explodeOffset); + lA = lA.Plus(explodeOffset); + lB = lB.Plus(explodeOffset); + } + // Find the closest point on the line Vector closest = pt.ClosestPointOnLine(lA, dl); @@ -655,7 +714,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, case Type::DIAMETER: { Entity *circle = SK.GetEntity(entityA); - Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); + Vector center = SK.GetEntity(circle->point[0])->PointGetDrawNum(); Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum(); Vector n = q.RotationN().WithMagnitude(1); double r = circle->CircleGetRadiusNum(); @@ -697,7 +756,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, Vector r = camera.projRight.ScaledBy((a+1)/camera.scale); Vector d = camera.projUp.ScaledBy((2-a)/camera.scale); for(int i = 0; i < 2; i++) { - Vector p = SK.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum(); + Vector p = SK.GetEntity(i == 0 ? ptA : ptB)->PointGetDrawNum(); if(refs) refs->push_back(p); canvas->DrawQuad(p.Plus (r).Plus (d), p.Plus (r).Minus(d), @@ -715,7 +774,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, case Type::PT_ON_FACE: case Type::PT_IN_PLANE: { double s = 8/camera.scale; - Vector p = SK.GetEntity(ptA)->PointGetNum(); + Vector p = SK.GetEntity(ptA)->PointGetDrawNum(); if(refs) refs->push_back(p); Vector r, d; if(type == Type::PT_ON_FACE) { @@ -740,7 +799,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, } case Type::WHERE_DRAGGED: { - Vector p = SK.GetEntity(ptA)->PointGetNum(); + Vector p = SK.GetEntity(ptA)->PointGetDrawNum(); if(refs) refs->push_back(p); Vector u = p.Plus(gu.WithMagnitude(8/camera.scale)).Plus( gr.WithMagnitude(8/camera.scale)), @@ -797,10 +856,10 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, } DoArcForAngle(canvas, hcs, a0, da, b0, db, - da.WithMagnitude(40/camera.scale), &ref, /*trim=*/false); + da.WithMagnitude(40/camera.scale), &ref, /*trim=*/false, a->ExplodeOffset()); if(refs) refs->push_back(ref); DoArcForAngle(canvas, hcs, c0, dc, d0, dd, - dc.WithMagnitude(40/camera.scale), &ref, /*trim=*/false); + dc.WithMagnitude(40/camera.scale), &ref, /*trim=*/false, c->ExplodeOffset()); if(refs) refs->push_back(ref); return; @@ -820,7 +879,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, } Vector ref; - DoArcForAngle(canvas, hcs, a0, da, b0, db, disp.offset, &ref, /*trim=*/true); + DoArcForAngle(canvas, hcs, a0, da, b0, db, disp.offset, &ref, /*trim=*/true, a->ExplodeOffset()); DoLabel(canvas, hcs, ref, labelPos, gr, gu); if(refs) refs->push_back(ref); return; @@ -855,7 +914,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, if(u.Dot(ru) < 0) u = u.ScaledBy(-1); } - Vector p = e->VectorGetRefPoint(); + Vector p = e->VectorGetRefPoint().Plus(e->ExplodeOffset()); Vector s = p.Plus(u).Plus(v); DoLine(canvas, hcs, s, s.Plus(v)); Vector m = s.Plus(v.ScaledBy(0.5)); @@ -873,9 +932,9 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, if(type == Type::ARC_LINE_TANGENT) { Entity *arc = SK.GetEntity(entityA); Entity *norm = SK.GetEntity(arc->normal); - Vector c = SK.GetEntity(arc->point[0])->PointGetNum(); + Vector c = SK.GetEntity(arc->point[0])->PointGetDrawNum(); Vector p = - SK.GetEntity(arc->point[other ? 2 : 1])->PointGetNum(); + SK.GetEntity(arc->point[other ? 2 : 1])->PointGetDrawNum(); Vector r = p.Minus(c); textAt = p.Plus(r.WithMagnitude(14/camera.scale)); u = norm->NormalU(); @@ -896,6 +955,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, Entity *cubic = SK.GetEntity(entityA); Vector p = other ? cubic->CubicGetFinishNum() : cubic->CubicGetStartNum(); + p = p.Plus(cubic->ExplodeOffset()); Vector dir = SK.GetEntity(entityB)->VectorGetNum(); Vector out = n.Cross(dir); textAt = p.Plus(out.WithMagnitude(14/camera.scale)); @@ -905,12 +965,12 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, u = wn->NormalU(); v = wn->NormalV(); n = wn->NormalN(); - EntityBase *eA = SK.GetEntity(entityA); + Entity *eA = SK.GetEntity(entityA); // Big pain; we have to get a vector tangent to the curve // at the shared point, which could be from either a cubic // or an arc. if(other) { - textAt = eA->EndpointFinish(); + textAt = eA->EndpointFinish().Plus(eA->ExplodeOffset()); if(eA->type == Entity::Type::CUBIC) { dir = eA->CubicGetFinishTangentNum(); } else { @@ -919,7 +979,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, dir = n.Cross(dir); } } else { - textAt = eA->EndpointStart(); + textAt = eA->EndpointStart().Plus(eA->ExplodeOffset()); if(eA->type == Entity::Type::CUBIC) { dir = eA->CubicGetStartTangentNum(); } else { @@ -947,6 +1007,10 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, Vector u = (gn.Cross(n)).WithMagnitude(4/camera.scale); Vector p = e->VectorGetRefPoint(); + if(ShouldDrawExploded()) { + p = p.Plus(e->ExplodeOffset()); + } + DoLine(canvas, hcs, p.Plus(u), p.Plus(u).Plus(n)); DoLine(canvas, hcs, p.Minus(u), p.Minus(u).Plus(n)); if(refs) refs->push_back(p.Plus(n.ScaledBy(0.5))); @@ -967,8 +1031,8 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, Entity *line = SK.GetEntity(entityA); Vector ref; DoEqualLenTicks(canvas, hcs, - SK.GetEntity(line->point[0])->PointGetNum(), - SK.GetEntity(line->point[1])->PointGetNum(), + SK.GetEntity(line->point[0])->PointGetDrawNum(), + SK.GetEntity(line->point[1])->PointGetDrawNum(), gn, &ref); if(refs) refs->push_back(ref); DoEqualRadiusTicks(canvas, hcs, entityB, &ref); @@ -990,6 +1054,12 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, DoProjectedPoint(canvas, hcs, &b); } + if(ShouldDrawExploded()) { + Vector offset = e->ExplodeOffset(); + a = a.Plus(offset); + b = b.Plus(offset); + } + Vector ref; DoEqualLenTicks(canvas, hcs, a, b, gn, &ref); if(refs) refs->push_back(ref); @@ -1000,7 +1070,42 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, } return; } + case Type::ARC_ARC_LEN_RATIO: + case Type::ARC_ARC_DIFFERENCE: { + Entity *circle = SK.GetEntity(entityA); + Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); + Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum(); + Vector n = q.RotationN().WithMagnitude(1); + Vector ref2; + DoEqualRadiusTicks(canvas, hcs, entityA, &ref2); + DoEqualRadiusTicks(canvas, hcs, entityB, &ref2); + + Vector ref = center.Plus(disp.offset); + // Force the label into the same plane as the circle. + ref = ref.Minus(n.ScaledBy(n.Dot(ref) - n.Dot(center))); + if(refs) refs->push_back(ref); + Vector topLeft; + DoLabel(canvas, hcs, ref, &topLeft, gr, gu); + if(labelPos) *labelPos = topLeft; + return; + } + case Type::ARC_LINE_LEN_RATIO: + case Type::ARC_LINE_DIFFERENCE: { + Vector a, b = Vector::From(0, 0, 0); + Vector ref; + Entity *e = SK.GetEntity(entityA); + a = SK.GetEntity(e->point[0])->PointGetNum(); + b = SK.GetEntity(e->point[1])->PointGetNum(); + DoEqualLenTicks(canvas, hcs, a, b, gn, &ref); + if(refs) refs->push_back(ref); + DoEqualRadiusTicks(canvas, hcs, entityB, &ref); + if(refs) refs->push_back(ref); + ref = ((a.Plus(b)).ScaledBy(0.5)).Plus(disp.offset); + DoLabel(canvas, hcs, ref, labelPos, gr, gu); + return; + } + case Type::EQ_LEN_PT_LINE_D: { Entity *forLen = SK.GetEntity(entityA); Vector a = SK.GetEntity(forLen->point[0])->PointGetNum(), @@ -1009,6 +1114,11 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, DoProjectedPoint(canvas, hcs, &a); DoProjectedPoint(canvas, hcs, &b); } + if(ShouldDrawExploded()) { + Vector offset = forLen->ExplodeOffset(); + a = a.Plus(offset); + b = b.Plus(offset); + } Vector refa; DoEqualLenTicks(canvas, hcs, a, b, gn, &refa); if(refs) refs->push_back(refa); @@ -1024,6 +1134,11 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, } Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la)); + if(ShouldDrawExploded()) { + Vector offset = SK.GetEntity(ptA)->ExplodeOffset(); + pt = pt.Plus(offset); + closest = closest.Plus(offset); + } DoLine(canvas, hcs, pt, closest); Vector refb; DoEqualLenTicks(canvas, hcs, pt, closest, gn, &refb); @@ -1046,6 +1161,11 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, } Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la)); + if(ShouldDrawExploded()) { + Vector offset = pte->ExplodeOffset(); + pt = pt.Plus(offset); + closest = closest.Plus(offset); + } DoLine(canvas, hcs, pt, closest); Vector ref; @@ -1075,8 +1195,8 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, goto s; } s: - Vector a = SK.GetEntity(ptA)->PointGetNum(); - Vector b = SK.GetEntity(ptB)->PointGetNum(); + Vector a = SK.GetEntity(ptA)->PointGetDrawNum(); + Vector b = SK.GetEntity(ptB)->PointGetDrawNum(); for(int i = 0; i < 2; i++) { Vector tail = (i == 0) ? a : b; @@ -1113,8 +1233,8 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, } // For "at midpoint", this branch is always taken. Entity *e = SK.GetEntity(entityA); - Vector a = SK.GetEntity(e->point[0])->PointGetNum(); - Vector b = SK.GetEntity(e->point[1])->PointGetNum(); + Vector a = SK.GetEntity(e->point[0])->PointGetDrawNum(); + Vector b = SK.GetEntity(e->point[1])->PointGetDrawNum(); Vector m = (a.ScaledBy(0.5)).Plus(b.ScaledBy(0.5)); Vector offset = (a.Minus(b)).Cross(n); offset = offset.WithMagnitude(textHeight); @@ -1138,8 +1258,8 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, r.WithMagnitude(1), u.WithMagnitude(1), hcs); if(refs) refs->push_back(o); } else { - Vector a = SK.GetEntity(ptA)->PointGetNum(); - Vector b = SK.GetEntity(ptB)->PointGetNum(); + Vector a = SK.GetEntity(ptA)->PointGetDrawNum(); + Vector b = SK.GetEntity(ptB)->PointGetDrawNum(); Entity *w = SK.GetEntity(workplane); Vector cu = w->Normal()->NormalU(); @@ -1189,8 +1309,13 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas, } hcs = canvas->GetStroke(stroke); } - DoLabel(canvas, hcs, disp.offset, labelPos, u, v); - if(refs) refs->push_back(disp.offset); + Vector ref = disp.offset; + if(ptA.v) { + Vector a = SK.GetEntity(ptA)->PointGetNum(); + ref = a.Plus(disp.offset); + } + DoLabel(canvas, hcs, ref, labelPos, u, v); + if(refs) refs->push_back(ref); return; } } @@ -1238,7 +1363,11 @@ bool Constraint::HasLabel() const { case Type::PT_FACE_DISTANCE: case Type::PROJ_PT_DISTANCE: case Type::LENGTH_RATIO: + case Type::ARC_ARC_LEN_RATIO: + case Type::ARC_LINE_LEN_RATIO: case Type::LENGTH_DIFFERENCE: + case Type::ARC_ARC_DIFFERENCE: + case Type::ARC_LINE_DIFFERENCE: case Type::DIAMETER: case Type::ANGLE: return true; @@ -1247,3 +1376,9 @@ bool Constraint::HasLabel() const { return false; } } + +bool Constraint::ShouldDrawExploded() const { + return SK.GetGroup(group)->ShouldDrawExploded(); +} + +} // namespace SolveSpace diff --git a/src/drawentity.cpp b/src/drawentity.cpp index 5e1122ad3..bf856a14e 100644 --- a/src/drawentity.cpp +++ b/src/drawentity.cpp @@ -7,6 +7,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + std::string Entity::DescriptionString() const { if(h.isFromRequest()) { Request *r = SK.GetRequest(h.request()); @@ -88,7 +90,7 @@ void Entity::GetReferencePoints(std::vector *refs) { case Type::POINT_N_ROT_AXIS_TRANS: case Type::POINT_IN_3D: case Type::POINT_IN_2D: - refs->push_back(PointGetNum()); + refs->push_back(PointGetDrawNum()); break; case Type::NORMAL_N_COPY: @@ -103,12 +105,12 @@ void Entity::GetReferencePoints(std::vector *refs) { case Type::CUBIC_PERIODIC: case Type::TTF_TEXT: case Type::IMAGE: - refs->push_back(SK.GetEntity(point[0])->PointGetNum()); + refs->push_back(SK.GetEntity(point[0])->PointGetDrawNum()); break; case Type::LINE_SEGMENT: { - Vector a = SK.GetEntity(point[0])->PointGetNum(), - b = SK.GetEntity(point[1])->PointGetNum(); + Vector a = SK.GetEntity(point[0])->PointGetDrawNum(), + b = SK.GetEntity(point[1])->PointGetDrawNum(); refs->push_back(b.Plus(a.Minus(b).ScaledBy(0.5))); break; } @@ -180,19 +182,43 @@ bool Entity::IsVisible() const { return true; } +static bool PtCanDrag(hEntity pt) { + Entity* p = SK.GetEntity(pt); + // a numeric copy can not move + if(p->type == Entity::Type::POINT_N_COPY) return false; + // these transforms applied zero times can not be moved + if(((p->type == Entity::Type::POINT_N_TRANS) || + (p->type == Entity::Type::POINT_N_ROT_AA) || + (p->type == Entity::Type::POINT_N_ROT_AXIS_TRANS)) + && (p->timesApplied == 0)) return false; + return true; +} + // entities that were created via some copy types will not be // draggable with the mouse. We identify the undraggables here bool Entity::CanBeDragged() const { - // a numeric copy can not move - if(type == Entity::Type::POINT_N_COPY) return false; - // these transforms applied zero times can not be moved - if(((type == Entity::Type::POINT_N_TRANS) || - (type == Entity::Type::POINT_N_ROT_AA) || - (type == Entity::Type::POINT_N_ROT_AXIS_TRANS)) - && (timesApplied == 0)) return false; + if(IsPoint()) { + if(!PtCanDrag(h)) + return false; + // are we constrained pt-on-point from a previous group? + for(const Constraint &cc : SK.constraint) { + if(cc.group == group && cc.type == ConstraintBase::Type::POINTS_COINCIDENT) { + if(cc.ptA == h) { + if((SK.GetEntity(cc.ptB)->group < group) + || (!PtCanDrag(cc.ptB))) + return false; + } + if(cc.ptB == h) { + if((SK.GetEntity(cc.ptA)->group < group) + || (!PtCanDrag(cc.ptA))) + return false; + } + } + } + } // for these types of entities the first point will indicate draggability if(HasEndpoints() || type == Entity::Type::CIRCLE) { - return SK.GetEntity(point[0])->CanBeDragged(); + return PtCanDrag(point[0]); } // if we're not certain it can't be dragged then default to true return true; @@ -451,7 +477,8 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) const { Vector v = topLeft.Minus(botLeft); Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude()); - SS.fonts.PlotString(font, str, sbl, botLeft, u, v); + // `extraPoints` is storing kerning boolean + SS.fonts.PlotString(font, str, sbl, extraPoints, botLeft, u, v); break; } @@ -466,6 +493,26 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) const { } } +bool Entity::ShouldDrawExploded() const { + return SK.GetGroup(group)->ShouldDrawExploded(); +} + +Vector Entity::ExplodeOffset() const { + if(ShouldDrawExploded() && workplane.v != 0) { + int requestIdx = SK.GetRequest(h.request())->groupRequestIndex; + double offset = SS.explodeDistance * (requestIdx + 1); + return SK.GetEntity(workplane)->Normal()->NormalN().ScaledBy(offset); + } else { + return Vector::From(0, 0, 0); + } +} + +Vector Entity::PointGetDrawNum() const { + // As per EntityBase::PointGetNum but specifically for when drawing/rendering the point + // (and not when solving), so we can potentially draw it somewhere different + return PointGetNum().Plus(ExplodeOffset()); +} + void Entity::Draw(DrawAs how, Canvas *canvas) { if(!(how == DrawAs::HOVERED || how == DrawAs::SELECTED) && !IsVisible()) return; @@ -557,16 +604,17 @@ void Entity::Draw(DrawAs how, Canvas *canvas) { pointStroke.unit = Canvas::Unit::PX; Canvas::hStroke hcsPoint = canvas->GetStroke(pointStroke); + Vector p = PointGetDrawNum(); if(free) { Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE); analyzeStroke.width = 14.0; analyzeStroke.layer = Canvas::Layer::FRONT; Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke); - canvas->DrawPoint(PointGetNum(), hcsAnalyze); + canvas->DrawPoint(p, hcsAnalyze); } - canvas->DrawPoint(PointGetNum(), hcsPoint); + canvas->DrawPoint(p, hcsPoint); return; } @@ -621,7 +669,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) { tail = camera.projRight.ScaledBy(w/s).Plus( camera.projUp. ScaledBy(h/s)).Minus(camera.offset); } else { - tail = SK.GetEntity(point[0])->PointGetNum(); + tail = SK.GetEntity(point[0])->PointGetDrawNum(); } tail = camera.AlignToPixelGrid(tail); @@ -709,8 +757,32 @@ void Entity::Draw(DrawAs how, Canvas *canvas) { case Type::TTF_TEXT: { // Generate the rational polynomial curves, then piecewise linearize // them, and display those. - if(!canvas->DrawBeziers(*GetOrGenerateBezierCurves(), hcs)) { - canvas->DrawEdges(*GetOrGenerateEdges(), hcs); + // Calculating the draw offset, if necessary. + const bool shouldExplode = ShouldDrawExploded(); + Vector explodeOffset; + SBezierList offsetBeziers = {}; + SBezierList *beziers = GetOrGenerateBezierCurves(); + if(shouldExplode) { + explodeOffset = ExplodeOffset(); + for(const SBezier& b : beziers->l) { + SBezier offset = b.TransformedBy(explodeOffset, Quaternion::IDENTITY, 1.0); + offsetBeziers.l.Add(&offset); + } + beziers = &offsetBeziers; + } + + SEdgeList *edges = nullptr; + SEdgeList offsetEdges = {}; + + if(!canvas->DrawBeziers(*beziers, hcs)) { + edges = GetOrGenerateEdges(); + if(shouldExplode) { + for(const SEdge &e : edges->l) { + offsetEdges.AddEdge(e.a.Plus(explodeOffset), e.b.Plus(explodeOffset), e.auxA, e.auxB, e.tag); + } + edges = &offsetEdges; + } + canvas->DrawEdges(*edges, hcs); } if(type == Type::CIRCLE) { Entity *dist = SK.GetEntity(distance); @@ -720,12 +792,14 @@ void Entity::Draw(DrawAs how, Canvas *canvas) { Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE); analyzeStroke.layer = Canvas::Layer::FRONT; Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke); - if(!canvas->DrawBeziers(*GetOrGenerateBezierCurves(), hcsAnalyze)) { - canvas->DrawEdges(*GetOrGenerateEdges(), hcsAnalyze); + if(!canvas->DrawBeziers(*beziers, hcsAnalyze)) { + canvas->DrawEdges(*edges, hcsAnalyze); } } } } + offsetBeziers.Clear(); + offsetEdges.Clear(); return; } case Type::IMAGE: { @@ -757,7 +831,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) { Canvas::hFill hf = canvas->GetFill(fill); Vector v[4] = {}; for(int i = 0; i < 4; i++) { - v[i] = SK.GetEntity(point[i])->PointGetNum(); + v[i] = SK.GetEntity(point[i])->PointGetDrawNum(); } Vector iu = v[3].Minus(v[0]); Vector iv = v[1].Minus(v[0]); @@ -789,3 +863,5 @@ void Entity::Draw(DrawAs how, Canvas *canvas) { } ssassert(false, "Unexpected entity type"); } + +} // namespace SolveSpace diff --git a/src/dsc.h b/src/dsc.h index 34190e620..1a535c6b5 100644 --- a/src/dsc.h +++ b/src/dsc.h @@ -7,35 +7,16 @@ #ifndef SOLVESPACE_DSC_H #define SOLVESPACE_DSC_H -#include "solvespace.h" +#include +#include +#include +#include +#include -#include +#include "defs.h" +#include "util.h" -/// Trait indicating which types are handle types and should get the associated operators. -/// Specialize for each handle type and inherit from std::true_type. -template -struct IsHandleOracle : std::false_type {}; - -// Equality-compare any two instances of a handle type. -template -static inline typename std::enable_if::value, bool>::type -operator==(T const &lhs, T const &rhs) { - return lhs.v == rhs.v; -} - -// Inequality-compare any two instances of a handle type. -template -static inline typename std::enable_if::value, bool>::type -operator!=(T const &lhs, T const &rhs) { - return !(lhs == rhs); -} - -// Less-than-compare any two instances of a handle type. -template -static inline typename std::enable_if::value, bool>::type -operator<(T const &lhs, T const &rhs) { - return lhs.v < rhs.v; -} +namespace SolveSpace { class Vector; class Vector4; @@ -148,9 +129,9 @@ inline double Vector::Element(int i) const { inline bool Vector::Equals(Vector v, double tol) const { // Quick axis-aligned tests before going further const Vector dv = this->Minus(v); - if (fabs(dv.x) > tol) return false; - if (fabs(dv.y) > tol) return false; - if (fabs(dv.z) > tol) return false; + if (std::abs(dv.x) > tol) return false; + if (std::abs(dv.y) > tol) return false; + if (std::abs(dv.z) > tol) return false; return dv.MagSquared() < tol*tol; } @@ -192,13 +173,13 @@ inline Vector Vector::ScaledBy(const double v) const { } inline void Vector::MakeMaxMin(Vector *maxv, Vector *minv) const { - maxv->x = max(maxv->x, x); - maxv->y = max(maxv->y, y); - maxv->z = max(maxv->z, z); + maxv->x = std::max(maxv->x, x); + maxv->y = std::max(maxv->y, y); + maxv->z = std::max(maxv->z, z); - minv->x = min(minv->x, x); - minv->y = min(minv->y, y); - minv->z = min(minv->z, z); + minv->x = std::min(minv->x, x); + minv->y = std::min(minv->y, y); + minv->z = std::min(minv->z, z); } struct VectorHash { @@ -371,15 +352,28 @@ class List { } }; +template class IdList; + // Comparison functor used by IdList and related classes template struct CompareId { - bool operator()(T const& lhs, T const& rhs) const { - return lhs.h.v < rhs.h.v; + + CompareId(const IdList *list) { + idlist = list; + } + + bool operator()(int lhs, T const& rhs) const { + return idlist->elemstore[lhs].h.v < rhs.h.v; } - bool operator()(T const& lhs, H rhs) const { - return lhs.h.v < rhs.v; + bool operator()(int lhs, H rhs) const { + return idlist->elemstore[lhs].h.v < rhs.v; } + bool operator()(T *lhs, int rhs) const { + return lhs->h.v < idlist->elemstore[rhs].h.v; + } + +private: + const IdList *idlist; }; // A list, where each element has an integer identifier. The list is kept @@ -387,154 +381,152 @@ struct CompareId { // id. template class IdList { - T *elem = nullptr; - int elemsAllocated = 0; + std::vector elemstore; + std::vector elemidx; + std::vector freelist; public: - int n = 0; + int n = 0; // PAR@@@@@ make this private to see all interesting and suspicious places in SoveSpace ;-) + friend struct CompareId; using Compare = CompareId; - bool IsEmpty() const { - return n == 0; - } + struct iterator { + typedef std::random_access_iterator_tag iterator_category; + typedef T value_type; + typedef int difference_type; + typedef T *pointer; + typedef T &reference; + + public: + T &operator*() const noexcept { return *elem; } + const T *operator->() const noexcept { return elem; } + + bool operator==(const iterator &p) const { return p.position == position; } + bool operator!=(const iterator &p) const { return !operator==(p); } + + iterator &operator++() { + ++position; + if(position >= (int)list->elemidx.size()) { + elem = nullptr; // PAR@@@@ Remove just debugging + } else if(0 <= position) { + elem = &(list->elemstore[list->elemidx[position]]); + } + return *this; + } - void AllocForOneMore() { - if(n >= elemsAllocated) { - ReserveMore((elemsAllocated + 32)*2 - n); + // Needed for std:find_if of gcc used in entity.cpp GenerateEquations + difference_type operator-(const iterator &rhs) const noexcept { + return position - rhs.position; } + + iterator(IdList *l) : position(0), list(l) { + if(list) { + if(list->elemstore.size() && list->elemidx.size()) { + elem = &(list->elemstore[list->elemidx[position]]); + } + } + }; + iterator(IdList *l, int pos) : position(pos), list(l) { + if(position >= (int)list->elemidx.size()) { + elem = nullptr; + } else if(0 <= position) { + elem = &((list->elemstore)[list->elemidx[position]]); + } + }; + + private: + int position; + T *elem; + IdList *list; + }; + + + bool IsEmpty() const { + return n == 0; } uint32_t MaximumId() { if(IsEmpty()) { return 0; } else { - return Last()->h.v; + return elemstore[elemidx.back()].h.v; } } H AddAndAssignId(T *t) { t->h.v = (MaximumId() + 1); - AllocForOneMore(); - // Copy-construct at the end of the list. - new(&elem[n]) T(*t); + // Add at the end of the list. + elemstore.push_back(*t); + elemidx.push_back(elemstore.size()-1); ++n; return t->h; } - T * LowerBound(T const& t) { - if(IsEmpty()) { - return nullptr; - } - auto it = std::lower_bound(begin(), end(), t, Compare()); - return it; - } - - T * LowerBound(H const& h) { - if(IsEmpty()) { - return nullptr; - } - auto it = std::lower_bound(begin(), end(), h, Compare()); - return it; - } - - int LowerBoundIndex(T const& t) { - if(IsEmpty()) { - return 0; - } - auto it = LowerBound(t); - auto idx = std::distance(begin(), it); - auto i = static_cast(idx); - return i; - } void ReserveMore(int howMuch) { - if(n + howMuch > elemsAllocated) { - elemsAllocated = n + howMuch; - T *newElem = (T *)::operator new[]((size_t)elemsAllocated*sizeof(T)); - for(int i = 0; i < n; i++) { - new(&newElem[i]) T(std::move(elem[i])); - elem[i].~T(); - } - ::operator delete[](elem); - elem = newElem; - } + elemstore.reserve(elemstore.size() + howMuch); + elemidx.reserve(elemidx.size() + howMuch); + // freelist.reserve(freelist.size() + howMuch); // PAR@@@@ maybe we should - not much more RAM } void Add(T *t) { - AllocForOneMore(); - // Look to see if we already have something with the same handle value. ssassert(FindByIdNoOops(t->h) == nullptr, "Handle isn't unique"); // Find out where the added element should be. - int pos = LowerBoundIndex(*t); - - // Shift everything from there to the end of the array. - new(&elem[n]) T(); - for (int i = n; i > pos; i--) - elem[i] = std::move(elem[i - 1]); + auto pos = std::lower_bound(elemidx.begin(), elemidx.end(), *t, Compare(this)); + + if(freelist.empty()) { // Add a new element to the store + elemstore.push_back(*t); + // Insert a pointer to the element at the correct position + if(elemidx.empty()) { + // The list is empty so pos, begin and end are all null. + // insert does not work in this case. + elemidx.push_back(elemstore.size()-1); + } else { + elemidx.insert(pos, elemstore.size() - 1); + } + } else { // Use the last element from the freelist + // Insert an index to the element at the correct position + elemidx.insert(pos, freelist.back()); + // Remove the element from the freelist + freelist.pop_back(); + + // Copy-construct to the element storage. + elemstore[*pos] = T(*t); + // *elemptr[pos] = *t; // PAR@@@@@@ maybe this? + } - // Copy-construct at the right place. - elem[pos] = T(*t); ++n; } T *FindById(H h) { T *t = FindByIdNoOops(h); - ssassert(t != NULL, "Cannot find handle"); + ssassert(t != nullptr, "Cannot find handle"); return t; } - int IndexOf(H h) { - if(IsEmpty()) { - return -1; - } - auto it = LowerBound(h); - auto idx = std::distance(begin(), it); - if (idx < n) { - return idx; - } - return -1; - } - T *FindByIdNoOops(H h) { if(IsEmpty()) { return nullptr; } - auto it = LowerBound(h); - if (it == nullptr || it == end()) { + auto it = std::lower_bound(elemidx.begin(), elemidx.end(), h, Compare(this)); + if(it == elemidx.end()) { return nullptr; + } else { + if(elemstore[*it].h.v != h.v) { + return nullptr; + } + return &elemstore[*it]; } - if (it->h.v == h.v) { - return it; - } - return nullptr; - } - - T *First() { - return (IsEmpty()) ? NULL : &(elem[0]); - } - T *Last() { - return (IsEmpty()) ? NULL : &(elem[n-1]); - } - T *NextAfter(T *prev) { - if(IsEmpty() || !prev) return NULL; - if(prev - First() == (n - 1)) return NULL; - return prev + 1; } - T &Get(size_t i) { return elem[i]; } - T const &Get(size_t i) const { return elem[i]; } + T &Get(size_t i) { return elemstore[elemidx[i]]; } T &operator[](size_t i) { return Get(i); } - T const &operator[](size_t i) const { return Get(i); } - T *begin() { return IsEmpty() ? nullptr : &elem[0]; } - T *end() { return IsEmpty() ? nullptr : &elem[0] + n; } - const T *begin() const { return IsEmpty() ? nullptr : &elem[0]; } - const T *end() const { return IsEmpty() ? nullptr : &elem[0] + n; } - const T *cbegin() const { return begin(); } - const T *cend() const { return end(); } + iterator begin() { return IsEmpty() ? nullptr : iterator(this); } + iterator end() { return IsEmpty() ? nullptr : iterator(this, elemidx.size()); } void ClearTags() { for(auto &elt : *this) { elt.tag = 0; } @@ -551,22 +543,23 @@ class IdList { int src, dest; dest = 0; for(src = 0; src < n; src++) { - if(elem[src].tag) { + if(elemstore[elemidx[src]].tag) { // this item should be deleted - elem[src].Clear(); + elemstore[elemidx[src]].Clear(); +// elemstore[elemidx[src]].~T(); // Clear below calls the destructors + freelist.push_back(elemidx[src]); + elemidx[src] = 0xDEADBEEF; // PAR@@@@@ just for debugging, not needed, remove later } else { if(src != dest) { - elem[dest] = elem[src]; + elemidx[dest] = elemidx[src]; } dest++; } } - for(int i = dest; i < n; i++) - elem[i].~T(); n = dest; - // and elemsAllocated is untouched, because we didn't resize + elemidx.resize(n); // Clear left over elements at the end. } - void RemoveById(H h) { + void RemoveById(H h) { // PAR@@@@@ this can be optimized ClearTags(); FindById(h)->tag = 1; RemoveTagged(); @@ -574,28 +567,35 @@ class IdList { void MoveSelfInto(IdList *l) { l->Clear(); - std::swap(l->elem, elem); - std::swap(l->elemsAllocated, elemsAllocated); + std::swap(l->elemstore, elemstore); + std::swap(l->elemidx, elemidx); + std::swap(l->freelist, freelist); std::swap(l->n, n); } void DeepCopyInto(IdList *l) { l->Clear(); - l->elem = (T *)::operator new[](elemsAllocated * sizeof(elem[0])); - for(int i = 0; i < n; i++) - new(&l->elem[i]) T(elem[i]); - l->elemsAllocated = elemsAllocated; + + for(auto const &it : elemstore) { + l->elemstore.push_back(it); + } + + for(auto const &it : elemidx) { + l->elemidx.push_back(it); + } + l->n = n; } void Clear() { - for(int i = 0; i < n; i++) { - elem[i].Clear(); - elem[i].~T(); + for(auto &it : elemidx) { + elemstore[it].Clear(); +// elemstore[it].~T(); // clear below calls the destructors } - if(elem) ::operator delete[](elem); - elem = NULL; - elemsAllocated = n = 0; + freelist.clear(); + elemidx.clear(); + elemstore.clear(); + n = 0; } }; @@ -725,4 +725,6 @@ class BBox { bool Contains(const Point2d &p, double r = 0.0) const; }; +} // namespace SolveSpace + #endif diff --git a/src/entity.cpp b/src/entity.cpp index a0f2f9461..7b7b7a081 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -7,6 +7,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + const hEntity EntityBase::FREE_IN_3D = { 0 }; const hEntity EntityBase::NO_ENTITY = { 0 }; @@ -26,6 +28,9 @@ bool EntityBase::HasVector() const { } ExprVector EntityBase::VectorGetExprsInWorkplane(hEntity wrkpl) const { + if(IsFace()) { + return FaceGetNormalExprs(); + } switch(type) { case Type::LINE_SEGMENT: return (SK.GetEntity(point[0])->PointGetExprsInWorkplane(wrkpl)).Minus( @@ -62,6 +67,9 @@ ExprVector EntityBase::VectorGetExprs() const { } Vector EntityBase::VectorGetNum() const { + if(IsFace()) { + return FaceGetNormalNum(); + } switch(type) { case Type::LINE_SEGMENT: return (SK.GetEntity(point[0])->PointGetNum()).Minus( @@ -79,6 +87,9 @@ Vector EntityBase::VectorGetNum() const { } Vector EntityBase::VectorGetRefPoint() const { + if(IsFace()) { + return FaceGetPointNum(); + } switch(type) { case Type::LINE_SEGMENT: return ((SK.GetEntity(point[0])->PointGetNum()).Plus( @@ -982,3 +993,5 @@ void EntityBase::GenerateEquations(IdList *l) const { break; } } + +} // namespace SolveSpace diff --git a/src/export.cpp b/src/export.cpp index f1c331fd7..5e6ad0738 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -9,6 +9,8 @@ #include "solvespace.h" #include "config.h" +namespace SolveSpace { + void SolveSpaceUI::ExportSectionTo(const Platform::Path &filename) { Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); gn = gn.WithMagnitude(1); @@ -227,7 +229,7 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const Platform::Path &filename, bool } } - if(SS.GW.showConstraints) { + if(SS.GW.showConstraints != GraphicsWindow::ShowConstraintMode::SCM_NOSHOW ) { if(!out->OutputConstraints(&SK.constraint)) { GetEdgesCanvas canvas = {}; canvas.camera = SS.GW.GetCamera(); @@ -1196,7 +1198,7 @@ void SolveSpaceUI::ExportMeshAsVrmlTo(FILE *f, const Platform::Path &filename, S } // Output all the vertices. - for(auto sp : spl.l) { + for(const auto &sp : spl.l) { fprintf(f, " %f %f %f,\n", sp.p.x / SS.exportScale, sp.p.y / SS.exportScale, @@ -1266,3 +1268,5 @@ void SolveSpaceUI::ExportAsPngTo(const Platform::Path &filename) { // The rest of the work is done in the next redraw. GW.Invalidate(); } + +} // namespace SolveSpace diff --git a/src/exportstep.cpp b/src/exportstep.cpp index db42dc085..6bfd974f9 100644 --- a/src/exportstep.cpp +++ b/src/exportstep.cpp @@ -5,6 +5,261 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + +#define FP "%.5f" // Floating Point coordinate precision. Since LENGTH_EPS = 1e-6 output 5 decimal places thus rounding out errors e.g. 0.999999mm +#define CARTESIAN_POINT_FORMAT "#%d=CARTESIAN_POINT('',(" FP "," FP "," FP "));\n" +const double PRECISION = 2*LENGTH_EPS; + +//----------------------------------------------------------------------------- +// Functions for STEP export: duplication check. +//----------------------------------------------------------------------------- +// Check if this point was already defined with a different ID number. +// inputs: +// number -> id of the cartesian point +// v -> position of the cartesian point +// vertex -> id of the vertex linked to this point (<1 if none) +// return: +// true, if the cartesian point is already defined +bool StepFileWriter::HasCartesianPointAnAlias(int number, Vector v, int vertex, bool *vertex_has_alias) { + // Look for this point "v" in the alias list. + for(pointAliases_t &p : pointAliases) { + if(p.v.Equals(v, PRECISION)) { + // This point was already defined. + // The new number is just an alias. + p.alias.aliases.push_back(number); + if(vertex > 0) { + p.vertexAlias.reference = (p.vertexAlias.reference <= 0 ? + vertex : p.vertexAlias.reference); + p.vertexAlias.aliases.push_back(vertex); + + // If the caller is interested let them know if the vertex is new or has an existing alias. + if(nullptr != vertex_has_alias) { + if(p.vertexAlias.aliases.size() == 1) { + // This is a new vertex. + *vertex_has_alias = false; + } else { + *vertex_has_alias = true; + } + } + } + return true; + } + } + + // No point was found, it means this is a new point. + pointAliases_t newPoint; + newPoint.alias.reference = number; + newPoint.alias.aliases.push_back(number); + newPoint.v = v; + newPoint.vertexAlias.reference = vertex; + if(vertex > 0) { + newPoint.vertexAlias.aliases.push_back(vertex); + } + + // A new entry in the list. + pointAliases.push_back(newPoint); + return false; +} + +// Return a cartesian point index; if the point has aliases return the +// first one. +// input: +// number -> the id of the requested cartesian point +// return: +// number, if the point has no aliases, otherwise its first alias +int StepFileWriter::InsertPoint(int number) { + // Look for a point with index "number" in the list of aliases. + for(pointAliases_t p : pointAliases) { + for(int alias : p.alias.aliases) { + if(alias == number) { + return p.alias.reference; + } + } + } + + // ERROR: it should never reach this point... + return -1; +} + +// Return a vertex index; if the vertex has aliases return the +// first one. +// input: +// number -> the id of the requested vertex +// return: +// number, if the vertex has no aliases, otherwise its first alias +int StepFileWriter::InsertVertex(int number) { + // Look for a point with index "number" in the list of vertex aliases. + for(pointAliases_t p : pointAliases) { + for(int alias : p.vertexAlias.aliases) { + if(alias == number) { + return p.vertexAlias.reference; + } + } + } + + // ERROR: it should never reach this point... + return -1; +} + +// Check whether this curve was already defined with a different ID number. +// inputs: +// number -> id of the curve +// points -> list of points that define the curve +// return: +// true, if the curve is already defined +bool StepFileWriter::HasBSplineCurveAnAlias(int number, std::vector points) { + // Look for this curve in the alias list. + for(curveAliases_t &c : curveAliases) { + if(points.size() != c.memberPoints.size()) { + continue; + } if(exportParts && !(c.color.Equals(currentColor))) { + continue; + } else { + size_t matches = 0; // is this the same curve? + // FIXME: this hack should work _most_ of the times + for(size_t i = 0; i < points.size(); i++) { + for(size_t j = 0; j < points.size(); j++) { + if(points[i] == c.memberPoints[j]) { + matches++; + } + } + } + + if(matches == points.size()) { + // Add this alias. + c.alias.aliases.push_back(number); + return true; + } + } + } + + // No curve was found, it means this is a new curve. + curveAliases_t newCurve; + newCurve.alias.reference = number; + newCurve.alias.aliases.push_back(number); + newCurve.memberPoints = points; + newCurve.color = currentColor; + + // A new entry in the list. + curveAliases.push_back(newCurve); + return false; +} + +// Return a curve index. As above. +int StepFileWriter::InsertCurve(int number) { + for(curveAliases_t c : curveAliases) { + for(int alias : c.alias.aliases) { + if(alias == number && (!exportParts || c.color.Equals(currentColor))) { + return c.alias.reference; + } + } + } + + // ERROR: it should never reach this point... + return -1; +} + +// Check whether this edge curve was already defined with a different ID number. +// inputs: +// number -> id of the edge +// prevFinish, thisFinish -> points of the edge +// curveID -> curve of the edge +// flip -> if an edge curve already exists this indicates whether it is in the other direction +// return: +// true, if the edge curve is already defined +bool StepFileWriter::HasEdgeCurveAnAlias(int number, int prevFinish, int thisFinish, int curveId, bool *flip) { + // Look for this edge in the alias list. + for(edgeCurveAliases_t &e : edgeCurveAliases) { + if(exportParts && !(e.color.Equals(currentColor))) { + continue; + } + // Check both directions. + if(((prevFinish == e.prevFinish && thisFinish == e.thisFinish) || + (prevFinish == e.thisFinish && thisFinish == e.prevFinish)) && + curveId == e.curveId) { + e.alias.aliases.push_back(number); + if(nullptr != flip) { + if(prevFinish == e.thisFinish && thisFinish == e.prevFinish) + *flip = true; + else + *flip = false; + } + return true; + } + } + + // New edge curve. + edgeCurveAliases_t newEdgeCurve; + newEdgeCurve.alias.reference = number; + newEdgeCurve.alias.aliases.push_back(number); + newEdgeCurve.prevFinish = prevFinish; + newEdgeCurve.thisFinish = thisFinish; + newEdgeCurve.curveId = curveId; + newEdgeCurve.color = currentColor; + + edgeCurveAliases.push_back(newEdgeCurve); + return false; +} + +// Return an edge curve index. As above. +int StepFileWriter::InsertEdgeCurve(int number) { + for(edgeCurveAliases_t e : edgeCurveAliases) { + for(int alias : e.alias.aliases) { + if(alias == number && (!exportParts || e.color.Equals(currentColor))) { + return e.alias.reference; + } + } + } + + // ERROR: it should never reach this point... + return -1; +} + +// Check whether this oriented edge curve was already defined with a different ID number. +// inputs: +// number -> id of the edge +// edgeCurveId -> curve of the edge +// flip -> the direction of the oriented edge +// return: +// true, if the oriented edge is already defined +bool StepFileWriter::HasOrientedEdgeAnAlias(int number, int edgeCurveId, bool flip) { + // Look for this edge in the alias list. + for(orientedEdgeAliases_t &e : orientedEdgeAliases) { + if((edgeCurveId == e.edgeCurveId) && (flip == e.flip)) { + e.alias.aliases.push_back(number); + return true; + } + } + + // New oriented edge. + orientedEdgeAliases_t newOrientedEdge; + newOrientedEdge.alias.reference = number; + newOrientedEdge.alias.aliases.push_back(number); + newOrientedEdge.edgeCurveId = edgeCurveId; + newOrientedEdge.flip = flip; + + orientedEdgeAliases.push_back(newOrientedEdge); + return false; +} + +// Return an oriented edge index. As above. +int StepFileWriter::InsertOrientedEdge(int number) { + for(orientedEdgeAliases_t e : orientedEdgeAliases) { + for(int alias : e.alias.aliases) { + if(alias == number) { + return e.alias.reference; + } + } + } + + // ERROR: it should never reach this point... + return -1; +} + +//----------------------------------------------------------------------------- +// Functions for STEP export: print to file. +//----------------------------------------------------------------------------- void StepFileWriter::WriteHeader() { fprintf(f, "ISO-10303-21;\n" @@ -46,7 +301,7 @@ void StepFileWriter::WriteHeader() { "SI_UNIT($,.STERADIAN.)\n" "SOLID_ANGLE_UNIT()\n" ");\n" -"#167=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.001),#158,\n" +"#167=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(%f),#158,\n" "'DISTANCE_ACCURACY_VALUE',\n" "'string');\n" "#168=(\n" @@ -60,8 +315,8 @@ void StepFileWriter::WriteHeader() { "#171=DIRECTION('',(0.,0.,1.));\n" "#172=DIRECTION('',(1.,0.,0.));\n" "#173=CARTESIAN_POINT('',(0.,0.,0.));\n" -"\n" - ); +"\n", + PRECISION); // Start the ID somewhere beyond the header IDs. id = 200; @@ -83,35 +338,44 @@ void StepFileWriter::WriteProductHeader() { } int StepFileWriter::ExportCurve(SBezier *sb) { int i, ret = id; + std::vector curvePoints = {}; - fprintf(f, "#%d=(\n", ret); - fprintf(f, "BOUNDED_CURVE()\n"); - fprintf(f, "B_SPLINE_CURVE(%d,(", sb->deg); for(i = 0; i <= sb->deg; i++) { - fprintf(f, "#%d", ret + i + 1); - if(i != sb->deg) fprintf(f, ","); + if (!HasCartesianPointAnAlias(id + 1 + i, sb->ctrl[i], -1)) + fprintf(f, CARTESIAN_POINT_FORMAT, + id + 1 + i, + CO(sb->ctrl[i])); } - fprintf(f, "),.UNSPECIFIED.,.F.,.F.)\n"); - fprintf(f, "B_SPLINE_CURVE_WITH_KNOTS((%d,%d),", - (sb->deg + 1), (sb-> deg + 1)); - fprintf(f, "(0.000,1.000),.UNSPECIFIED.)\n"); - fprintf(f, "CURVE()\n"); - fprintf(f, "GEOMETRIC_REPRESENTATION_ITEM()\n"); - fprintf(f, "RATIONAL_B_SPLINE_CURVE(("); + for(i = 0; i <= sb->deg; i++) { - fprintf(f, "%.10f", sb->weight[i]); - if(i != sb->deg) fprintf(f, ","); + int point = InsertPoint(id + 1 + i); + curvePoints.push_back(point); } - fprintf(f, "))\n"); - fprintf(f, "REPRESENTATION_ITEM('')\n);\n"); - - for(i = 0; i <= sb->deg; i++) { - fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n", - id + 1 + i, - CO(sb->ctrl[i])); + + if (!HasBSplineCurveAnAlias(ret, curvePoints)) { + fprintf(f, "#%d=(\n", ret); + fprintf(f, "BOUNDED_CURVE()\n"); + fprintf(f, "B_SPLINE_CURVE(%d,(", sb->deg); + for(i = 0; i <= sb->deg; i++) { + fprintf(f, "#%d", InsertPoint(ret + i + 1)); + if(i != sb->deg) fprintf(f, ","); + } + fprintf(f, "),.UNSPECIFIED.,.F.,.F.)\n"); + fprintf(f, "B_SPLINE_CURVE_WITH_KNOTS((%d,%d),", + (sb->deg + 1), (sb-> deg + 1)); + fprintf(f, "(0.000,1.000),.UNSPECIFIED.)\n"); + fprintf(f, "CURVE()\n"); + fprintf(f, "GEOMETRIC_REPRESENTATION_ITEM()\n"); + fprintf(f, "RATIONAL_B_SPLINE_CURVE(("); + for(i = 0; i <= sb->deg; i++) { + fprintf(f, "%.10f", sb->weight[i]); + if(i != sb->deg) fprintf(f, ","); + } + fprintf(f, "))\n"); + fprintf(f, "REPRESENTATION_ITEM('')\n);\n"); + fprintf(f, "\n"); } - fprintf(f, "\n"); - + id = ret + 1 + (sb->deg + 1); return ret; } @@ -123,13 +387,24 @@ int StepFileWriter::ExportCurveLoop(SBezierLoop *loop, bool inner) { SBezier *sb = loop->l.Last(); + int lastFinish, prevFinish; // Generate "exactly closed" contours, with the same vertex id for the // finish of a previous edge and the start of the next one. So we need // the finish of the last Bezier in the loop before we start our process. - fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n", - id, CO(sb->Finish())); - fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id+1, id); - int lastFinish = id + 1, prevFinish = lastFinish; + bool vertex_has_alias; + if(!HasCartesianPointAnAlias(id, sb->Finish(), id + 1, &vertex_has_alias)) { + fprintf(f, CARTESIAN_POINT_FORMAT, + id, CO(sb->Finish())); + fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id + 1, InsertPoint(id)); + lastFinish = id + 1; + } else if(!vertex_has_alias) { + fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id + 1, InsertPoint(id)); + lastFinish = id + 1; + } + else { + lastFinish = InsertVertex(id+1); + } + prevFinish = lastFinish; id += 2; for(sb = loop->l.First(); sb; sb = loop->l.NextAfter(sb)) { @@ -137,20 +412,36 @@ int StepFileWriter::ExportCurveLoop(SBezierLoop *loop, bool inner) { int thisFinish; if(loop->l.NextAfter(sb) != NULL) { - fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n", - id, CO(sb->Finish())); - fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id+1, id); - thisFinish = id + 1; + if(!HasCartesianPointAnAlias(id, sb->Finish(), id + 1, &vertex_has_alias)) { + fprintf(f, CARTESIAN_POINT_FORMAT, + id, CO(sb->Finish())); + fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id + 1, InsertPoint(id)); + thisFinish = id + 1; + } else if(!vertex_has_alias) { + fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id + 1, InsertPoint(id)); + thisFinish = id + 1; + } else { + thisFinish = InsertVertex(id+1); + } id += 2; } else { thisFinish = lastFinish; } - fprintf(f, "#%d=EDGE_CURVE('',#%d,#%d,#%d,%s);\n", - id, prevFinish, thisFinish, curveId, ".T."); - fprintf(f, "#%d=ORIENTED_EDGE('',*,*,#%d,.T.);\n", - id+1, id); - + bool flip_edge; + if (!HasEdgeCurveAnAlias(id, prevFinish, thisFinish, InsertCurve(curveId), &flip_edge)) { + fprintf(f, "#%d=EDGE_CURVE('',#%d,#%d,#%d,%s);\n", + id, prevFinish, thisFinish, InsertCurve(curveId), ".T."); + if(!HasOrientedEdgeAnAlias(id + 1, id, flip_edge)) { + fprintf(f, "#%d=ORIENTED_EDGE('',*,*,#%d,.T.);\n", id + 1, id); + } else { + ssassert(false, "Impossible"); + } + } else if(!HasOrientedEdgeAnAlias(id + 1, id, flip_edge)) { + fprintf(f, "#%d=ORIENTED_EDGE('',*,*,#%d,.%c.);\n", id + 1, InsertEdgeCurve(id), + flip_edge ? 'F' : 'T'); + } + int oe = id+1; listOfTrims.Add(&oe); id += 2; @@ -161,7 +452,7 @@ int StepFileWriter::ExportCurveLoop(SBezierLoop *loop, bool inner) { fprintf(f, "#%d=EDGE_LOOP('',(", id); int *oe; for(oe = listOfTrims.First(); oe; oe = listOfTrims.NextAfter(oe)) { - fprintf(f, "#%d", *oe); + fprintf(f, "#%d", InsertOrientedEdge(*oe)); if(listOfTrims.NextAfter(oe) != NULL) fprintf(f, ","); } fprintf(f, "));\n"); @@ -179,7 +470,24 @@ int StepFileWriter::ExportCurveLoop(SBezierLoop *loop, bool inner) { void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) { int i, j, srfid = id; - // First, we create the untrimmed surface. We always specify a rational + // Read the colour of the surface: use it to tell apart surfaces + // from different parts. + currentColor = ss->color; + + // First, define the control points for the untrimmed surface, if they + // were not already defined. + for(i = 0; i <= ss->degm; i++) { + for(j = 0; j <= ss->degn; j++) { + if (!HasCartesianPointAnAlias(srfid + 1 + j + i*(ss->degn + 1), + ss->ctrl[i][j], -1)) { + fprintf(f, CARTESIAN_POINT_FORMAT, + srfid + 1 + j + i*(ss->degn + 1), + CO(ss->ctrl[i][j])); + } + } + } + + // Then, we create the untrimmed surface. We always specify a rational // B-spline surface (in fact, just a Bezier surface). fprintf(f, "#%d=(\n", srfid); fprintf(f, "BOUNDED_SURFACE()\n"); @@ -187,7 +495,7 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) { for(i = 0; i <= ss->degm; i++) { fprintf(f, "("); for(j = 0; j <= ss->degn; j++) { - fprintf(f, "#%d", srfid + 1 + j + i*(ss->degn + 1)); + fprintf(f, "#%d", InsertPoint(srfid + 1 + j + i*(ss->degn + 1))); if(j != ss->degn) fprintf(f, ","); } fprintf(f, ")"); @@ -214,14 +522,6 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) { fprintf(f, "SURFACE()\n"); fprintf(f, ");\n"); - // The control points for the untrimmed surface. - for(i = 0; i <= ss->degm; i++) { - for(j = 0; j <= ss->degn; j++) { - fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n", - srfid + 1 + j + i*(ss->degn + 1), - CO(ss->ctrl[i][j])); - } - } fprintf(f, "\n"); id = srfid + 1 + (ss->degm + 1)*(ss->degn + 1); @@ -348,27 +648,32 @@ void StepFileWriter::ExportSurfacesTo(const Platform::Path &filename) { return; } + // Initialization of lists. + pointAliases = {}; + curveAliases = {}; + edgeCurveAliases = {}; + orientedEdgeAliases = {}; + WriteHeader(); - WriteProductHeader(); + WriteProductHeader(); advancedFaces = {}; - SSurface *ss; - for(ss = shell->surface.First(); ss; ss = shell->surface.NextAfter(ss)) { - if(ss->trim.IsEmpty()) + for(SSurface &ss : shell->surface) { + if(ss.trim.IsEmpty()) continue; // Get all of the loops of Beziers that trim our surface (with each // Bezier split so that we use the section as t goes from 0 to 1), and // the piecewise linearization of those loops in xyz space. SBezierList sbl = {}; - ss->MakeSectionEdgesInto(shell, NULL, &sbl); + ss.MakeSectionEdgesInto(shell, NULL, &sbl); // Apply the export scale factor. - ss->ScaleSelfBy(1.0/SS.exportScale); + ss.ScaleSelfBy(1.0/SS.exportScale); sbl.ScaleSelfBy(1.0/SS.exportScale); - ExportSurface(ss, &sbl); + ExportSurface(&ss, &sbl); sbl.Clear(); } @@ -390,6 +695,10 @@ void StepFileWriter::ExportSurfacesTo(const Platform::Path &filename) { fclose(f); advancedFaces.Clear(); + pointAliases.clear(); + curveAliases.clear(); + edgeCurveAliases.clear(); + orientedEdgeAliases.clear(); } void StepFileWriter::WriteWireframe() { @@ -409,3 +718,4 @@ void StepFileWriter::WriteWireframe() { curves.Clear(); } +} // namespace SolveSpace diff --git a/src/exportvector.cpp b/src/exportvector.cpp index 977309976..175399730 100644 --- a/src/exportvector.cpp +++ b/src/exportvector.cpp @@ -6,6 +6,8 @@ #include #include "solvespace.h" +namespace SolveSpace { + //----------------------------------------------------------------------------- // Routines for DXF export //----------------------------------------------------------------------------- @@ -134,9 +136,9 @@ class DxfWriteInterface : public DRW_Interface { polyline.flags |= 8 /* 3d polyline */; } polyline.vertlist.push_back( - new DRW_Vertex(start->pos.x, start->pos.y, start->pos.z, 0.0)); + std::make_shared(start->pos.x, start->pos.y, start->pos.z, 0.0)); polyline.vertlist.push_back( - new DRW_Vertex(next->pos.x, next->pos.y, next->pos.z, 0.0)); + std::make_shared(next->pos.x, next->pos.y, next->pos.z, 0.0)); }; auto nextFunc = [&](PolylineBuilder::Vertex *next, PolylineBuilder::Edge *e) { @@ -144,7 +146,7 @@ class DxfWriteInterface : public DRW_Interface { polyline.flags |= 8 /* 3d polyline */; } polyline.vertlist.push_back( - new DRW_Vertex(next->pos.x, next->pos.y, next->pos.z, 0.0)); + std::make_shared(next->pos.x, next->pos.y, next->pos.z, 0.0)); }; auto endFunc = [&]() { @@ -170,22 +172,21 @@ class DxfWriteInterface : public DRW_Interface { } if(writer->constraint) { - Constraint *c; - for(c = writer->constraint->First(); c; c = writer->constraint->NextAfter(c)) { - if(!writer->NeedToOutput(c)) continue; - switch(c->type) { + for(Constraint &c : *writer->constraint) { + if(!writer->NeedToOutput(&c)) continue; + switch(c.type) { case Constraint::Type::PT_PT_DISTANCE: { - Vector ap = SK.GetEntity(c->ptA)->PointGetNum(); - Vector bp = SK.GetEntity(c->ptB)->PointGetNum(); - Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(c->disp.offset); + Vector ap = SK.GetEntity(c.ptA)->PointGetNum(); + Vector bp = SK.GetEntity(c.ptB)->PointGetNum(); + Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(c.disp.offset); writeAlignedDimension(xfrm(ap), xfrm(bp), xfrm(ref), - xfrm(ref), c->Label(), c->GetStyle(), c->valA); + xfrm(ref), c.Label(), c.GetStyle(), c.valA); break; } case Constraint::Type::PT_LINE_DISTANCE: { - Vector pt = SK.GetEntity(c->ptA)->PointGetNum(); - Entity *line = SK.GetEntity(c->entityA); + Vector pt = SK.GetEntity(c.ptA)->PointGetNum(); + Entity *line = SK.GetEntity(c.entityA); Vector lA = SK.GetEntity(line->point[0])->PointGetNum(); Vector lB = SK.GetEntity(line->point[1])->PointGetNum(); Vector dl = lB.Minus(lA); @@ -194,7 +195,7 @@ class DxfWriteInterface : public DRW_Interface { if(pt.Equals(closest)) break; - Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(c->disp.offset); + Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(c.disp.offset); Vector refClosest = ref.ClosestPointOnLine(lA, dl); double ddl = dl.Dot(dl); @@ -209,54 +210,54 @@ class DxfWriteInterface : public DRW_Interface { Vector xdl = xfrm(lB).Minus(xfrm(lA)); writeLinearDimension(xfrm(pt), xfrm(refClosest), xfrm(ref), - xfrm(ref), c->Label(), + xfrm(ref), c.Label(), atan2(xdl.y, xdl.x) / PI * 180.0 + 90.0, 0.0, - c->GetStyle(), c->valA); + c.GetStyle(), c.valA); break; } case Constraint::Type::DIAMETER: { - Entity *circle = SK.GetEntity(c->entityA); + Entity *circle = SK.GetEntity(c.entityA); Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum(); Vector n = q.RotationN().WithMagnitude(1); double r = circle->CircleGetRadiusNum(); - Vector ref = center.Plus(c->disp.offset); + Vector ref = center.Plus(c.disp.offset); // Force the label into the same plane as the circle. ref = ref.Minus(n.ScaledBy(n.Dot(ref) - n.Dot(center))); Vector rad = ref.Minus(center).WithMagnitude(r); - if(/*isRadius*/c->other) { + if(/*isRadius*/c.other) { writeRadialDimension( xfrm(center), xfrm(center.Plus(rad)), - xfrm(ref), c->Label(), c->GetStyle(), c->valA); + xfrm(ref), c.Label(), c.GetStyle(), c.valA); } else { writeDiametricDimension( xfrm(center.Minus(rad)), xfrm(center.Plus(rad)), - xfrm(ref), c->Label(), c->GetStyle(), c->valA); + xfrm(ref), c.Label(), c.GetStyle(), c.valA); } break; } case Constraint::Type::ANGLE: { - Entity *a = SK.GetEntity(c->entityA); - Entity *b = SK.GetEntity(c->entityB); + Entity *a = SK.GetEntity(c.entityA); + Entity *b = SK.GetEntity(c.entityB); Vector a0 = a->VectorGetStartPoint(); Vector b0 = b->VectorGetStartPoint(); Vector da = a->VectorGetNum(); Vector db = b->VectorGetNum(); - if(/*otherAngle*/c->other) { + if(/*otherAngle*/c.other) { a0 = a0.Plus(da); da = da.ScaledBy(-1); } bool skew = false; - Vector ref = c->disp.offset; + Vector ref = c.disp.offset; Vector pi = Vector::AtIntersectionOfLines(a0, a0.Plus(da), b0, b0.Plus(db), &skew); - if(!skew) ref = pi.Plus(c->disp.offset); + if(!skew) ref = pi.Plus(c.disp.offset); Vector norm = da.Cross(db); Vector dna = norm.Cross(da).WithMagnitude(1.0); @@ -277,7 +278,7 @@ class DxfWriteInterface : public DRW_Interface { Vector bisect = da.WithMagnitude(1.0).ScaledBy(cos(thetaf / 2.0)).Plus( dna.ScaledBy(sin(thetaf / 2.0))); - ref = pi.Plus(bisect.WithMagnitude(c->disp.offset.Magnitude())); + ref = pi.Plus(bisect.WithMagnitude(c.disp.offset.Magnitude())); // Get lines again to write exact line. a0 = a->VectorGetStartPoint(); @@ -287,15 +288,15 @@ class DxfWriteInterface : public DRW_Interface { writeAngularDimension( xfrm(a0), xfrm(a0.Plus(da)), xfrm(b0), xfrm(b0.Plus(db)), xfrm(ref), - xfrm(ref), c->Label(), c->GetStyle(), c->valA); + xfrm(ref), c.Label(), c.GetStyle(), c.valA); break; } case Constraint::Type::COMMENT: { - Style *st = SK.style.FindById(c->GetStyle()); - writeText(xfrm(c->disp.offset), c->Label(), - Style::TextHeight(c->GetStyle()) / SS.GW.scale, - st->textAngle, st->textOrigin, c->GetStyle()); + Style *st = SK.style.FindById(c.GetStyle()); + writeText(xfrm(c.disp.offset), c.Label(), + Style::TextHeight(c.GetStyle()) / SS.GW.scale, + st->textAngle, st->textOrigin, c.GetStyle()); break; } @@ -371,8 +372,7 @@ class DxfWriteInterface : public DRW_Interface { assignEntityDefaults(&polyline, hs); for(int i = 0; i < lv.n; i++) { Vector *v = &lv[i]; - DRW_Vertex *vertex = new DRW_Vertex(v->x, v->y, v->z, 0.0); - polyline.vertlist.push_back(vertex); + polyline.vertlist.push_back(std::make_shared(v->x, v->y, v->z, 0.0)); } dxf->writePolyline(&polyline); lv.Clear(); @@ -412,7 +412,7 @@ class DxfWriteInterface : public DRW_Interface { makeKnotsFor(&spline); for(int i = 0; i <= sb->deg; i++) { spline.controllist.push_back( - new DRW_Coord(sb->ctrl[i].x, sb->ctrl[i].y, sb->ctrl[i].z)); + std::make_shared(sb->ctrl[i].x, sb->ctrl[i].y, sb->ctrl[i].z)); if(isRational) spline.weightlist.push_back(sb->weight[i]); } dxf->writeSpline(&spline); @@ -1103,7 +1103,7 @@ void SvgFileWriter::StartFile() { fprintf(f, "stroke-dasharray:%s;\r\n", patternStr.c_str()); } if(s->filled) { - fprintf(f, "fill:#%02x%02x%02x;\r\n", fillRgb.red, fillRgb.green, fillRgb.blue); + fprintf(f, "fill:#%02x%02x%02x;\r\n", fillRgb.red, fillRgb.green, fillRgb.blue); } else { fprintf(f, "fill:none;\r\n"); @@ -1309,9 +1309,9 @@ void GCodeFileWriter::FinishAndCloseFile() { SS.MmToString(pt->p.x).c_str(), SS.MmToString(pt->p.y).c_str(), SS.MmToString(SS.gCode.feed).c_str()); } - // Move up to a clearance plane 5mm above the work. + // Move up to a clearance plane above the work. fprintf(f, "G00 Z%s\r\n", - SS.MmToString(SS.gCode.depth < 0 ? +5 : -5).c_str()); + SS.MmToString(SS.gCode.safeHeight).c_str()); } } @@ -1357,3 +1357,4 @@ void Step2dFileWriter::FinishAndCloseFile() { fclose(f); } +} // namespace SolveSpace diff --git a/src/expr.cpp b/src/expr.cpp index 99d68ec4a..6b077fa58 100644 --- a/src/expr.cpp +++ b/src/expr.cpp @@ -8,6 +8,12 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + +static inline Expr *AllocExpr() { + return (Expr *)Platform::AllocTemporary(sizeof(Expr)); +} + ExprVector ExprVector::From(Expr *x, Expr *y, Expr *z) { ExprVector r = { x, y, z}; return r; @@ -306,9 +312,8 @@ Expr *Expr::DeepCopy() const { return n; } -Expr *Expr::DeepCopyWithParamsAsPointers(IdList *firstTry, - IdList *thenTry) const -{ +Expr *Expr::DeepCopyWithParamsAsPointers(ParamList *firstTry, ParamList *thenTry, + bool foldConstants) const { Expr *n = AllocExpr(); if(op == Op::PARAM) { // A param that is referenced by its hParam gets rewritten to go @@ -328,8 +333,17 @@ Expr *Expr::DeepCopyWithParamsAsPointers(IdList *firstTry, *n = *this; int c = n->Children(); - if(c > 0) n->a = a->DeepCopyWithParamsAsPointers(firstTry, thenTry); - if(c > 1) n->b = b->DeepCopyWithParamsAsPointers(firstTry, thenTry); + if(c > 0) { + n->a = a->DeepCopyWithParamsAsPointers(firstTry, thenTry, foldConstants); + bool hasConstants = n->a->op == Op::CONSTANT; + if(c > 1) { + n->b = b->DeepCopyWithParamsAsPointers(firstTry, thenTry, foldConstants); + hasConstants |= n->b->op == Op::CONSTANT; + } + if(hasConstants && foldConstants) { + n = n->FoldConstants(false, 0); + } + } return n; } @@ -400,15 +414,19 @@ Expr *Expr::PartialWrt(hParam p) const { ssassert(false, "Unexpected operation"); } -uint64_t Expr::ParamsUsed() const { - uint64_t r = 0; - if(op == Op::PARAM) r |= ((uint64_t)1 << (parh.v % 61)); - if(op == Op::PARAM_PTR) r |= ((uint64_t)1 << (parp->h.v % 61)); +void Expr::ParamsUsedList(ParamSet *list) const { + if(op == Op::PARAM || op == Op::PARAM_PTR) { + // leaf: just add ourselves if we aren't already there + hParam param = (op == Op::PARAM) ? parh : parp->h; + list->insert(param); + return; + } int c = Children(); - if(c >= 1) r |= a->ParamsUsed(); - if(c >= 2) r |= b->ParamsUsed(); - return r; + if(c >= 1) { + a->ParamsUsedList(list); + if(c >= 2) b->ParamsUsedList(list); + } } bool Expr::DependsOn(hParam p) const { @@ -424,13 +442,20 @@ bool Expr::DependsOn(hParam p) const { bool Expr::Tol(double a, double b) { return fabs(a - b) < 0.001; } -Expr *Expr::FoldConstants() { - Expr *n = AllocExpr(); - *n = *this; - int c = Children(); - if(c >= 1) n->a = a->FoldConstants(); - if(c >= 2) n->b = b->FoldConstants(); +bool Expr::IsZeroConst() const { + return op == Op::CONSTANT && EXACT(v == 0.0); +} + +Expr *Expr::FoldConstants(bool allocCopy, size_t depth) { + Expr *n; + + if(!allocCopy) { + n = this; + } else { + n = AllocExpr(); + *n = *this; + } switch(op) { case Op::PARAM_PTR: @@ -443,6 +468,11 @@ Expr *Expr::FoldConstants() { case Op::TIMES: case Op::DIV: case Op::PLUS: + if(depth > 0) { + n->a = a->FoldConstants(allocCopy, depth - 1); + n->b = b->FoldConstants(allocCopy, depth - 1); + } + // If both ops are known, then we can evaluate immediately if(n->a->op == Op::CONSTANT && n->b->op == Op::CONSTANT) { double nv = n->Eval(); @@ -481,6 +511,10 @@ Expr *Expr::FoldConstants() { case Op::COS: case Op::ASIN: case Op::ACOS: + if(depth > 0) { + n->a = a->FoldConstants(allocCopy, depth - 1); + } + if(n->a->op == Op::CONSTANT) { double nv = n->Eval(); n->op = Op::CONSTANT; @@ -491,15 +525,21 @@ Expr *Expr::FoldConstants() { return n; } -void Expr::Substitute(hParam oldh, hParam newh) { +void Expr::Substitute(const SubstitutionMap &subMap) { ssassert(op != Op::PARAM_PTR, "Expected an expression that refer to params via handles"); - if(op == Op::PARAM && parh == oldh) { - parh = newh; + if(op == Op::PARAM) { + auto it = subMap.find(parh); + if(it != subMap.end()) { + parh = it->second->h; + } + } else { + int c = Children(); + if(c >= 1) { + a->Substitute(subMap); + if(c >= 2) b->Substitute(subMap); + } } - int c = Children(); - if(c >= 1) a->Substitute(oldh, newh); - if(c >= 2) b->Substitute(oldh, newh); } //----------------------------------------------------------------------------- @@ -508,7 +548,7 @@ void Expr::Substitute(hParam oldh, hParam newh) { // If multiple params are referenced, then return MULTIPLE_PARAMS. //----------------------------------------------------------------------------- const hParam Expr::NO_PARAMS = { 0 }; -const hParam Expr::MULTIPLE_PARAMS = { 1 }; +const hParam Expr::MULTIPLE_PARAMS = { std::numeric_limits::max() }; hParam Expr::ReferencedParams(ParamList *pl) const { if(op == Op::PARAM) { if(pl->FindByIdNoOops(parh)) { @@ -636,7 +676,7 @@ ExprParser::Token ExprParser::Token::From(TokenType type, Expr *expr) { ExprParser::Token ExprParser::Token::From(TokenType type, Expr::Op op) { Token t; t.type = type; - t.expr = Expr::AllocExpr(); + t.expr = AllocExpr(); t.expr->op = op; return t; } @@ -919,3 +959,5 @@ Expr *Expr::From(const std::string &input, bool popUpError) { } return e; } + +} // namespace SolveSpace diff --git a/src/expr.h b/src/expr.h index 7109cf651..eb0191722 100644 --- a/src/expr.h +++ b/src/expr.h @@ -7,6 +7,18 @@ #ifndef SOLVESPACE_EXPR_H #define SOLVESPACE_EXPR_H +#include +#include +#include +#include + +#include "dsc.h" +#include "param.h" + +namespace SolveSpace { + +using SubstitutionMap = std::unordered_map>; + class Expr { public: @@ -48,9 +60,6 @@ class Expr { Expr() = default; Expr(double val) : op(Op::CONSTANT) { v = val; } - static inline Expr *AllocExpr() - { return (Expr *)AllocTemporary(sizeof(Expr)); } - static Expr *From(hParam p); static Expr *From(double v); @@ -70,11 +79,12 @@ class Expr { Expr *PartialWrt(hParam p) const; double Eval() const; - uint64_t ParamsUsed() const; + void ParamsUsedList(ParamSet *list) const; bool DependsOn(hParam p) const; static bool Tol(double a, double b); - Expr *FoldConstants(); - void Substitute(hParam oldh, hParam newh); + bool IsZeroConst() const; + Expr *FoldConstants(bool allocCopy = true, size_t depth = std::numeric_limits::max()); + void Substitute(const SubstitutionMap &subMap); static const hParam NO_PARAMS, MULTIPLE_PARAMS; hParam ReferencedParams(ParamList *pl) const; @@ -93,8 +103,9 @@ class Expr { // Make a copy, with the parameters (usually referenced by hParam) // resolved to pointers to the actual value. This speeds things up // considerably. - Expr *DeepCopyWithParamsAsPointers(IdList *firstTry, - IdList *thenTry) const; + Expr *DeepCopyWithParamsAsPointers(ParamList *firstTry, + ParamList *thenTry, + bool foldConstants = false) const; static Expr *Parse(const std::string &input, std::string *error); static Expr *From(const std::string &input, bool popUpError); @@ -137,4 +148,7 @@ class ExprQuaternion { Expr *Magnitude() const; }; + +} // namespace SolveSpace + #endif diff --git a/src/file.cpp b/src/file.cpp index 160b5ec7b..86da16b87 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -5,6 +5,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + #define VERSION_STRING "\261\262\263" "SolveSpaceREVa" static int StrStartsWith(const char *str, const char *start) { @@ -246,7 +248,7 @@ void SolveSpaceUI::SaveUsingTable(const Platform::Path &filename, int type) { case 'P': { if(!p->P().IsEmpty()) { - Platform::Path relativePath = p->P().RelativeTo(filename.Parent()); + Platform::Path relativePath = p->P().Expand(/*fromCurrentDirectory=*/true).RelativeTo(filename.Expand(/*fromCurrentDirectory=*/true).Parent()); ssassert(!relativePath.IsEmpty(), "Cannot relativize path"); fprintf(fh, "%s", relativePath.ToPortable().c_str()); } @@ -261,7 +263,7 @@ void SolveSpaceUI::SaveUsingTable(const Platform::Path &filename, int type) { [](std::pair &a, std::pair &b) { return a.second.v < b.second.v; }); - for(auto it : sorted) { + for(const auto &it : sorted) { fprintf(fh, " %d %08x %d\n", it.second.v, it.first.input.v, it.first.copyNumber); } @@ -285,7 +287,12 @@ bool SolveSpaceUI::SaveToFile(const Platform::Path &filename) { for(Group &g : SK.group) { if(g.type != Group::Type::LINKED) continue; - if(g.linkFile.RelativeTo(filename).IsEmpty()) { + // Expand for "filename" below is needed on Linux when the file was opened with a relative + // path on the command line. dialog->RunModal() in SolveSpaceUI::GetFilenameAndSave will + // convert the file name to full path on Windows but not on GTK. + if(g.linkFile.Expand(/*fromCurrentDirectory=*/true) + .RelativeTo(filename.Expand(/*fromCurrentDirectory=*/true)) + .IsEmpty()) { Error("This sketch links the sketch '%s'; it can only be saved " "on the same volume.", g.linkFile.raw.c_str()); return false; @@ -354,19 +361,18 @@ bool SolveSpaceUI::SaveToFile(const Platform::Path &filename) { } SShell *s = &g->runningShell; - SSurface *srf; - for(srf = s->surface.First(); srf; srf = s->surface.NextAfter(srf)) { + for(SSurface &srf : s->surface) { fprintf(fh, "Surface %08x %08x %08x %d %d\n", - srf->h.v, srf->color.ToPackedInt(), srf->face, srf->degm, srf->degn); - for(i = 0; i <= srf->degm; i++) { - for(j = 0; j <= srf->degn; j++) { + srf.h.v, srf.color.ToPackedInt(), srf.face, srf.degm, srf.degn); + for(i = 0; i <= srf.degm; i++) { + for(j = 0; j <= srf.degn; j++) { fprintf(fh, "SCtrl %d %d %.20f %.20f %.20f Weight %20.20f\n", - i, j, CO(srf->ctrl[i][j]), srf->weight[i][j]); + i, j, CO(srf.ctrl[i][j]), srf.weight[i][j]); } } STrimBy *stb; - for(stb = srf->trim.First(); stb; stb = srf->trim.NextAfter(stb)) { + for(stb = srf.trim.First(); stb; stb = srf.trim.NextAfter(stb)) { fprintf(fh, "TrimBy %08x %d %.20f %.20f %.20f %.20f %.20f %.20f\n", stb->curve.v, stb->backwards ? 1 : 0, CO(stb->start), CO(stb->finish)); @@ -374,21 +380,20 @@ bool SolveSpaceUI::SaveToFile(const Platform::Path &filename) { fprintf(fh, "AddSurface\n"); } - SCurve *sc; - for(sc = s->curve.First(); sc; sc = s->curve.NextAfter(sc)) { + for(SCurve &sc : s->curve) { fprintf(fh, "Curve %08x %d %d %08x %08x\n", - sc->h.v, - sc->isExact ? 1 : 0, sc->exact.deg, - sc->surfA.v, sc->surfB.v); + sc.h.v, + sc.isExact ? 1 : 0, sc.exact.deg, + sc.surfA.v, sc.surfB.v); - if(sc->isExact) { - for(i = 0; i <= sc->exact.deg; i++) { + if(sc.isExact) { + for(i = 0; i <= sc.exact.deg; i++) { fprintf(fh, "CCtrl %d %.20f %.20f %.20f Weight %.20f\n", - i, CO(sc->exact.ctrl[i]), sc->exact.weight[i]); + i, CO(sc.exact.ctrl[i]), sc.exact.weight[i]); } } SCurvePt *scpt; - for(scpt = sc->pts.First(); scpt; scpt = sc->pts.NextAfter(scpt)) { + for(scpt = sc.pts.First(); scpt; scpt = sc.pts.NextAfter(scpt)) { fprintf(fh, "CurvePt %d %.20f %.20f %.20f\n", scpt->vertex ? 1 : 0, CO(scpt->p)); } @@ -578,8 +583,8 @@ void SolveSpaceUI::UpgradeLegacyData() { // at workplane origin, and the solver will mess up the sketch if // it is not fully constrained. case Request::Type::TTF_TEXT: { - IdList entity = {}; - IdList param = {}; + EntityList entity = {}; + ParamList param = {}; r.Generate(&entity, ¶m); // If we didn't load all of the entities and params that this @@ -618,12 +623,12 @@ void SolveSpaceUI::UpgradeLegacyData() { // Constraints saved in versions prior to 3.0 never had any params; // version 3.0 introduced params to constraints to avoid the hairy ball problem, // so force them where they belong. - IdList oldParam = {}; + ParamList oldParam = {}; SK.param.DeepCopyInto(&oldParam); SS.GenerateAll(SolveSpaceUI::Generate::REGEN); auto AllParamsExistFor = [&](Constraint &c) { - IdList param = {}; + ParamList param = {}; c.Generate(¶m); bool allParamsExist = true; for(Param &p : param) { @@ -712,7 +717,13 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const Platform::Path &filename, EntityLi SMesh *m, SShell *sh) { if(strcmp(filename.Extension().c_str(), "emn")==0) { - return LinkIDF(filename, le, m, sh); + return LinkIDF(filename, le, m, sh); + } else if(strcmp(filename.Extension().c_str(), "EMN")==0) { + return LinkIDF(filename, le, m, sh); + } else if(strcmp(filename.Extension().c_str(), "stl")==0) { + return LinkStl(filename, le, m, sh); + } else if(strcmp(filename.Extension().c_str(), "STL")==0) { + return LinkStl(filename, le, m, sh); } else { return LoadEntitiesFromSlvs(filename, le, m, sh); } @@ -909,6 +920,8 @@ bool SolveSpaceUI::ReloadAllLinked(const Platform::Path &saveFile, bool canCance return false; } } + if(g.IsTriangleMeshAssembly()) + g.forceToMesh = true; } else if(linkMap.count(g.linkFile) == 0) { dbp("Missing file for group: %s", g.name.c_str()); // The file was moved; prompt the user for its new location. @@ -916,7 +929,7 @@ bool SolveSpaceUI::ReloadAllLinked(const Platform::Path &saveFile, bool canCance switch(LocateImportedFile(linkFileRelative, canCancel)) { case Platform::MessageDialog::Response::YES: { Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window); - dialog->AddFilters(Platform::SolveSpaceModelFileFilters); + dialog->AddFilters(Platform::SolveSpaceLinkFileFilters); dialog->ThawChoices(settings, "LinkSketch"); dialog->SuggestFilename(linkFileRelative); if(dialog->RunModal()) { @@ -1013,3 +1026,5 @@ bool SolveSpaceUI::ReloadLinkedImage(const Platform::Path &saveFile, SS.images[*filename] = pixmap; return true; } + +} // namespace SolveSpace diff --git a/src/generate.cpp b/src/generate.cpp index fdda99462..4d38a5204 100644 --- a/src/generate.cpp +++ b/src/generate.cpp @@ -8,6 +8,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + void SolveSpaceUI::MarkGroupDirtyByEntity(hEntity he) { Entity *e = SK.GetEntity(he); MarkGroupDirty(e->group); @@ -30,24 +32,24 @@ void SolveSpaceUI::MarkGroupDirty(hGroup hg, bool onlyThis) { } bool SolveSpaceUI::PruneOrphans() { - - auto r = std::find_if(SK.request.begin(), SK.request.end(), - [&](Request &r) { return !GroupExists(r.group); }); - if(r != SK.request.end()) { - (deleted.requests)++; - SK.request.RemoveById(r->h); - return true; + const int requests = SK.request.n; + for(Request &r : SK.request) { + if(!GroupExists(r.group)) + r.tag = 1; } + SK.request.RemoveTagged(); + deleted.requests += requests - SK.request.n; - auto c = std::find_if(SK.constraint.begin(), SK.constraint.end(), - [&](Constraint &c) { return !GroupExists(c.group); }); - if(c != SK.constraint.end()) { - (deleted.constraints)++; - (deleted.nonTrivialConstraints)++; - SK.constraint.RemoveById(c->h); - return true; + const int constraints = SK.constraint.n; + for(Constraint &c : SK.constraint) { + if(!GroupExists(c.group)) + c.tag = 1; } - return false; + SK.constraint.RemoveTagged(); + deleted.constraints += constraints - SK.constraint.n; + deleted.nonTrivialConstraints += constraints - SK.constraint.n; + + return (requests > SK.request.n) || (constraints > SK.constraint.n); } bool SolveSpaceUI::GroupsInOrder(hGroup before, hGroup after) { @@ -86,46 +88,65 @@ bool SolveSpaceUI::PruneGroups(hGroup hg) { return true; } -bool SolveSpaceUI::PruneRequests(hGroup hg) { - auto e = std::find_if(SK.entity.begin(), SK.entity.end(), - [&](Entity &e) { return e.group == hg && !EntityExists(e.workplane); }); - if(e != SK.entity.end()) { - (deleted.requests)++; - SK.entity.RemoveById(e->h); - return true; +bool SolveSpaceUI::PruneRequestsAndConstraints(hGroup hg) { + auto entityRequestExists = [](hEntity he, bool checkEntity = false) { + if(he == Entity::NO_ENTITY) { + return true; + } + + if(he.isFromRequest()) { + if(SK.request.FindByIdNoOops(he.request()) != nullptr) { + return true; + } + } else if(!checkEntity || SK.entity.FindByIdNoOops(he) != nullptr) { + return true; + } + + return false; + }; + + const int requests = SK.request.n; + for(Request &r : SK.request) { + if(r.group != hg) { + continue; + } + + if(entityRequestExists(r.workplane)) { + continue; + } + + r.tag = 1; } - return false; -} + SK.request.RemoveTagged(); + deleted.requests += requests - SK.request.n; -bool SolveSpaceUI::PruneConstraints(hGroup hg) { - auto c = std::find_if(SK.constraint.begin(), SK.constraint.end(), [&](Constraint &c) { + const int constraints = SK.constraint.n; + for(Constraint &c : SK.constraint) { if(c.group != hg) - return false; - - if(EntityExists(c.workplane) && - EntityExists(c.ptA) && - EntityExists(c.ptB) && - EntityExists(c.entityA) && - EntityExists(c.entityB) && - EntityExists(c.entityC) && - EntityExists(c.entityD)) { - return false; + continue; + + if(entityRequestExists(c.workplane, true) && + entityRequestExists(c.ptA, true) && + entityRequestExists(c.ptB, true) && + entityRequestExists(c.entityA, true) && + entityRequestExists(c.entityB, true) && + entityRequestExists(c.entityC, true) && + entityRequestExists(c.entityD, true)) { + continue; } - return true; - }); - - if(c != SK.constraint.end()) { - (deleted.constraints)++; - if(c->type != Constraint::Type::POINTS_COINCIDENT && - c->type != Constraint::Type::HORIZONTAL && - c->type != Constraint::Type::VERTICAL) { + + if(c.type != Constraint::Type::POINTS_COINCIDENT && + c.type != Constraint::Type::HORIZONTAL && + c.type != Constraint::Type::VERTICAL) { (deleted.nonTrivialConstraints)++; } - SK.constraint.RemoveById(c->h); - return true; + c.tag = 1; } - return false; + SK.constraint.RemoveTagged(); + deleted.constraints += constraints - SK.constraint.n; + + return (requests > SK.request.n) || (constraints > SK.constraint.n); } void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) { @@ -203,11 +224,10 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) // Remove any requests or constraints that refer to a nonexistent // group; can check those immediately, since we know what the list // of groups should be. - while(PruneOrphans()) - ; + PruneOrphans(); // Don't lose our numerical guesses when we regenerate. - IdList prev = {}; + ParamList prev = {}; SK.param.MoveSelfInto(&prev); SK.param.ReserveMore(prev.n); int oldEntityCount = SK.entity.n; @@ -224,9 +244,11 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) if(PruneGroups(hg)) goto pruned; + int groupRequestIndex = 0; for(auto &req : SK.request) { Request *r = &req; if(r->group != hg) continue; + r->groupRequestIndex = groupRequestIndex++; r->Generate(&(SK.entity), &(SK.param)); } @@ -240,7 +262,7 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) // The requests and constraints depend on stuff in this or the // previous group, so check them after generating. - if(PruneRequests(hg) || PruneConstraints(hg)) + if(PruneRequestsAndConstraints(hg)) goto pruned; // Use the previous values for params that we've seen before, as @@ -323,11 +345,14 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) ScheduleShowTW(); GW.ClearSuper(); + auto deletedStat = deleted; + deleted = {}; + // People get annoyed if I complain whenever they delete any request, // and I otherwise will, since those always come with pt-coincident // constraints. - if(deleted.requests > 0 || deleted.nonTrivialConstraints > 0 || - deleted.groups > 0) + if(deletedStat.requests > 0 || deletedStat.nonTrivialConstraints > 0 || + deletedStat.groups > 0) { // Don't display any errors until we've regenerated fully. The // sketch is not necessarily in a consistent state until we've @@ -341,16 +366,14 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) " %d constraint%s\n" " %d group%s" "%s", - deleted.requests, deleted.requests == 1 ? "" : "s", - deleted.constraints, deleted.constraints == 1 ? "" : "s", - deleted.groups, deleted.groups == 1 ? "" : "s", + deletedStat.requests, deletedStat.requests == 1 ? "" : "s", + deletedStat.constraints, deletedStat.constraints == 1 ? "" : "s", + deletedStat.groups, deletedStat.groups == 1 ? "" : "s", undo.cnt > 0 ? "\n\nChoose Edit -> Undo to undelete all elements." : ""); } - - deleted = {}; } - FreeAllTemporary(); + Platform::FreeAllTemporary(); allConsistent = true; SS.GW.persistentDirty = true; SS.centerOfMass.dirty = true; @@ -420,7 +443,7 @@ void SolveSpaceUI::UpdateCenterOfMass() { } void SolveSpaceUI::MarkDraggedParams() { - sys.dragged.Clear(); + sys.dragged.clear(); for(int i = -1; i < SS.GW.pending.points.n; i++) { hEntity hp; @@ -440,14 +463,14 @@ void SolveSpaceUI::MarkDraggedParams() { case Entity::Type::POINT_N_TRANS: case Entity::Type::POINT_IN_3D: case Entity::Type::POINT_N_ROT_AXIS_TRANS: - sys.dragged.Add(&(pt->param[0])); - sys.dragged.Add(&(pt->param[1])); - sys.dragged.Add(&(pt->param[2])); + sys.dragged.insert(pt->param[0]); + sys.dragged.insert(pt->param[1]); + sys.dragged.insert(pt->param[2]); break; case Entity::Type::POINT_IN_2D: - sys.dragged.Add(&(pt->param[0])); - sys.dragged.Add(&(pt->param[1])); + sys.dragged.insert(pt->param[0]); + sys.dragged.insert(pt->param[1]); break; default: // Only the entities above can be dragged. @@ -461,7 +484,7 @@ void SolveSpaceUI::MarkDraggedParams() { Entity *dist = SK.GetEntity(circ->distance); switch(dist->type) { case Entity::Type::DISTANCE: - sys.dragged.Add(&(dist->param[0])); + sys.dragged.insert(dist->param[0]); break; default: // Only the entities above can be dragged. @@ -474,10 +497,10 @@ void SolveSpaceUI::MarkDraggedParams() { if(norm) { switch(norm->type) { case Entity::Type::NORMAL_IN_3D: - sys.dragged.Add(&(norm->param[0])); - sys.dragged.Add(&(norm->param[1])); - sys.dragged.Add(&(norm->param[2])); - sys.dragged.Add(&(norm->param[3])); + sys.dragged.insert(norm->param[0]); + sys.dragged.insert(norm->param[1]); + sys.dragged.insert(norm->param[2]); + sys.dragged.insert(norm->param[3]); break; default: // Only the entities above can be dragged. @@ -534,8 +557,7 @@ void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) { Group *g = SK.GetGroup(hg); g->solved.remove.Clear(); g->solved.findToFixTimeout = SS.timeoutRedundantConstr; - SolveResult how = sys.Solve(g, NULL, - &(g->solved.dof), + SolveResult how = sys.Solve(g, &(g->solved.dof), &(g->solved.remove), /*andFindBad=*/!g->allowRedundant, /*andFindFree=*/andFindFree, @@ -544,14 +566,17 @@ void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) { g->dofCheckOk = true; } g->solved.how = how; - FreeAllTemporary(); + Platform::FreeAllTemporary(); } SolveResult SolveSpaceUI::TestRankForGroup(hGroup hg, int *rank) { - WriteEqSystemForGroup(hg); Group *g = SK.GetGroup(hg); + // If we don't calculate dof or redundant is allowed, there is + // no point to solve rank because this result is not meaningful + if(g->suppressDofCalculation || g->allowRedundant) return SolveResult::OKAY; + WriteEqSystemForGroup(hg); SolveResult result = sys.SolveRank(g, rank); - FreeAllTemporary(); + Platform::FreeAllTemporary(); return result; } @@ -566,3 +591,4 @@ bool SolveSpaceUI::ActiveGroupsOkay() { return true; } +} // namespace SolveSpace diff --git a/src/graphicswin.cpp b/src/graphicswin.cpp index 8427fa1e9..98ceb739a 100644 --- a/src/graphicswin.cpp +++ b/src/graphicswin.cpp @@ -6,6 +6,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + typedef void MenuHandler(Command id); using MenuKind = Platform::MenuItem::Indicator; struct MenuEntry { @@ -43,7 +45,7 @@ const MenuEntry Menu[] = { { 1, N_("&Open..."), Command::OPEN, C|'o', KN, mFile }, { 1, N_("Open &Recent"), Command::OPEN_RECENT, 0, KN, mFile }, { 1, N_("&Save"), Command::SAVE, C|'s', KN, mFile }, -{ 1, N_("Save &As..."), Command::SAVE_AS, 0, KN, mFile }, +{ 1, N_("Save &As..."), Command::SAVE_AS, C|S|'s', KN, mFile }, { 1, NULL, Command::NONE, 0, KN, NULL }, { 1, N_("Export &Image..."), Command::EXPORT_IMAGE, 0, KN, mFile }, { 1, N_("Export 2d &View..."), Command::EXPORT_VIEW, 0, KN, mFile }, @@ -94,12 +96,14 @@ const MenuEntry Menu[] = { { 1, N_("Show Snap &Grid"), Command::SHOW_GRID, '>', KC, mView }, { 1, N_("Darken Inactive Solids"), Command::DIM_SOLID_MODEL, 0, KC, mView }, { 1, N_("Use &Perspective Projection"), Command::PERSPECTIVE_PROJ, '`', KC, mView }, +{ 1, N_("Show E&xploded View"), Command::EXPLODE_SKETCH, '\\', KC, mView }, { 1, N_("Dimension &Units"), Command::NONE, 0, KN, NULL }, { 2, N_("Dimensions in &Millimeters"), Command::UNITS_MM, 0, KR, mView }, { 2, N_("Dimensions in M&eters"), Command::UNITS_METERS, 0, KR, mView }, { 2, N_("Dimensions in &Inches"), Command::UNITS_INCHES, 0, KR, mView }, +{ 2, N_("Dimensions in &Feet and Inches"), Command::UNITS_FEET_INCHES, 0, KR, mView }, { 1, NULL, Command::NONE, 0, KN, NULL }, -{ 1, N_("Show &Toolbar"), Command::SHOW_TOOLBAR, 0, KC, mView }, +{ 1, N_("Show &Toolbar"), Command::SHOW_TOOLBAR, C|'\t', KC, mView }, { 1, N_("Show Property Bro&wser"), Command::SHOW_TEXT_WND, '\t', KC, mView }, { 1, NULL, Command::NONE, 0, KN, NULL }, { 1, N_("&Full Screen"), Command::FULL_SCREEN, C|F|11, KC, mView }, @@ -124,7 +128,7 @@ const MenuEntry Menu[] = { { 1, N_("Anywhere In &3d"), Command::FREE_IN_3D, '3', KR, mReq }, { 1, NULL, Command::NONE, 0, KN, NULL }, { 1, N_("Datum &Point"), Command::DATUM_POINT, 'p', KN, mReq }, -{ 1, N_("&Workplane"), Command::WORKPLANE, 0, KN, mReq }, +{ 1, N_("Wor&kplane"), Command::WORKPLANE, 0, KN, mReq }, { 1, NULL, Command::NONE, 0, KN, NULL }, { 1, N_("Line &Segment"), Command::LINE_SEGMENT, 's', KN, mReq }, { 1, N_("C&onstruction Line Segment"), Command::CONSTR_SEGMENT, S|'s', KN, mReq }, @@ -134,16 +138,16 @@ const MenuEntry Menu[] = { { 1, N_("&Bezier Cubic Spline"), Command::CUBIC, 'b', KN, mReq }, { 1, NULL, Command::NONE, 0, KN, NULL }, { 1, N_("&Text in TrueType Font"), Command::TTF_TEXT, 't', KN, mReq }, -{ 1, N_("&Image"), Command::IMAGE, 0, KN, mReq }, +{ 1, N_("I&mage"), Command::IMAGE, 0, KN, mReq }, { 1, NULL, Command::NONE, 0, KN, NULL }, { 1, N_("To&ggle Construction"), Command::CONSTRUCTION, 'g', KN, mReq }, -{ 1, N_("Tangent &Arc at Point"), Command::TANGENT_ARC, S|'a', KN, mReq }, +{ 1, N_("Ta&ngent Arc at Point"), Command::TANGENT_ARC, S|'a', KN, mReq }, { 1, N_("Split Curves at &Intersection"), Command::SPLIT_CURVES, 'i', KN, mReq }, { 0, N_("&Constrain"), Command::NONE, 0, KN, mCon }, { 1, N_("&Distance / Diameter"), Command::DISTANCE_DIA, 'd', KN, mCon }, { 1, N_("Re&ference Dimension"), Command::REF_DISTANCE, S|'d', KN, mCon }, -{ 1, N_("A&ngle"), Command::ANGLE, 'n', KN, mCon }, +{ 1, N_("A&ngle / Equal Angle"), Command::ANGLE, 'n', KN, mCon }, { 1, N_("Reference An&gle"), Command::REF_ANGLE, S|'n', KN, mCon }, { 1, N_("Other S&upplementary Angle"), Command::OTHER_ANGLE, 'u', KN, mCon }, { 1, N_("Toggle R&eference Dim"), Command::REFERENCE, 'e', KN, mCon }, @@ -152,9 +156,9 @@ const MenuEntry Menu[] = { { 1, N_("&Vertical"), Command::VERTICAL, 'v', KN, mCon }, { 1, NULL, Command::NONE, 0, KN, NULL }, { 1, N_("&On Point / Curve / Plane"), Command::ON_ENTITY, 'o', KN, mCon }, -{ 1, N_("E&qual Length / Radius / Angle"), Command::EQUAL, 'q', KN, mCon }, -{ 1, N_("Length Ra&tio"), Command::RATIO, 'z', KN, mCon }, -{ 1, N_("Length Diff&erence"), Command::DIFFERENCE, 'j', KN, mCon }, +{ 1, N_("E&qual Length / Radius"), Command::EQUAL, 'q', KN, mCon }, +{ 1, N_("Length / Arc Ra&tio"), Command::RATIO, 'z', KN, mCon }, +{ 1, N_("Length / Arc Diff&erence"), Command::DIFFERENCE, 'j', KN, mCon }, { 1, N_("At &Midpoint"), Command::AT_MIDPOINT, 'm', KN, mCon }, { 1, N_("S&ymmetric"), Command::SYMMETRIC, 'y', KN, mCon }, { 1, N_("Para&llel / Tangent"), Command::PARALLEL, 'l', KN, mCon }, @@ -181,6 +185,7 @@ const MenuEntry Menu[] = { { 0, N_("&Help"), Command::NONE, 0, KN, mHelp }, { 1, N_("&Language"), Command::LOCALE, 0, KN, mHelp }, { 1, N_("&Website / Manual"), Command::WEBSITE, 0, KN, mHelp }, +{ 1, N_("&Go to GitHub commit"), Command::GITHUB, 0, KN, mHelp }, #ifndef __APPLE__ { 1, N_("&About"), Command::ABOUT, 0, KN, mHelp }, #endif @@ -297,7 +302,7 @@ void GraphicsWindow::PopulateMainMenu() { SS.UpdateWindowTitles(); PopulateMainMenu(); - EnsureValidActives(); + SS.GW.EnsureValidActives(); }); } } else if(Menu[i].fn == NULL) { @@ -317,6 +322,8 @@ void GraphicsWindow::PopulateMainMenu() { dimSolidModelMenuItem = menuItem; } else if(Menu[i].cmd == Command::PERSPECTIVE_PROJ) { perspectiveProjMenuItem = menuItem; + } else if(Menu[i].cmd == Command::EXPLODE_SKETCH) { + explodeMenuItem = menuItem; } else if(Menu[i].cmd == Command::SHOW_TOOLBAR) { showToolbarMenuItem = menuItem; } else if(Menu[i].cmd == Command::SHOW_TEXT_WND) { @@ -329,6 +336,8 @@ void GraphicsWindow::PopulateMainMenu() { unitsMetersMenuItem = menuItem; } else if(Menu[i].cmd == Command::UNITS_INCHES) { unitsInchesMenuItem = menuItem; + } else if(Menu[i].cmd == Command::UNITS_FEET_INCHES) { + unitsFeetInchesMenuItem = menuItem; } else if(Menu[i].cmd == Command::SEL_WORKPLANE) { inWorkplaneMenuItem = menuItem; } else if(Menu[i].cmd == Command::FREE_IN_3D) { @@ -370,7 +379,7 @@ static void PopulateMenuWithPathnames(Platform::MenuRef menu, void GraphicsWindow::PopulateRecentFiles() { PopulateMenuWithPathnames(openRecentMenu, SS.recentFiles, [](const Platform::Path &path) { // OkayToStartNewFile could mutate recentFiles, which will invalidate path (which is a - // refererence into the recentFiles vector), so take a copy of it here. + // reference into the recentFiles vector), so take a copy of it here. Platform::Path pathCopy(path); if(!SS.OkayToStartNewFile()) return; SS.Load(pathCopy); @@ -402,11 +411,13 @@ void GraphicsWindow::Init() { showNormals = true; showPoints = true; showConstruction = true; - showConstraints = true; + showConstraints = ShowConstraintMode::SCM_SHOW_ALL; showShaded = true; showEdges = true; showMesh = false; showOutlines = false; + showFacesDrawing = false; + showFacesNonDrawing = true; drawOccludedAs = DrawOccludedAs::INVISIBLE; showTextWindow = true; @@ -422,8 +433,13 @@ void GraphicsWindow::Init() { using namespace std::placeholders; // Do this first, so that if it causes an onRender event we don't try to paint without // a canvas. - window->SetMinContentSize(720, 670); + window->SetMinContentSize(720, /*ToolbarDrawOrHitTest 636*/ 32 * 18 + 3 * 16 + 8 + 4); window->onClose = std::bind(&SolveSpaceUI::MenuFile, Command::EXIT); + window->onContextLost = [&] { + canvas = NULL; + persistentCanvas = NULL; + persistentDirty = true; + }; window->onRender = std::bind(&GraphicsWindow::Paint, this); window->onKeyboardEvent = std::bind(&GraphicsWindow::KeyboardEvent, this, _1); window->onMouseEvent = std::bind(&GraphicsWindow::MouseEvent, this, _1); @@ -489,12 +505,18 @@ void GraphicsWindow::AnimateOnto(Quaternion quatf, Vector offsetf) { // Animate transition, unless it's a tiny move. int64_t t0 = GetMilliseconds(); - int32_t dt = (mp < 0.01 && mo < 10) ? (-20) : - (int32_t)(100 + 1000*mp + 0.4*mo); - // Don't ever animate for longer than 2000 ms; we can get absurdly + int32_t dt = (mp < 0.01 && mo < 10) ? 0 : + (int32_t)(SS.animationSpeed*0.75*mp + SS.animationSpeed*0.0005*mo); + // Apply a minimum animation time, for small moves. This gets overridden by the maximum setting + // so setting the animation speed to 0 disables animations entirely. + dt = std::max(dt, 100 /* ms */); + // Don't ever animate for longer than animationSpeed ms; we can get absurdly // long translations (as measured in pixels) if the user zooms out, moves, // and then zooms in again. - if(dt > 2000) dt = 2000; + dt = std::min(dt, SS.animationSpeed); + // If the resulting animation time is very short, disable it completely. + if (dt < 10) dt = -20; + Quaternion dq = quatf.Times(quat0.Inverse()); if(!animateTimer) { @@ -578,7 +600,7 @@ void GraphicsWindow::LoopOverPoints(const std::vector &entities, for(Constraint *c : constraints) { std::vector refs; c->GetReferencePoints(camera, &refs); - for(Vector p : refs) { + for(const Vector &p : refs) { HandlePointForZoomToFit(p, pmax, pmin, wmin, usePerspective, camera); } } @@ -703,16 +725,47 @@ double GraphicsWindow::ZoomToFit(const Camera &camera, 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) { switch(id) { case Command::ZOOM_IN: - SS.GW.scale *= 1.2; - SS.ScheduleShowTW(); + SS.GW.ZoomToMouse(1); break; case Command::ZOOM_OUT: - SS.GW.scale /= 1.2; - SS.ScheduleShowTW(); + SS.GW.ZoomToMouse(-1); break; case Command::ZOOM_TO_FIT: @@ -748,6 +801,12 @@ void GraphicsWindow::MenuView(Command id) { } break; + case Command::EXPLODE_SKETCH: + SS.explode = !SS.explode; + SS.GW.EnsureValidActives(); + SS.MarkGroupDirty(SS.GW.activeGroup, true); + break; + case Command::ONTO_WORKPLANE: if(SS.GW.LockedInWorkplane()) { SS.GW.AnimateOntoWorkplane(); @@ -766,13 +825,18 @@ void GraphicsWindow::MenuView(Command id) { Quaternion quatf = quat0; double dmin = 1e10; - // There are 24 possible views; 3*2*2*2 - int i, j, negi, negj; - for(i = 0; i < 3; i++) { - for(j = 0; j < 3; j++) { + // There are 24 possible views (3*2*2*2), if all are + // allowed. If the user is in turn-table mode, the + // isometric view must have the z-axis facing up, leaving + // 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; - for(negi = 0; negi < 2; negi++) { - for(negj = 0; negj < 2; negj++) { + if(require_turntable && (j!=2)) continue; + for(int negi = 0; negi < 2; negi++) { + for(int negj = 0; negj < 2; negj++) { Vector ou = ortho[i], ov = ortho[j]; if(negi) ou = ou.ScaledBy(-1); if(negj) ov = ov.ScaledBy(-1); @@ -841,6 +905,12 @@ void GraphicsWindow::MenuView(Command id) { SS.GW.EnsureValidActives(); break; + case Command::UNITS_FEET_INCHES: + SS.viewUnits = Unit::FEET_INCHES; + SS.ScheduleShowTW(); + SS.GW.EnsureValidActives(); + break; + case Command::UNITS_MM: SS.viewUnits = Unit::MM; SS.ScheduleShowTW(); @@ -923,6 +993,7 @@ void GraphicsWindow::EnsureValidActives() { case Unit::MM: case Unit::METERS: case Unit::INCHES: + case Unit::FEET_INCHES: break; default: SS.viewUnits = Unit::MM; @@ -931,6 +1002,7 @@ void GraphicsWindow::EnsureValidActives() { unitsMmMenuItem->SetActive(SS.viewUnits == Unit::MM); unitsMetersMenuItem->SetActive(SS.viewUnits == Unit::METERS); unitsInchesMenuItem->SetActive(SS.viewUnits == Unit::INCHES); + unitsFeetInchesMenuItem->SetActive(SS.viewUnits == Unit::FEET_INCHES); if(SS.TW.window) SS.TW.window->SetVisible(SS.GW.showTextWindow); showTextWndMenuItem->SetActive(SS.GW.showTextWindow); @@ -938,6 +1010,7 @@ void GraphicsWindow::EnsureValidActives() { showGridMenuItem->SetActive(SS.GW.showSnapGrid); dimSolidModelMenuItem->SetActive(SS.GW.dimSolidModel); perspectiveProjMenuItem->SetActive(SS.usePerspectiveProj); + explodeMenuItem->SetActive(SS.explode); showToolbarMenuItem->SetActive(SS.showToolbar); fullScreenMenuItem->SetActive(SS.GW.window->IsFullScreen()); @@ -968,20 +1041,11 @@ void GraphicsWindow::ForceTextWindowShown() { } void GraphicsWindow::DeleteTaggedRequests() { - Request *r; - // Delete any requests that were affected by this deletion. - for(r = SK.request.First(); r; r = SK.request.NextAfter(r)) { - if(r->workplane == Entity::FREE_IN_3D) continue; - if(!r->workplane.isFromRequest()) continue; - Request *wrkpl = SK.GetRequest(r->workplane.request()); - if(wrkpl->tag) - r->tag = 1; - } // Rewrite any point-coincident constraints that were affected by this // deletion. - for(r = SK.request.First(); r; r = SK.request.NextAfter(r)) { - if(!r->tag) continue; - FixConstraintsForRequestBeingDeleted(r->h); + for(Request &r : SK.request) { + if(!r.tag) continue; + FixConstraintsForRequestBeingDeleted(r.h); } // and then delete the tagged requests. SK.request.RemoveTagged(); @@ -1038,6 +1102,15 @@ void GraphicsWindow::MenuEdit(Command id) { } } } + // some pending operations need an Undo to properly clean up on ESC + if ( (SS.GW.pending.operation == Pending::DRAGGING_NEW_POINT) + || (SS.GW.pending.operation == Pending::DRAGGING_NEW_LINE_POINT) + || (SS.GW.pending.operation == Pending::DRAGGING_NEW_ARC_POINT) + || (SS.GW.pending.operation == Pending::DRAGGING_NEW_RADIUS) ) + { + SS.GW.ClearSuper(); + SS.UndoUndo(); + } SS.GW.ClearSuper(); SS.TW.HideEditControl(); SS.nakedEdges.Clear(); @@ -1045,9 +1118,8 @@ void GraphicsWindow::MenuEdit(Command id) { SS.centerOfMass.draw = false; // This clears the marks drawn to indicate which points are // still free to drag. - Param *p; - for(p = SK.param.First(); p; p = SK.param.NextAfter(p)) { - p->free = false; + for(Param &p : SK.param) { + p.free = false; } if(SS.exportMode) { SS.exportMode = false; @@ -1057,13 +1129,12 @@ void GraphicsWindow::MenuEdit(Command id) { break; case Command::SELECT_ALL: { - Entity *e; - for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - if(e->group != SS.GW.activeGroup) continue; - if(e->IsFace() || e->IsDistance()) continue; - if(!e->IsVisible()) continue; + for(Entity &e : SK.entity) { + if(e.group != SS.GW.activeGroup) continue; + if(e.IsFace() || e.IsDistance()) continue; + if(!e.IsVisible()) continue; - SS.GW.MakeSelected(e->h); + SS.GW.MakeSelected(e.h); } SS.GW.Invalidate(); SS.ScheduleShowTW(); @@ -1071,24 +1142,23 @@ void GraphicsWindow::MenuEdit(Command id) { } case Command::SELECT_CHAIN: { - Entity *e; int newlySelected = 0; bool didSomething; do { didSomething = false; - for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - if(e->group != SS.GW.activeGroup) continue; - if(!e->HasEndpoints()) continue; - if(!e->IsVisible()) continue; + for(Entity &e : SK.entity) { + if(e.group != SS.GW.activeGroup) continue; + if(!e.HasEndpoints()) continue; + if(!e.IsVisible()) continue; - Vector st = e->EndpointStart(), - fi = e->EndpointFinish(); + Vector st = e.EndpointStart(), + fi = e.EndpointFinish(); bool onChain = false, alreadySelected = false; List *ls = &(SS.GW.selection); for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { if(!s->entity.v) continue; - if(s->entity == e->h) { + if(s->entity == e.h) { alreadySelected = true; continue; } @@ -1105,7 +1175,7 @@ void GraphicsWindow::MenuEdit(Command id) { } } if(onChain && !alreadySelected) { - SS.GW.MakeSelected(e->h); + SS.GW.MakeSelected(e.h); newlySelected++; didSomething = true; } @@ -1370,6 +1440,14 @@ void GraphicsWindow::ToggleBool(bool *v) { SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE); } + if(v == &showFaces) { + if(g->type == Group::Type::DRAWING_WORKPLANE || g->type == Group::Type::DRAWING_3D) { + showFacesDrawing = showFaces; + } else { + showFacesNonDrawing = showFaces; + } + } + Invalidate(/*clearPersistent=*/true); SS.ScheduleShowTW(); } @@ -1399,3 +1477,5 @@ bool GraphicsWindow::SuggestLineConstraint(hRequest request, Constraint::Type *t } return false; } + +} // namespace SolveSpace diff --git a/src/group.cpp b/src/group.cpp index 1539e68c7..4e41ffc30 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -8,6 +8,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + const hParam Param::NO_PARAM = { 0 }; #define NO_PARAM (Param::NO_PARAM) @@ -34,7 +36,7 @@ void Group::Clear() { remap.clear(); } -void Group::AddParam(IdList *param, hParam hp, double v) { +void Group::AddParam(ParamList *param, hParam hp, double v) { Param pa = {}; pa.h = hp; pa.val = v; @@ -136,14 +138,30 @@ void Group::MenuGroup(Command id, Platform::Path linkFile) { g.predef.negateV = wrkplg->predef.negateV; } else if(wrkplg->subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) { g.predef.q = wrkplg->predef.q; + } else if(wrkplg->subtype == Subtype::WORKPLANE_BY_POINT_NORMAL) { + g.predef.q = wrkplg->predef.q; + g.predef.entityB = wrkplg->predef.entityB; } else ssassert(false, "Unexpected workplane subtype"); } + } else if(gs.anyNormals == 1 && gs.points == 1 && gs.n == 2) { + g.subtype = Subtype::WORKPLANE_BY_POINT_NORMAL; + g.predef.entityB = gs.anyNormal[0]; + g.predef.q = SK.GetEntity(gs.anyNormal[0])->NormalGetNum(); + g.predef.origin = gs.point[0]; + //} else if(gs.faces == 1 && gs.points == 1 && gs.n == 2) { + // g.subtype = Subtype::WORKPLANE_BY_POINT_FACE; + // g.predef.q = SK.GetEntity(gs.face[0])->NormalGetNum(); + // g.predef.origin = gs.point[0]; } else { Error(_("Bad selection for new sketch in workplane. This " "group can be created with:\n\n" " * a point (through the point, orthogonal to coordinate axes)\n" " * a point and two line segments (through the point, " - "parallel to the lines)\n" + "parallel to the lines)\n" + " * a point and a normal (through the point, " + "orthogonal to the normal)\n" + /*" * a point and a face (through the point, " + "parallel to the face)\n"*/ " * a workplane (copy of the workplane)\n")); return; } @@ -392,7 +410,13 @@ bool Group::IsForcedToMeshBySource() const { } bool Group::IsForcedToMesh() const { - return forceToMesh || IsForcedToMeshBySource(); + return forceToMesh || IsTriangleMeshAssembly() || IsForcedToMeshBySource(); +} + +bool Group::IsTriangleMeshAssembly() const { + if (type != Type::LINKED) return false; + if (!impMesh.IsEmpty() && impShell.IsEmpty()) return true; + return false; } std::string Group::DescriptionString() { @@ -404,18 +428,16 @@ std::string Group::DescriptionString() { } void Group::Activate() { - if(type == Type::EXTRUDE || type == Type::LINKED || type == Type::LATHE || - type == Type::REVOLVE || type == Type::HELIX || type == Type::TRANSLATE || type == Type::ROTATE) { - SS.GW.showFaces = true; + if(type == Type::DRAWING_WORKPLANE || type == Type::DRAWING_3D) { + SS.GW.showFaces = SS.GW.showFacesDrawing; } else { - SS.GW.showFaces = false; + SS.GW.showFaces = SS.GW.showFacesNonDrawing; } SS.MarkGroupDirty(h); // for good measure; shouldn't be needed SS.ScheduleShowTW(); } -void Group::Generate(IdList *entity, - IdList *param) +void Group::Generate(EntityList *entity, ParamList *param) { Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); Vector gp = SS.GW.projRight.Plus(SS.GW.projUp); @@ -443,11 +465,14 @@ void Group::Generate(IdList *entity, } else if(subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) { // Already given, numerically. q = predef.q; + } else if(subtype == Subtype::WORKPLANE_BY_POINT_NORMAL) { + q = SK.GetEntity(predef.entityB)->NormalGetNum(); } else ssassert(false, "Unexpected workplane subtype"); Entity normal = {}; normal.type = Entity::Type::NORMAL_N_COPY; normal.numNormal = q; + normal.point[0] = h.entity(2); normal.group = h; normal.h = h.entity(1); @@ -476,9 +501,9 @@ void Group::Generate(IdList *entity, AddParam(param, h.param(1), gn.y); AddParam(param, h.param(2), gn.z); int ai, af; - if(subtype == Subtype::ONE_SIDED) { + if((subtype == Subtype::ONE_SIDED) || (subtype == Subtype::ONE_SKEWED)) { ai = 0; af = 2; - } else if(subtype == Subtype::TWO_SIDED) { + } else if((subtype == Subtype::TWO_SIDED) || (subtype == Subtype::TWO_SKEWED)) { ai = -1; af = 1; } else ssassert(false, "Unexpected extrusion subtype"); @@ -800,10 +825,17 @@ void Group::GenerateEquations(IdList *l) { AddEq(l, (EC(axis.z))->Minus(EP(6)), 5); #undef EC #undef EP - } else if(type == Type::EXTRUDE) { + if(type == Type::HELIX) { + if(valB != 0.0) { + AddEq(l, Expr::From(h.param(7))->Times(Expr::From(PI))-> + Minus(Expr::From(h.param(3))->Times(Expr::From(valB))), 6); + } + } + } else if((type == Type::EXTRUDE) && (subtype != Subtype::ONE_SKEWED) && + (subtype != Subtype::TWO_SKEWED)) { if(predef.entityB != Entity::FREE_IN_3D) { // The extrusion path is locked along a line, normal to the - // specified workplane. + // specified workplane. Don't constrain for skewed extrusions. Entity *w = SK.GetEntity(predef.entityB); ExprVector u = w->Normal()->NormalExprsU(); ExprVector v = w->Normal()->NormalExprsV(); @@ -831,13 +863,18 @@ void Group::GenerateEquations(IdList *l) { hEntity Group::Remap(hEntity in, int copyNumber) { auto it = remap.find({ in, copyNumber }); if(it == remap.end()) { + // Due to the way the remap value is combined with the group handle into a 32-bit + // handle value to generate an entity handle, the mapped value must fit in a 16-bit + // variable. + // This limit can be lifted once the handle values are extended to 64-bit. + ssassert(remap.size() < (1 << 16) - 1, "Too many entities in group"); std::tie(it, std::ignore) = remap.insert({ { in, copyNumber }, { (uint32_t)remap.size() + 1 } }); } return h.entity(it->second.v); } -void Group::MakeExtrusionLines(IdList *el, hEntity in) { +void Group::MakeExtrusionLines(EntityList *el, hEntity in) { Entity *ep = SK.GetEntity(in); Entity en = {}; @@ -873,7 +910,7 @@ void Group::MakeExtrusionLines(IdList *el, hEntity in) { } } -void Group::MakeLatheCircles(IdList *el, IdList *param, hEntity in, Vector pt, Vector axis) { +void Group::MakeLatheCircles(EntityList *el, ParamList *param, hEntity in, Vector pt, Vector axis) { Entity *ep = SK.GetEntity(in); Entity en = {}; @@ -920,7 +957,7 @@ void Group::MakeLatheCircles(IdList *el, IdList *p } } -void Group::MakeLatheSurfacesSelectable(IdList *el, hEntity in, Vector axis) { +void Group::MakeLatheSurfacesSelectable(EntityList *el, hEntity in, Vector axis) { Entity *ep = SK.GetEntity(in); Entity en = {}; @@ -955,7 +992,7 @@ void Group::MakeLatheSurfacesSelectable(IdList *el, hEntity in, // For Revolve and Helix groups the end faces are remapped from an arbitrary // point on the sketch. We reference the transformed point but there is // no existing normal so we need to define the rotation and timesApplied. -void Group::MakeRevolveEndFaces(IdList *el, hEntity pt, int ai, int af) +void Group::MakeRevolveEndFaces(EntityList *el, hEntity pt, int ai, int af) { if(pt.v == 0) return; Group *src = SK.GetGroup(opA); @@ -992,7 +1029,7 @@ void Group::MakeRevolveEndFaces(IdList *el, hEntity pt, int ai, el->Add(&en); } -void Group::MakeExtrusionTopBottomFaces(IdList *el, hEntity pt) +void Group::MakeExtrusionTopBottomFaces(EntityList *el, hEntity pt) { if(pt.v == 0) return; Group *src = SK.GetGroup(opA); @@ -1018,7 +1055,7 @@ void Group::MakeExtrusionTopBottomFaces(IdList *el, hEntity pt) el->Add(&en); } -void Group::CopyEntity(IdList *el, +void Group::CopyEntity(EntityList *el, Entity *ep, int timesApplied, int remap, hParam dx, hParam dy, hParam dz, hParam qw, hParam qvx, hParam qvy, hParam qvz, hParam dist, @@ -1150,10 +1187,15 @@ void Group::CopyEntity(IdList *el, break; default: { + if((Entity::Type::IMAGE == ep->type) && (true == ep->construction)) { + // Do not copy image entities if they are construction. + return; + } + int i, points; bool hasNormal, hasDistance; - EntReqTable::GetEntityInfo(ep->type, ep->extraPoints, - NULL, &points, &hasNormal, &hasDistance); + ssassert(EntReqTable::GetEntityInfo(ep->type, ep->extraPoints, + NULL, &points, &hasNormal, &hasDistance), "No entity info"); for(i = 0; i < points; i++) { en.point[i] = Remap(ep->point[i], remap); } @@ -1172,3 +1214,8 @@ void Group::CopyEntity(IdList *el, el->Add(&en); } +bool Group::ShouldDrawExploded() const { + return SS.explode && h == SS.GW.activeGroup && type == Type::DRAWING_WORKPLANE && !SS.exportMode; +} + +} // namespace SolveSpace diff --git a/src/groupmesh.cpp b/src/groupmesh.cpp index f1041d632..785817b3f 100644 --- a/src/groupmesh.cpp +++ b/src/groupmesh.cpp @@ -7,6 +7,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + void Group::AssembleLoops(bool *allClosed, bool *allCoplanar, bool *allNonZeroLen) @@ -83,13 +85,12 @@ void Group::GenerateLoops() { } void SShell::RemapFaces(Group *g, int remap) { - SSurface *ss; - for(ss = surface.First(); ss; ss = surface.NextAfter(ss)){ - hEntity face = { ss->face }; + for(SSurface &ss : surface){ + hEntity face = { ss.face }; if(face == Entity::NO_ENTITY) continue; face = g->Remap(face, remap); - ss->face = face.v; + ss.face = face.v; } } @@ -249,7 +250,7 @@ void Group::GenerateShellAndMesh() { Vector translate = Vector::From(h.param(0), h.param(1), h.param(2)); Vector tbot, ttop; - if(subtype == Subtype::ONE_SIDED) { + if(subtype == Subtype::ONE_SIDED || subtype == Subtype::ONE_SKEWED) { tbot = Vector::From(0, 0, 0); ttop = translate.ScaledBy(2); } else { tbot = translate.ScaledBy(-1); ttop = translate.ScaledBy(1); @@ -292,13 +293,12 @@ void Group::GenerateShellAndMesh() { // So these are the sides if(ss->degm != 1 || ss->degn != 1) continue; - Entity *e; - for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - if(e->group != opA) continue; - if(e->type != Entity::Type::LINE_SEGMENT) continue; + for(Entity &e : SK.entity) { + if(e.group != opA) continue; + if(e.type != Entity::Type::LINE_SEGMENT) continue; - Vector a = SK.GetEntity(e->point[0])->PointGetNum(), - b = SK.GetEntity(e->point[1])->PointGetNum(); + Vector a = SK.GetEntity(e.point[0])->PointGetNum(), + b = SK.GetEntity(e.point[1])->PointGetNum(); a = a.Plus(ttop); b = b.Plus(ttop); // Could get taken backwards, so check all cases. @@ -307,7 +307,7 @@ void Group::GenerateShellAndMesh() { (a.Equals(ss->ctrl[0][1]) && b.Equals(ss->ctrl[1][1])) || (b.Equals(ss->ctrl[0][1]) && a.Equals(ss->ctrl[1][1]))) { - face = Remap(e->h, REMAP_LINE_TO_FACE); + face = Remap(e.h, REMAP_LINE_TO_FACE); ss->face = face.v; break; } @@ -637,8 +637,11 @@ void Group::DrawMesh(DrawMeshAs how, Canvas *canvas) { std::vector faces; SS.GW.GroupSelection(); auto const &gs = SS.GW.gs; - if(gs.faces > 0) faces.push_back(gs.face[0].v); - if(gs.faces > 1) faces.push_back(gs.face[1].v); + // See also GraphicsWindow::MakeSelected "if(c >= MAX_SELECTABLE_FACES)" + // and GraphicsWindow::GroupSelection "if(e->IsFace())" + for(auto &fc : gs.face) { + faces.push_back(fc.v); + } canvas->DrawFaces(displayMesh, faces, hcf); break; } @@ -797,3 +800,4 @@ void Group::DrawContourAreaLabels(Canvas *canvas) { } } +} // namespace SolveSpace diff --git a/src/handle.h b/src/handle.h new file mode 100644 index 000000000..e247e6083 --- /dev/null +++ b/src/handle.h @@ -0,0 +1,47 @@ +#ifndef SOVLESPACE_HANDLE_H +#define SOVLESPACE_HANDLE_H + +#include +#include + +namespace SolveSpace { + +/// Trait indicating which types are handle types and should get the associated operators. +/// Specialize for each handle type and inherit from std::true_type. +template +struct IsHandleOracle : std::false_type {}; + +// Equality-compare any two instances of a handle type. +template +static inline typename std::enable_if::value, bool>::type +operator==(T const &lhs, T const &rhs) { + return lhs.v == rhs.v; +} + +// Inequality-compare any two instances of a handle type. +template +static inline typename std::enable_if::value, bool>::type +operator!=(T const &lhs, T const &rhs) { + return !(lhs == rhs); +} + +// Less-than-compare any two instances of a handle type. +template +static inline typename std::enable_if::value, bool>::type +operator<(T const &lhs, T const &rhs) { + return lhs.v < rhs.v; +} + +template +struct HandleHasher { + static_assert(IsHandleOracle::value, "Not a valid handle type"); + + inline size_t operator()(const T &h) const { + using Hasher = std::hash; + return Hasher{}(h.v); + } +}; + +} // namespace SolveSpace + +#endif // !SOVLESPACE_HANDLE_H diff --git a/src/importidf.cpp b/src/importidf.cpp index 1789e5cc1..5523cab38 100644 --- a/src/importidf.cpp +++ b/src/importidf.cpp @@ -8,6 +8,8 @@ #include "solvespace.h" #include "sketch.h" +namespace SolveSpace { + // Split a string into substrings separated by spaces. // Allow quotes to enclose spaces within a string static std::vector splitString(const std::string line) { @@ -53,8 +55,22 @@ static std::vector splitString(const std::string line) { return v; } +static bool isHoleDuplicate(EntityList *el, double x, double y, double r) { + bool duplicate = false; + for(int i = 0; i < el->n && !duplicate; i++) { + Entity &en = el->Get(i); + if(en.type != Entity::Type::CIRCLE) + continue; + Entity *distance = el->FindById(en.distance); + Entity *center = el->FindById(en.point[0]); + duplicate = + center->actPoint.x == x && center->actPoint.y == y && distance->actDistance == r; + } + return duplicate; +} + ////////////////////////////////////////////////////////////////////////////// -// Functions for linking an IDF file - we need to create entites that +// Functions for linking an IDF file - we need to create entities that // get remapped into a linked group similar to linking .slvs files ////////////////////////////////////////////////////////////////////////////// @@ -77,7 +93,7 @@ static hEntity newPoint(EntityList *el, int *id, Vector p, bool visible = true) return en.h; } -static hEntity newLine(EntityList *el, int *id, hEntity p0, hEntity p1) { +static hEntity newLine(EntityList *el, int *id, hEntity p0, hEntity p1, bool keepout) { Entity en = {}; en.type = Entity::Type::LINE_SEGMENT; en.point[0] = p0; @@ -85,8 +101,8 @@ static hEntity newLine(EntityList *el, int *id, hEntity p0, hEntity p1) { en.extraPoints = 0; en.timesApplied = 0; en.group.v = 493; - en.construction = false; - en.style.v = Style::ACTIVE_GRP; + en.construction = keepout; + en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP; en.actVisible = true; en.forceHidden = false; @@ -117,7 +133,7 @@ static hEntity newNormal(EntityList *el, int *id, Quaternion normal) { return en.h; } -static hEntity newArc(EntityList *el, int *id, hEntity p0, hEntity p1, hEntity pc, hEntity hnorm) { +static hEntity newArc(EntityList *el, int *id, hEntity p0, hEntity p1, hEntity pc, hEntity hnorm, bool keepout) { Entity en = {}; en.type = Entity::Type::ARC_OF_CIRCLE; en.point[0] = pc; @@ -127,8 +143,8 @@ static hEntity newArc(EntityList *el, int *id, hEntity p0, hEntity p1, hEntity p en.extraPoints = 0; en.timesApplied = 0; en.group.v = 403; - en.construction = false; - en.style.v = Style::ACTIVE_GRP; + en.construction = keepout; + en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP; en.actVisible = true; en.forceHidden = false; *id = *id+1; @@ -158,7 +174,7 @@ static hEntity newDistance(EntityList *el, int *id, double distance) { return en.h; } -static hEntity newCircle(EntityList *el, int *id, hEntity p0, hEntity hdist, hEntity hnorm) { +static hEntity newCircle(EntityList *el, int *id, hEntity p0, hEntity hdist, hEntity hnorm, bool keepout) { Entity en = {}; en.type = Entity::Type::CIRCLE; en.point[0] = p0; @@ -167,8 +183,8 @@ static hEntity newCircle(EntityList *el, int *id, hEntity p0, hEntity hdist, hEn en.extraPoints = 0; en.timesApplied = 0; en.group.v = 399; - en.construction = false; - en.style.v = Style::ACTIVE_GRP; + en.construction = keepout; + en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP; en.actVisible = true; en.forceHidden = false; @@ -196,37 +212,35 @@ static Vector ArcCenter(Vector p0, Vector p1, double angle) { // Positive angles are counter clockwise, negative are clockwise. An angle of 360 // indicates a circle centered at x1,y1 passing through x2,y2 and is a complete loop. static void CreateEntity(EntityList *el, int *id, hEntity h0, hEntity h1, hEntity hnorm, - Vector p0, Vector p1, double angle) { - if (angle == 0.0) { + Vector p0, Vector p1, double angle, bool keepout) { + if (fabs(angle) < 0.1) { //line if(p0.Equals(p1)) return; - newLine(el, id, h0, h1); + newLine(el, id, h0, h1, keepout); } else if(angle == 360.0) { // circle double d = p1.Minus(p0).Magnitude(); hEntity hd = newDistance(el, id, d); - newCircle(el, id, h1, hd, hnorm); + newCircle(el, id, h1, hd, hnorm, keepout); } else { // arc if(angle < 0.0) { swap(p0,p1); swap(h0,h1); + angle = fabs(angle); } // locate the center of the arc Vector m = p0.Plus(p1).ScaledBy(0.5); Vector perp = Vector::From(p1.y-p0.y, p0.x-p1.x, 0.0).WithMagnitude(1.0); - double dist = 0; - if (angle != 180) { - dist = (p1.Minus(m).Magnitude())/tan(0.5*angle*3.141592653589793/180.0); - } else { - dist = 0.0; - } + // half angle in radians + double theta = 0.5*angle*PI/180.0; + double dist = (p1.Minus(m).Magnitude())*cos(theta)/sin(theta); Vector c = m.Minus(perp.ScaledBy(dist)); hEntity hc = newPoint(el, id, c, /*visible=*/false); - newArc(el, id, h0, h1, hc, hnorm); + newArc(el, id, h0, h1, hc, hnorm, keepout); } } @@ -288,12 +302,10 @@ static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vecto } } -namespace SolveSpace { - // Here we read the important section of an IDF file. SolveSpace Entities are directly created by -// the funcions above, which is only OK because of the way linking works. For example points do +// the functions above, which is only OK because of the way linking works. For example points do // not have handles for solver parameters (coordinates), they only have their actPoint values -// set (or actNormal or actDistance). These are incompete entites and would be a problem if +// set (or actNormal or actDistance). These are incomplete entities and would be a problem if // they were part of the sketch, but they are not. After making a list of them here, a new group // gets created from copies of these. Those copies are complete and part of the sketch group. bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *sh) { @@ -332,8 +344,9 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s double board_thickness = 10.0; double scale = 1.0; //mm - bool topEntities, bottomEntities; - + bool topEntities = false; + bool bottomEntities = false; + Quaternion normal = Quaternion::From(Vector::From(1,0,0), Vector::From(0,1,0)); hEntity hnorm = newNormal(el, &entityCount, normal); @@ -355,10 +368,9 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s } else if (line.find(".BOARD_OUTLINE") == 0) { section = board_outline; record_number = 1; -// no keepouts for now - they should also be shown as construction? -// } else if (line.find(".ROUTE_KEEPOUT") == 0) { -// section = routing_keepout; -// record_number = 1; + } else if (line.find(".ROUTE_KEEPOUT") == 0) { + section = routing_keepout; + record_number = 1; } else if(line.find(".DRILLED_HOLES") == 0) { section = drilled_holes; record_number = 1; @@ -399,8 +411,8 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s std::vector values = splitString(line); if(values.size() != 4) continue; int c = stoi(values[0]); - double x = stof(values[1]); - double y = stof(values[2]); + double x = stof(values[1]) * scale; + double y = stof(values[2]) * scale; double ang = stof(values[3]); Vector point = Vector::From(x,y,0.0); Vector pTop = Vector::From(x,y,board_thickness); @@ -433,13 +445,15 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s bool vis = (ang == 360.0); if (bottomEntities) { hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis); - CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang); + CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang, + (section == routing_keepout) ); pprev = point; hprev = hp; } if (topEntities) { hEntity hp = newPoint(el, &entityCount, pTop, /*visible=*/vis); - CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop, ang); + CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop, + ang, (section == routing_keepout) ); pprevTop = pTop; hprevTop = hp; } @@ -457,22 +471,23 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s case drilled_holes: { std::vector values = splitString(line); if(values.size() < 6) continue; - double d = stof(values[0]); - double x = stof(values[1]); - double y = stof(values[2]); + double d = stof(values[0]) * scale; + double x = stof(values[1]) * scale; + double y = stof(values[2]) * scale; + bool duplicate = isHoleDuplicate(el, x, y, d / 2); // Only show holes likely to be useful in MCAD to reduce complexity. - if((d > 1.7) || (values[5].compare(0,3,"PIN") == 0) - || (values[5].compare(0,3,"MTG") == 0)) { + if(((d > 1.7) || (values[5].compare(0,3,"PIN") == 0) + || (values[5].compare(0,3,"MTG") == 0)) && !duplicate) { // create the entity Vector cent = Vector::From(x,y,0.0); hEntity hcent = newPoint(el, &entityCount, cent); hEntity hdist = newDistance(el, &entityCount, d/2); - newCircle(el, &entityCount, hcent, hdist, hnorm); + newCircle(el, &entityCount, hcent, hdist, hnorm, false); // and again for the top Vector cTop = Vector::From(x,y,board_thickness); hcent = newPoint(el, &entityCount, cTop); hdist = newDistance(el, &entityCount, d/2); - newCircle(el, &entityCount, hcent, hdist, hnorm); + newCircle(el, &entityCount, hcent, hdist, hnorm, false); // create the curves for the extrusion Vector pt = Vector::From(x+d/2, y, 0.0); MakeBeziersForArcs(&sbl, cent, pt, pt, normal, 360.0); @@ -518,4 +533,4 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s return true; } -} +} // namespace SolveSpace diff --git a/src/importmesh.cpp b/src/importmesh.cpp new file mode 100644 index 000000000..fc1a4ff81 --- /dev/null +++ b/src/importmesh.cpp @@ -0,0 +1,258 @@ +//----------------------------------------------------------------------------- +// Triangle mesh file reader. Reads an STL file triangle mesh and creates +// a SovleSpace SMesh from it. Supports only Linking, not import. +// +// Copyright 2020 Paul Kahler. +//----------------------------------------------------------------------------- +#include "solvespace.h" +#include "sketch.h" +#include +#include + +#define MIN_POINT_DISTANCE 0.001 + +namespace SolveSpace { + +// we will check for duplicate vertices and keep all their normals +class vertex { +public: + Vector p; + std::vector normal; +}; + +static bool isEdgeVertex(vertex &v) { + unsigned int i,j; + bool result = false; + for(i=0;i &lv, Vector &p, Vector &n) { + unsigned int i; + for(i=0; iAdd(&en); + return en.h; +} + +// check if a vertex is unique and add it via newPoint if it is. +static void addVertex(EntityList *el, Vector v) { + if(el->n < 15000) { + int id = el->n; + newPoint(el, &id, v); + } +} + +static hEntity newNormal(EntityList *el, int *id, Quaternion normal, hEntity p) { + // normals have parameters, but we don't need them to make a NORMAL_N_COPY from this + Entity en = {}; + en.type = Entity::Type::NORMAL_N_COPY; + en.extraPoints = 0; + en.timesApplied = 0; + en.group.v = 472; + en.actNormal = normal; + en.construction = false; + en.style.v = Style::NORMALS; + // to be visible we need to add a point. +// en.point[0] = newPoint(el, id, Vector::From(0,0,0)); + en.point[0] = p; + en.actVisible = true; + en.forceHidden = false; + + *id = *id+1; + en.h.v = *id + en.group.v*65536; + el->Add(&en); + return en.h; +} + +static hEntity newLine(EntityList *el, int *id, hEntity p0, hEntity p1) { + Entity en = {}; + en.type = Entity::Type::LINE_SEGMENT; + en.point[0] = p0; + en.point[1] = p1; + en.extraPoints = 0; + en.timesApplied = 0; + en.group.v = 493; + en.construction = true; + en.style.v = Style::CONSTRUCTION; + en.actVisible = true; + en.forceHidden = false; + + en.h.v = *id + en.group.v*65536; + *id = *id + 1; + el->Add(&en); + return en.h; +} + +bool LinkStl(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *sh) { + dbp("\nLink STL triangle mesh."); + el->Clear(); + std::string data; + if(!ReadFile(filename, &data)) { + Error("Couldn't read from '%s'", filename.raw.c_str()); + return false; + } + + std::stringstream f(data); + + char str[80] = {}; + f.read(str, 80); + + if(0==memcmp("solid", str, 5)) { + // just returning false will trigger the warning that linked file is not present + // best solution is to add an importer for text STL. + Message(_("Text-formated STL files are not currently supported")); + return false; + } + + uint32_t n; + uint32_t color; + + f.read((char*)&n, 4); + dbp("%d triangles", n); + + float x,y,z; + float xn,yn,zn; + + std::vector verts = {}; + + for(uint32_t i = 0; i> 7) & 0xf8; + tr.meta.color.green = (color >> 2) & 0xf8; + tr.meta.color.blue = (color << 3); + tr.meta.color.alpha = 255; + } else { + tr.meta.color.red = 90; + tr.meta.color.green = 120; + tr.meta.color.blue = 140; + tr.meta.color.alpha = 255; + } + + m->AddTriangle(&tr); + Vector normal = tr.Normal().WithMagnitude(1.0); + addUnique(verts, tr.a, normal); + addUnique(verts, tr.b, normal); + addUnique(verts, tr.c, normal); + } + dbp("%d vertices", verts.size()); + + int id = 1; + + //add the STL origin and normals + hEntity origin = newPoint(el, &id, Vector::From(0.0, 0.0, 0.0)); + newNormal(el, &id, Quaternion::From(Vector::From(1,0,0),Vector::From(0,1,0)), origin); + newNormal(el, &id, Quaternion::From(Vector::From(0,1,0),Vector::From(0,0,1)), origin); + newNormal(el, &id, Quaternion::From(Vector::From(0,0,1),Vector::From(1,0,0)), origin); + + BBox box = {}; + box.minp = verts[0].p; + box.maxp = verts[0].p; + + // determine the bounding box for all vertexes + for(unsigned int i=1; i - -Sketch SolveSpace::SK = {}; -static System SYS; - -void SolveSpace::Platform::FatalError(const std::string &message) { - fprintf(stderr, "%s", message.c_str()); - abort(); -} - -void Group::GenerateEquations(IdList *) { - // Nothing to do for now. -} - -extern "C" { - -void Slvs_QuaternionU(double qw, double qx, double qy, double qz, - double *x, double *y, double *z) -{ - Quaternion q = Quaternion::From(qw, qx, qy, qz); - Vector v = q.RotationU(); - *x = v.x; - *y = v.y; - *z = v.z; -} - -void Slvs_QuaternionV(double qw, double qx, double qy, double qz, - double *x, double *y, double *z) -{ - Quaternion q = Quaternion::From(qw, qx, qy, qz); - Vector v = q.RotationV(); - *x = v.x; - *y = v.y; - *z = v.z; -} - -void Slvs_QuaternionN(double qw, double qx, double qy, double qz, - double *x, double *y, double *z) -{ - Quaternion q = Quaternion::From(qw, qx, qy, qz); - Vector v = q.RotationN(); - *x = v.x; - *y = v.y; - *z = v.z; -} - -void Slvs_MakeQuaternion(double ux, double uy, double uz, - double vx, double vy, double vz, - double *qw, double *qx, double *qy, double *qz) -{ - Vector u = Vector::From(ux, uy, uz), - v = Vector::From(vx, vy, vz); - Quaternion q = Quaternion::From(u, v); - *qw = q.w; - *qx = q.vx; - *qy = q.vy; - *qz = q.vz; -} - -void Slvs_Solve(Slvs_System *ssys, Slvs_hGroup shg) -{ - int i; - for(i = 0; i < ssys->params; i++) { - Slvs_Param *sp = &(ssys->param[i]); - Param p = {}; - - p.h.v = sp->h; - p.val = sp->val; - SK.param.Add(&p); - if(sp->group == shg) { - SYS.param.Add(&p); - } - } - - for(i = 0; i < ssys->entities; i++) { - Slvs_Entity *se = &(ssys->entity[i]); - EntityBase e = {}; - - switch(se->type) { -case SLVS_E_POINT_IN_3D: e.type = Entity::Type::POINT_IN_3D; break; -case SLVS_E_POINT_IN_2D: e.type = Entity::Type::POINT_IN_2D; break; -case SLVS_E_NORMAL_IN_3D: e.type = Entity::Type::NORMAL_IN_3D; break; -case SLVS_E_NORMAL_IN_2D: e.type = Entity::Type::NORMAL_IN_2D; break; -case SLVS_E_DISTANCE: e.type = Entity::Type::DISTANCE; break; -case SLVS_E_WORKPLANE: e.type = Entity::Type::WORKPLANE; break; -case SLVS_E_LINE_SEGMENT: e.type = Entity::Type::LINE_SEGMENT; break; -case SLVS_E_CUBIC: e.type = Entity::Type::CUBIC; break; -case SLVS_E_CIRCLE: e.type = Entity::Type::CIRCLE; break; -case SLVS_E_ARC_OF_CIRCLE: e.type = Entity::Type::ARC_OF_CIRCLE; break; - -default: dbp("bad entity type %d", se->type); return; - } - e.h.v = se->h; - e.group.v = se->group; - e.workplane.v = se->wrkpl; - e.point[0].v = se->point[0]; - e.point[1].v = se->point[1]; - e.point[2].v = se->point[2]; - e.point[3].v = se->point[3]; - e.normal.v = se->normal; - e.distance.v = se->distance; - e.param[0].v = se->param[0]; - e.param[1].v = se->param[1]; - e.param[2].v = se->param[2]; - e.param[3].v = se->param[3]; - - SK.entity.Add(&e); - } - IdList params = {}; - for(i = 0; i < ssys->constraints; i++) { - Slvs_Constraint *sc = &(ssys->constraint[i]); - ConstraintBase c = {}; - - Constraint::Type t; - switch(sc->type) { -case SLVS_C_POINTS_COINCIDENT: t = Constraint::Type::POINTS_COINCIDENT; break; -case SLVS_C_PT_PT_DISTANCE: t = Constraint::Type::PT_PT_DISTANCE; break; -case SLVS_C_PT_PLANE_DISTANCE: t = Constraint::Type::PT_PLANE_DISTANCE; break; -case SLVS_C_PT_LINE_DISTANCE: t = Constraint::Type::PT_LINE_DISTANCE; break; -case SLVS_C_PT_FACE_DISTANCE: t = Constraint::Type::PT_FACE_DISTANCE; break; -case SLVS_C_PT_IN_PLANE: t = Constraint::Type::PT_IN_PLANE; break; -case SLVS_C_PT_ON_LINE: t = Constraint::Type::PT_ON_LINE; break; -case SLVS_C_PT_ON_FACE: t = Constraint::Type::PT_ON_FACE; break; -case SLVS_C_EQUAL_LENGTH_LINES: t = Constraint::Type::EQUAL_LENGTH_LINES; break; -case SLVS_C_LENGTH_RATIO: t = Constraint::Type::LENGTH_RATIO; break; -case SLVS_C_EQ_LEN_PT_LINE_D: t = Constraint::Type::EQ_LEN_PT_LINE_D; break; -case SLVS_C_EQ_PT_LN_DISTANCES: t = Constraint::Type::EQ_PT_LN_DISTANCES; break; -case SLVS_C_EQUAL_ANGLE: t = Constraint::Type::EQUAL_ANGLE; break; -case SLVS_C_EQUAL_LINE_ARC_LEN: t = Constraint::Type::EQUAL_LINE_ARC_LEN; break; -case SLVS_C_LENGTH_DIFFERENCE: t = Constraint::Type::LENGTH_DIFFERENCE; break; -case SLVS_C_SYMMETRIC: t = Constraint::Type::SYMMETRIC; break; -case SLVS_C_SYMMETRIC_HORIZ: t = Constraint::Type::SYMMETRIC_HORIZ; break; -case SLVS_C_SYMMETRIC_VERT: t = Constraint::Type::SYMMETRIC_VERT; break; -case SLVS_C_SYMMETRIC_LINE: t = Constraint::Type::SYMMETRIC_LINE; break; -case SLVS_C_AT_MIDPOINT: t = Constraint::Type::AT_MIDPOINT; break; -case SLVS_C_HORIZONTAL: t = Constraint::Type::HORIZONTAL; break; -case SLVS_C_VERTICAL: t = Constraint::Type::VERTICAL; break; -case SLVS_C_DIAMETER: t = Constraint::Type::DIAMETER; break; -case SLVS_C_PT_ON_CIRCLE: t = Constraint::Type::PT_ON_CIRCLE; break; -case SLVS_C_SAME_ORIENTATION: t = Constraint::Type::SAME_ORIENTATION; break; -case SLVS_C_ANGLE: t = Constraint::Type::ANGLE; break; -case SLVS_C_PARALLEL: t = Constraint::Type::PARALLEL; break; -case SLVS_C_PERPENDICULAR: t = Constraint::Type::PERPENDICULAR; break; -case SLVS_C_ARC_LINE_TANGENT: t = Constraint::Type::ARC_LINE_TANGENT; break; -case SLVS_C_CUBIC_LINE_TANGENT: t = Constraint::Type::CUBIC_LINE_TANGENT; break; -case SLVS_C_EQUAL_RADIUS: t = Constraint::Type::EQUAL_RADIUS; break; -case SLVS_C_PROJ_PT_DISTANCE: t = Constraint::Type::PROJ_PT_DISTANCE; break; -case SLVS_C_WHERE_DRAGGED: t = Constraint::Type::WHERE_DRAGGED; break; -case SLVS_C_CURVE_CURVE_TANGENT:t = Constraint::Type::CURVE_CURVE_TANGENT; break; - -default: dbp("bad constraint type %d", sc->type); return; - } - - c.type = t; - - c.h.v = sc->h; - c.group.v = sc->group; - c.workplane.v = sc->wrkpl; - c.valA = sc->valA; - c.ptA.v = sc->ptA; - c.ptB.v = sc->ptB; - c.entityA.v = sc->entityA; - c.entityB.v = sc->entityB; - c.entityC.v = sc->entityC; - c.entityD.v = sc->entityD; - c.other = (sc->other) ? true : false; - c.other2 = (sc->other2) ? true : false; - - c.Generate(¶ms); - if(!params.IsEmpty()) { - for(Param &p : params) { - p.h = SK.param.AddAndAssignId(&p); - c.valP = p.h; - SYS.param.Add(&p); - } - params.Clear(); - c.ModifyToSatisfy(); - } - - SK.constraint.Add(&c); - } - - for(i = 0; i < (int)arraylen(ssys->dragged); i++) { - if(ssys->dragged[i]) { - hParam hp = { ssys->dragged[i] }; - SYS.dragged.Add(&hp); - } - } - - Group g = {}; - g.h.v = shg; - - List bad = {}; - - // Now we're finally ready to solve! - bool andFindBad = ssys->calculateFaileds ? true : false; - SolveResult how = SYS.Solve(&g, NULL, &(ssys->dof), &bad, andFindBad, /*andFindFree=*/false); - - switch(how) { - case SolveResult::OKAY: - ssys->result = SLVS_RESULT_OKAY; - break; - - case SolveResult::DIDNT_CONVERGE: - ssys->result = SLVS_RESULT_DIDNT_CONVERGE; - break; - - case SolveResult::REDUNDANT_DIDNT_CONVERGE: - case SolveResult::REDUNDANT_OKAY: - ssys->result = SLVS_RESULT_INCONSISTENT; - break; - - case SolveResult::TOO_MANY_UNKNOWNS: - ssys->result = SLVS_RESULT_TOO_MANY_UNKNOWNS; - break; - } - - // Write the new parameter values back to our caller. - for(i = 0; i < ssys->params; i++) { - Slvs_Param *sp = &(ssys->param[i]); - hParam hp = { sp->h }; - sp->val = SK.GetParam(hp)->val; - } - - if(ssys->failed) { - // Copy over any the list of problematic constraints. - for(i = 0; i < ssys->faileds && i < bad.n; i++) { - ssys->failed[i] = bad[i].v; - } - ssys->faileds = bad.n; - } - - bad.Clear(); - SYS.param.Clear(); - SYS.entity.Clear(); - SYS.eq.Clear(); - SYS.dragged.Clear(); - - SK.param.Clear(); - SK.entity.Clear(); - SK.constraint.Clear(); - - FreeAllTemporary(); -} - -} /* extern "C" */ diff --git a/src/mesh.cpp b/src/mesh.cpp index 57377d0cc..d0e866df9 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -8,6 +8,8 @@ #include +namespace SolveSpace { + void SMesh::Clear() { l.Clear(); } @@ -16,7 +18,7 @@ void SMesh::AddTriangle(STriMeta meta, Vector n, Vector a, Vector b, Vector c) { Vector ab = b.Minus(a), bc = c.Minus(b); Vector np = ab.Cross(bc); if(np.Magnitude() < 1e-10) { - // ugh; gl sometimes tesselates to collinear triangles + // ugh; gl sometimes tessellates to collinear triangles return; } if(np.Dot(n) > 0) { @@ -371,13 +373,13 @@ Vector SMesh::GetCenterOfMass() const { } STriangleLl *STriangleLl::Alloc() - { return (STriangleLl *)AllocTemporary(sizeof(STriangleLl)); } + { return (STriangleLl *)Platform::AllocTemporary(sizeof(STriangleLl)); } SKdNode *SKdNode::Alloc() - { return (SKdNode *)AllocTemporary(sizeof(SKdNode)); } + { return (SKdNode *)Platform::AllocTemporary(sizeof(SKdNode)); } SKdNode *SKdNode::From(SMesh *m) { int i; - STriangle *tra = (STriangle *)AllocTemporary((m->l.n) * sizeof(*tra)); + STriangle *tra = (STriangle *)Platform::AllocTemporary((m->l.n) * sizeof(*tra)); for(i = 0; i < m->l.n; i++) { tra[i] = m->l[i]; @@ -663,7 +665,7 @@ void SKdNode::SnapToMesh(SMesh *m) { SnapToVertex(v, &extra); for(k = 0; k < extra.l.n; k++) { - STriangle *tra = (STriangle *)AllocTemporary(sizeof(*tra)); + STriangle *tra = (STriangle *)Platform::AllocTemporary(sizeof(*tra)); *tra = extra.l[k]; AddTriangle(tra); } @@ -1215,3 +1217,5 @@ double SMesh::CalculateSurfaceArea(const std::vector &faces) const { } return area; } + +} // namespace SolveSpace diff --git a/src/modify.cpp b/src/modify.cpp index 94b247878..60079df8c 100644 --- a/src/modify.cpp +++ b/src/modify.cpp @@ -7,6 +7,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + //----------------------------------------------------------------------------- // Replace constraints on oldpt with the same constraints on newpt. // Useful when splitting, tangent arcing, or removing bezier points. @@ -50,38 +52,36 @@ void GraphicsWindow::FixConstraintsForRequestBeingDeleted(hRequest hr) { Request *r = SK.GetRequest(hr); if(r->group != SS.GW.activeGroup) return; - Entity *e; - for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - if(!(e->h.isFromRequest())) continue; - if(e->h.request() != hr) continue; + for(Entity &e : SK.entity) { + if(!(e.h.isFromRequest())) continue; + if(e.h.request() != hr) continue; - if(e->type != Entity::Type::POINT_IN_2D && - e->type != Entity::Type::POINT_IN_3D) + if(e.type != Entity::Type::POINT_IN_2D && + e.type != Entity::Type::POINT_IN_3D) { continue; } // This is a point generated by the request being deleted; so fix // the constraints for that. - FixConstraintsForPointBeingDeleted(e->h); + FixConstraintsForPointBeingDeleted(e.h); } } void GraphicsWindow::FixConstraintsForPointBeingDeleted(hEntity hpt) { List ld = {}; - Constraint *c; SK.constraint.ClearTags(); - for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { - if(c->type != Constraint::Type::POINTS_COINCIDENT) continue; - if(c->group != SS.GW.activeGroup) continue; + for(Constraint &c : SK.constraint) { + if(c.type != Constraint::Type::POINTS_COINCIDENT) continue; + if(c.group != SS.GW.activeGroup) continue; - if(c->ptA == hpt) { - ld.Add(&(c->ptB)); - c->tag = 1; + if(c.ptA == hpt) { + ld.Add(&(c.ptB)); + c.tag = 1; } - if(c->ptB == hpt) { - ld.Add(&(c->ptA)); - c->tag = 1; + if(c.ptB == hpt) { + ld.Add(&(c.ptA)); + c.tag = 1; } } // Remove constraints without waiting for regeneration; this way @@ -225,21 +225,21 @@ void GraphicsWindow::ParametricCurve::CreateRequestTrimmedTo(double t, // happens to exist, then constrain that point coincident to hpt. //----------------------------------------------------------------------------- void GraphicsWindow::ParametricCurve::ConstrainPointIfCoincident(hEntity hpt) { - Entity *e, *pt; + Entity *pt; pt = SK.GetEntity(hpt); Vector ev, ptv; ptv = pt->PointGetNum(); - for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - if(e->h == pt->h) continue; - if(!e->IsPoint()) continue; - if(e->group != pt->group) continue; - if(e->workplane != pt->workplane) continue; + for(Entity &e : SK.entity) { + if(e.h == pt->h) continue; + if(!e.IsPoint()) continue; + if(e.group != pt->group) continue; + if(e.workplane != pt->workplane) continue; - ev = e->PointGetNum(); + ev = e.PointGetNum(); if(!ev.Equals(ptv)) continue; - Constraint::ConstrainCoincident(hpt, e->h); + Constraint::ConstrainCoincident(hpt, e.h); break; } } @@ -450,6 +450,14 @@ hEntity GraphicsWindow::SplitLine(hEntity he, Vector pinter) { Vector p0 = SK.GetEntity(hep0)->PointGetNum(), p1 = SK.GetEntity(hep1)->PointGetNum(); + if(p0.Equals(pinter)) { + return hep0; + } + + if(p1.Equals(pinter)) { + return hep1; + } + // Add the two line segments this one gets split into. hRequest r0i = AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false), ri1 = AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false); @@ -584,7 +592,6 @@ hEntity GraphicsWindow::SplitCubic(hEntity he, Vector pinter) { hEntity GraphicsWindow::SplitEntity(hEntity he, Vector pinter) { Entity *e = SK.GetEntity(he); - Entity::Type entityType = e->type; hEntity ret; if(e->IsCircle()) { @@ -599,22 +606,20 @@ hEntity GraphicsWindow::SplitEntity(hEntity he, Vector pinter) { } // Finally, delete the request that generated the original entity. - Request::Type reqType = EntReqTable::GetRequestForEntity(entityType); - SK.request.ClearTags(); - for(auto &r : SK.request) { - if(r.group != activeGroup) - continue; - if(r.type != reqType) - continue; - - // If the user wants to keep the old entities around, they can just - // mark them construction first. - if(he == r.h.entity(0) && !r.construction) { - r.tag = 1; - break; + if(he.isFromRequest() && he == he.request().entity(0)) { + hRequest hr = he.request(); + // Only delete the original request if we actually made a split + // (i.e. the split point is not from the original request) + if(hr != ret.request()) { + Request *r = SK.GetRequest(hr); + // If the user wants to keep the old entities around, they can just + // mark them construction first. + if(r->group == activeGroup && !r->construction) { + r->tag = 1; + DeleteTaggedRequests(); + } } } - DeleteTaggedRequests(); return ret; } @@ -639,13 +644,8 @@ void GraphicsWindow::SplitLinesOrCurves() { Entity *ea = SK.GetEntity(ha), *eb = SK.GetEntity(hb); - SPointList inters = {}; - SBezierList sbla = {}, - sblb = {}; Vector pi = Vector::From(0, 0, 0); - SK.constraint.ClearTags(); - // First, decide the point where we're going to make the split. bool foundInters = false; if(splitAtPoint) { @@ -657,6 +657,8 @@ void GraphicsWindow::SplitLinesOrCurves() { p1 = ea->EndpointFinish(); } + SK.constraint.ClearTags(); + for(Constraint &c : SK.constraint) { if(c.ptA.request() == hb.request() && c.entityA.request() == ha.request()) { @@ -675,25 +677,30 @@ void GraphicsWindow::SplitLinesOrCurves() { } } else { // Compute the possibly-rational Bezier curves for each of these non-point entities... + SBezierList sbla = {}, sblb = {}; ea->GenerateBezierCurves(&sbla); eb->GenerateBezierCurves(&sblb); // ... and then compute the points where they intersect, based on those curves. + SPointList inters = {}; sbla.AllIntersectionsWith(&sblb, &inters); // If there's multiple points, then take the one closest to the mouse pointer. if(!inters.l.IsEmpty()) { double dmin = VERY_POSITIVE; - SPoint *sp; - for(sp = inters.l.First(); sp; sp = inters.l.NextAfter(sp)) { - double d = ProjectPoint(sp->p).DistanceTo(currentMousePosition); + for(const SPoint &sp : inters.l) { + double d = ProjectPoint(sp.p).DistanceTo(currentMousePosition); if(d < dmin) { dmin = d; - pi = sp->p; + pi = sp.p; } } + + foundInters = true; } - foundInters = true; + inters.Clear(); + sbla.Clear(); + sblb.Clear(); } // Then, actually split the entities. @@ -738,8 +745,7 @@ void GraphicsWindow::SplitLinesOrCurves() { } // All done, clean up and regenerate. - inters.Clear(); - sbla.Clear(); - sblb.Clear(); ClearSelection(); } + +} // namespace SolveSpace diff --git a/src/mouse.cpp b/src/mouse.cpp index b8a191f77..930897b23 100644 --- a/src/mouse.cpp +++ b/src/mouse.cpp @@ -5,11 +5,15 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + void GraphicsWindow::UpdateDraggedPoint(hEntity hp, double mx, double my) { Entity *p = SK.GetEntity(hp); Vector pos = p->PointGetNum(); UpdateDraggedNum(&pos, mx, my); p->PointForceTo(pos); + + SS.ScheduleShowTW(); } void GraphicsWindow::UpdateDraggedNum(Vector *pos, double mx, double my) { @@ -55,11 +59,17 @@ void GraphicsWindow::StartDraggingByEntity(hEntity he) { e->type == Entity::Type::IMAGE) { int pts; - EntReqTable::GetEntityInfo(e->type, e->extraPoints, - NULL, &pts, NULL, NULL); + ssassert(EntReqTable::GetEntityInfo(e->type, e->extraPoints, + NULL, &pts, NULL, NULL), "No entity info"); for(int i = 0; i < pts; i++) { AddPointToDraggedList(e->point[i]); } + } else if(e->type == Entity::Type::FACE_NORMAL_PT ) { +// || e->type == Entity::Type::FACE_ROT_NORMAL_PT ) { // needed for helix and revolve +// Revolve is unstable because the point is on the axis. Helix drags in axial direction for same reason +// || e->type == Entity::Type::FACE_N_ROT_TRANS) { // needed for linked objects +// Linking fails possibly because the point[0] is never remapped in Group::CopyEntity + AddPointToDraggedList(e->point[0]); } } @@ -101,7 +111,10 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, shiftDown = !shiftDown; } - if(SS.showToolbar) { + // Not passing right-button and middle-button drags to the toolbar avoids + // some cosmetic issues with trackpad pans/rotates implemented with + // simulated right-button drag events causing spurious hover events. + if(SS.showToolbar && !middleDown) { if(ToolbarMouseMoved((int)x, (int)y)) { hover.Clear(); return; @@ -134,8 +147,9 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, double dy = (y - orig.mouse.y) / scale; if(!(shiftDown || ctrlDown)) { - double s = 0.3*(PI/180)*scale; // degrees per pixel - if(SS.turntableNav) { // lock the Z to vertical + double sign = SS.cameraNav ? -1.0 : 1.0; + double s = 0.3*(PI/180)*scale*sign; // degrees per pixel + if(SS.turntableNav) { // lock the Z to vertical projRight = orig.projRight.RotatedAbout(Vector::From(0, 0, 1), -s * dx); projUp = orig.projUp.RotatedAbout( Vector::From(orig.projRight.x, orig.projRight.y, orig.projRight.y), s * dy); @@ -188,32 +202,23 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, hEntity dragEntity = ChooseFromHoverToDrag().entity; if(dragEntity.v) e = SK.GetEntity(dragEntity); if(e && e->type != Entity::Type::WORKPLANE) { - Entity *e = SK.GetEntity(dragEntity); + if(!hoverWasSelectedOnMousedown) { + // The user clicked an unselected entity, which + // means they're dragging just the hovered thing, + // not the full selection. So clear all the selection + // except that entity. + ClearSelection(); + MakeSelected(dragEntity); + } if(e->type == Entity::Type::CIRCLE && selection.n <= 1) { // Drag the radius. - ClearSelection(); pending.circle = dragEntity; pending.operation = Pending::DRAGGING_RADIUS; } else if(e->IsNormal()) { - ClearSelection(); pending.normal = dragEntity; pending.operation = Pending::DRAGGING_NORMAL; } else { - if(!hoverWasSelectedOnMousedown) { - // The user clicked an unselected entity, which - // means they're dragging just the hovered thing, - // not the full selection. So clear all the selection - // except that entity. - ClearSelection(); - MakeSelected(e->h); - } StartDraggingBySelection(); - if(!hoverWasSelectedOnMousedown) { - // And then clear the selection again, since they - // probably didn't want that selected if they just - // were dragging it. - ClearSelection(); - } hover.Clear(); pending.operation = Pending::DRAGGING_POINTS; } @@ -425,6 +430,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, SK.GetEntity(circle->distance)->DistanceForceTo(r); SS.MarkGroupDirtyByEntity(pending.circle); + SS.ScheduleShowTW(); break; } @@ -719,11 +725,12 @@ void GraphicsWindow::MouseRightUp(double x, double y) { if(gs.points == 1) { Entity *p = SK.GetEntity(gs.point[0]); - Constraint *c; + Constraint *c = nullptr; IdList *lc = &(SK.constraint); - for(c = lc->First(); c; c = lc->NextAfter(c)) { - if(c->type != Constraint::Type::POINTS_COINCIDENT) continue; - if(c->ptA == p->h || c->ptB == p->h) { + for(Constraint &ci : *lc) { + if(ci.type != Constraint::Type::POINTS_COINCIDENT) continue; + if(ci.ptA == p->h || ci.ptB == p->h) { + c = &ci; break; } } @@ -733,11 +740,10 @@ void GraphicsWindow::MouseRightUp(double x, double y) { SS.UndoRemember(); SK.constraint.ClearTags(); - Constraint *c; - for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { - if(c->type != Constraint::Type::POINTS_COINCIDENT) continue; - if(c->ptA == p->h || c->ptB == p->h) { - c->tag = 1; + for(Constraint &c : SK.constraint) { + if(c.type != Constraint::Type::POINTS_COINCIDENT) continue; + if(c.ptA == p->h || c.ptB == p->h) { + c.tag = 1; } } SK.constraint.RemoveTagged(); @@ -827,7 +833,7 @@ Vector GraphicsWindow::SnapToEntityByScreenPoint(Point2d pp, hEntity he) { SEdgeList *edges = e->GetOrGenerateEdges(); double minD = -1.0f; - double k; + double k = 0.0; const SEdge *edge = NULL; for(const auto &e : edges->l) { Point2d p0 = ProjectPoint(e.a); @@ -917,7 +923,7 @@ bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) { break; 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; case MouseEvent::Type::LEAVE: @@ -1140,6 +1146,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my, bool shiftDown, bool ct AddToPending(hr); Request *r = SK.GetRequest(hr); r->file = pending.filename; + r->construction = true; for(int i = 1; i <= 4; i++) { SK.GetEntity(hr.entity(i))->PointForceTo(v); @@ -1311,15 +1318,20 @@ void GraphicsWindow::MouseLeftDown(double mx, double my, bool shiftDown, bool ct void GraphicsWindow::MouseLeftUp(double mx, double my, bool shiftDown, bool ctrlDown) { orig.mouseDown = false; - hoverWasSelectedOnMousedown = false; switch(pending.operation) { case Pending::DRAGGING_POINTS: - SS.extraLine.draw = false; - // fall through case Pending::DRAGGING_CONSTRAINT: case Pending::DRAGGING_NORMAL: case Pending::DRAGGING_RADIUS: + if(!hoverWasSelectedOnMousedown) { + // And then clear the selection again, since they + // probably didn't want that selected if they just + // were dragging it. + ClearSelection(); + } + hoverWasSelectedOnMousedown = false; + SS.extraLine.draw = false; ClearPending(); Invalidate(); break; @@ -1374,12 +1386,12 @@ void GraphicsWindow::EditConstraint(hConstraint constraint) { value /= 2; // Try showing value with default number of digits after decimal first. - if(c->type == Constraint::Type::LENGTH_RATIO) { + if(c->type == Constraint::Type::LENGTH_RATIO || c->type == Constraint::Type::ARC_ARC_LEN_RATIO || c->type == Constraint::Type::ARC_LINE_LEN_RATIO) { editValue = ssprintf("%.3f", value); } else if(c->type == Constraint::Type::ANGLE) { editValue = SS.DegreeToString(value); } else { - editValue = SS.MmToString(value); + editValue = SS.MmToString(value, true); value /= SS.MmPerUnit(); } // If that's not enough to represent it exactly, show the value with as many @@ -1435,7 +1447,9 @@ void GraphicsWindow::EditControlDone(const std::string &s) { case Constraint::Type::PT_LINE_DISTANCE: case Constraint::Type::PT_FACE_DISTANCE: case Constraint::Type::PT_PLANE_DISTANCE: - case Constraint::Type::LENGTH_DIFFERENCE: { + case Constraint::Type::LENGTH_DIFFERENCE: + case Constraint::Type::ARC_ARC_DIFFERENCE: + case Constraint::Type::ARC_LINE_DIFFERENCE: { // The sign is not displayed to the user, but this is a signed // distance internally. To flip the sign, the user enters a // negative distance. @@ -1449,6 +1463,8 @@ void GraphicsWindow::EditControlDone(const std::string &s) { } case Constraint::Type::ANGLE: case Constraint::Type::LENGTH_RATIO: + case Constraint::Type::ARC_ARC_LEN_RATIO: + case Constraint::Type::ARC_LINE_LEN_RATIO: // These don't get the units conversion for distance, and // they're always positive c->valA = fabs(e->Eval()); @@ -1472,17 +1488,10 @@ void GraphicsWindow::EditControlDone(const std::string &s) { } } -void GraphicsWindow::MouseScroll(double x, double y, double delta) { - double offsetRight = offset.Dot(projRight); - double offsetUp = offset.Dot(projUp); - - double righti = x/scale - offsetRight; - double upi = y/scale - offsetUp; - - // The default zoom factor is 1.2x for one scroll wheel click (delta==1). +void GraphicsWindow::MouseScroll(double zoomMultiplyer) { // To support smooth scrolling where scroll wheel events come in increments // 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 // 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 @@ -1490,21 +1499,7 @@ void GraphicsWindow::MouseScroll(double x, double y, double delta) { // while // scale * a * b != scale * (a+b) // So this constant is ln(1.2) = 0.1823216 to make the default zoom 1.2x - scale *= exp(0.1823216 * delta); - - double rightf = x/scale - offsetRight; - double upf = y/scale - offsetUp; - - 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(); + ZoomToMouse(zoomMultiplyer); } void GraphicsWindow::MouseLeave() { @@ -1585,3 +1580,5 @@ void GraphicsWindow::SixDofEvent(Platform::SixDofEvent event) { havePainted = false; Invalidate(); } + +} // namespace SolveSpace diff --git a/src/param.h b/src/param.h new file mode 100644 index 000000000..a32aec8ad --- /dev/null +++ b/src/param.h @@ -0,0 +1,52 @@ +#ifndef SOLVESPACE_PARAM_H +#define SOLVESPACE_PARAM_H + +#include +#include + +#include "handle.h" + +namespace SolveSpace { + +class hRequest; + +class hParam { +public: + // bits 15: 0 -- param index + // 31:16 -- request index + uint32_t v; + + inline hRequest request() const; +}; + +template<> +struct IsHandleOracle : std::true_type {}; + +class Param { +public: + int tag; + hParam h; + + double val; + bool known; + bool free; + + // Used only in the solver + Param *substd; + + static const hParam NO_PARAM; + + void Clear() {} +}; + +// Use a forward declaration in order to avoid pulling dsc.h in for units that +// don't need to use `ParamList` +template class IdList; + +using ParamList = IdList; + +using ParamSet = std::unordered_set>; + +} // namespace SolveSpace + +#endif // !SOLVESPACE_PARAM_H diff --git a/src/platform/entrycli.cpp b/src/platform/entrycli.cpp index 70b6919b0..6284e12a7 100644 --- a/src/platform/entrycli.cpp +++ b/src/platform/entrycli.cpp @@ -3,9 +3,15 @@ // // Copyright 2016 whitequark //----------------------------------------------------------------------------- + +#include +#include + #include "solvespace.h" #include "config.h" +using namespace SolveSpace; + static void ShowUsage(const std::string &cmd) { fprintf(stderr, "Usage: %s [filename...]", cmd.c_str()); //-----------------------------------------------------------------------------> 80 col */ diff --git a/src/platform/gui.cpp b/src/platform/gui.cpp index 28fded44c..0c9637ff4 100644 --- a/src/platform/gui.cpp +++ b/src/platform/gui.cpp @@ -86,8 +86,10 @@ std::vector SolveSpaceModelFileFilters = { }; std::vector SolveSpaceLinkFileFilters = { + { CN_("file-type", "ALL"), { "slvs", "emn", "stl" } }, { CN_("file-type", "SolveSpace models"), { "slvs" } }, { CN_("file-type", "IDF circuit board"), { "emn" } }, + { CN_("file-type", "STL triangle mesh"), { "stl" } }, }; std::vector RasterFileFilters = { diff --git a/src/platform/gui.h b/src/platform/gui.h index 1608a6f21..46dcc182c 100644 --- a/src/platform/gui.h +++ b/src/platform/gui.h @@ -7,6 +7,14 @@ #ifndef SOLVESPACE_GUI_H #define SOLVESPACE_GUI_H +#include +#include +#include +#include + +#include "platform.h" + +namespace SolveSpace { class RgbaColor; namespace Platform { @@ -220,6 +228,7 @@ class Window { std::function onKeyboardEvent; std::function onEditingDone; std::function onScrollbarAdjusted; + std::function onContextLost; std::function onRender; virtual ~Window() = default; @@ -228,7 +237,7 @@ class Window { virtual double GetPixelDensity() = 0; // 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. - virtual int GetDevicePixelRatio() = 0; + virtual double GetDevicePixelRatio() = 0; // Returns (fractional) font scale, to be applied on top of (integral) device pixel ratio. virtual double GetDeviceFontScale() { return GetPixelDensity() / GetDevicePixelRatio() / 96.0; @@ -386,5 +395,6 @@ void ExitGui(); void ClearGui(); } +} // namespace SolveSpace #endif diff --git a/src/platform/guigtk.cpp b/src/platform/guigtk.cpp index e4bc3156c..522fb1358 100644 --- a/src/platform/guigtk.cpp +++ b/src/platform/guigtk.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -33,7 +34,13 @@ #if defined(HAVE_SPACEWARE) # include -# include +# include +# if defined(GDK_WINDOWING_X11) +# include +# endif +# if defined(GDK_WINDOWING_WAYLAND) +# include +# endif # if GTK_CHECK_VERSION(3, 20, 0) # include # else @@ -222,6 +229,10 @@ class TimerImplGtk final : public Timer { } return false; }; + // Note: asan warnings about new-delete-type-mismatch are false positives here: + // https://gitlab.gnome.org/GNOME/gtkmm/-/issues/65 + // Pass new_delete_type_mismatch=0 to ASAN_OPTIONS to disable those warnings. + // Unfortunately they won't go away until upgrading to gtkmm4 _connection = Glib::signal_timeout().connect(handler, milliseconds); } }; @@ -446,10 +457,18 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) { //----------------------------------------------------------------------------- class GtkGLWidget : public Gtk::GLArea { + struct LastPress { + double x, y; + guint button; + guint32 time; + GdkDevice *device; + }; + Window *_receiver; + LastPress _last_press; public: - GtkGLWidget(Platform::Window *receiver) : _receiver(receiver) { + GtkGLWidget(Platform::Window *receiver) : _receiver(receiver), _last_press() { set_has_depth_buffer(true); set_can_focus(true); set_events(Gdk::POINTER_MOTION_MASK | @@ -457,6 +476,8 @@ class GtkGLWidget : public Gtk::GLArea { Gdk::BUTTON_RELEASE_MASK | Gdk::BUTTON_MOTION_MASK | Gdk::SCROLL_MASK | + Gdk::SMOOTH_SCROLL_MASK | + Gdk::TOUCHPAD_GESTURE_MASK | Gdk::LEAVE_NOTIFY_MASK | Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK); @@ -507,8 +528,12 @@ class GtkGLWidget : public Gtk::GLArea { } bool on_motion_notify_event(GdkEventMotion *gdk_event) override { - if(process_pointer_event(MouseEvent::Type::MOTION, - gdk_event->x, gdk_event->y, gdk_event->state)) + double x,y; + GdkModifierType state; + gdk_event_get_coords((GdkEvent*)gdk_event, &x, &y); + gdk_event_get_state((GdkEvent*)gdk_event, &state); + + if(process_pointer_event(MouseEvent::Type::MOTION, x, y, state)) return true; return Gtk::GLArea::on_motion_notify_event(gdk_event); @@ -516,51 +541,137 @@ class GtkGLWidget : public Gtk::GLArea { bool on_button_press_event(GdkEventButton *gdk_event) override { MouseEvent::Type type; - if(gdk_event->type == GDK_BUTTON_PRESS) { + GdkEventType gdk_type; + gdk_type = gdk_event_get_event_type((GdkEvent*)gdk_event); + + if(gdk_type == GDK_BUTTON_PRESS) { type = MouseEvent::Type::PRESS; - } else if(gdk_event->type == GDK_2BUTTON_PRESS) { + } else if(gdk_type == GDK_2BUTTON_PRESS) { type = MouseEvent::Type::DBL_PRESS; } else { return Gtk::GLArea::on_button_press_event(gdk_event); } + double x,y; + gdk_event_get_coords((GdkEvent*)gdk_event, &x, &y); + GdkModifierType state; + gdk_event_get_state((GdkEvent*)gdk_event, &state); + guint button; + gdk_event_get_button((GdkEvent*)gdk_event, &button); + + // In GTK, the sequence of events for a double click is: + // - press + // - release + // - press + // - double-press + // - release + // + // Having a press event right before the double press event is inconsistent with + // the way double click handling works on Windows and macOS, and may cause receiver + // code to treat the sequence as three press events. + // To avoid this, we check if a press event happened quickly enough after the + // previous one to be considered a double press, and eat it up if a receiver + // is set up. + // Since we ignore triple press events, we reset the last press data when receiving + // the double press event, in order to avoid eating up the third press event of + // a triple click sequence. + if(gdk_type == GDK_BUTTON_PRESS) { + LastPress c = { + x, y, button, + gdk_event_get_time((GdkEvent*)gdk_event), + gdk_event_get_device((GdkEvent*)gdk_event), + }; + LastPress p = _last_press; + _last_press = c; + + if(_receiver->onMouseEvent) { + const Glib::RefPtr settings = get_settings(); + const guint dbl_press_time = settings-> + property_gtk_double_click_time().get_value(); + const double dbl_press_distance = settings-> + property_gtk_double_click_distance().get_value(); + + if(c.device == p.device && + c.button == p.button && + c.time - p.time < dbl_press_time && + std::abs(c.x - p.x) <= dbl_press_distance && + std::abs(c.y - p.y) <= dbl_press_distance) { + // Eat this press event, as it'll be followed immediately + // by a double press event + return true; + } + } + } else if(gdk_type == GDK_2BUTTON_PRESS) { + const guint32 time = gdk_event_get_time((GdkEvent*)gdk_event); + const GdkDevice *device = gdk_event_get_device((GdkEvent*)gdk_event); + // A double press event is synthesised by GTK, and has the exact same + // time and device as the press event the preceeded it. + ssassert(_last_press.time == time && _last_press.device == device, + "double press event not following a press event"); + // Reset in order to avoid eating the press event for a triple click + _last_press = {}; + } - if(process_pointer_event(type, gdk_event->x, gdk_event->y, - gdk_event->state, gdk_event->button)) + if(process_pointer_event(type, x, y, state, button)) return true; return Gtk::GLArea::on_button_press_event(gdk_event); } bool on_button_release_event(GdkEventButton *gdk_event) override { - if(process_pointer_event(MouseEvent::Type::RELEASE, - gdk_event->x, gdk_event->y, - gdk_event->state, gdk_event->button)) + double x,y; + gdk_event_get_coords((GdkEvent*)gdk_event, &x, &y); + GdkModifierType state; + gdk_event_get_state((GdkEvent*)gdk_event, &state); + guint button; + gdk_event_get_button((GdkEvent*)gdk_event, &button); + if(process_pointer_event(MouseEvent::Type::RELEASE, x, y, state, button)) return true; return Gtk::GLArea::on_button_release_event(gdk_event); } bool on_scroll_event(GdkEventScroll *gdk_event) override { + double dx, dy; + GdkScrollDirection dir; double delta; - if(gdk_event->delta_y < 0 || gdk_event->direction == GDK_SCROLL_UP) { - delta = 1; - } else if(gdk_event->delta_y > 0 || gdk_event->direction == GDK_SCROLL_DOWN) { - delta = -1; + +// for gtk4 ?? +// gdk_scroll_event_get_deltas((GdkEvent*)gdk_event, &dx, &dy); +// gdk_scroll_event_get_direction((GdkEvent*)gdk_event, &dir); + + if(gdk_event_get_scroll_deltas((GdkEvent*)gdk_event, &dx, &dy)) { + delta = -dy; + } else if(gdk_event_get_scroll_direction((GdkEvent*)gdk_event, &dir)) { + if(dir == GDK_SCROLL_UP) { + delta = 1; + } else if(dir == GDK_SCROLL_DOWN) { + delta = -1; + } else { + return false; + } } else { return false; } + double x,y; + gdk_event_get_coords((GdkEvent*)gdk_event, &x, &y); + GdkModifierType state; + gdk_event_get_state((GdkEvent*)gdk_event, &state); + if(process_pointer_event(MouseEvent::Type::SCROLL_VERT, - gdk_event->x, gdk_event->y, - gdk_event->state, 0, delta)) + x, y, state, 0, delta)) return true; return Gtk::GLArea::on_scroll_event(gdk_event); } bool on_leave_notify_event(GdkEventCrossing *gdk_event) override { - if(process_pointer_event(MouseEvent::Type::LEAVE, - gdk_event->x, gdk_event->y, gdk_event->state)) + double x,y; + gdk_event_get_coords((GdkEvent*)gdk_event, &x, &y); + GdkModifierType state; + gdk_event_get_state((GdkEvent*)gdk_event, &state); + + if(process_pointer_event(MouseEvent::Type::LEAVE, x, y, state)) return true; return Gtk::GLArea::on_leave_notify_event(gdk_event); @@ -570,22 +681,28 @@ class GtkGLWidget : public Gtk::GLArea { KeyboardEvent event = {}; event.type = type; + GdkModifierType state; + gdk_event_get_state((GdkEvent*)gdk_event, &state); + Gdk::ModifierType mod_mask = get_modifier_mask(Gdk::MODIFIER_INTENT_DEFAULT_MOD_MASK); - if((gdk_event->state & mod_mask) & ~(GDK_SHIFT_MASK|GDK_CONTROL_MASK)) { + if((state & mod_mask) & ~(GDK_SHIFT_MASK|GDK_CONTROL_MASK)) { return false; } - event.shiftDown = (gdk_event->state & GDK_SHIFT_MASK) != 0; - event.controlDown = (gdk_event->state & GDK_CONTROL_MASK) != 0; + event.shiftDown = (state & GDK_SHIFT_MASK) != 0; + event.controlDown = (state & GDK_CONTROL_MASK) != 0; + + guint keyval; + gdk_event_get_keyval((GdkEvent*)gdk_event, &keyval); - char32_t chr = gdk_keyval_to_unicode(gdk_keyval_to_lower(gdk_event->keyval)); + char32_t chr = gdk_keyval_to_unicode(gdk_keyval_to_lower(keyval)); if(chr != 0) { event.key = KeyboardEvent::Key::CHARACTER; event.chr = chr; - } else if(gdk_event->keyval >= GDK_KEY_F1 && - gdk_event->keyval <= GDK_KEY_F12) { + } else if(keyval >= GDK_KEY_F1 && + keyval <= GDK_KEY_F12) { event.key = KeyboardEvent::Key::FUNCTION; - event.num = gdk_event->keyval - GDK_KEY_F1 + 1; + event.num = keyval - GDK_KEY_F1 + 1; } else { return false; } @@ -687,8 +804,11 @@ class GtkEditorOverlay : public Gtk::Fixed { protected: bool on_key_press_event(GdkEventKey *gdk_event) override { + guint keyval; + gdk_event_get_keyval((GdkEvent*)gdk_event, &keyval); + if(is_editing()) { - if(gdk_event->keyval == GDK_KEY_Escape) { + if(keyval == GDK_KEY_Escape) { return _gl_widget.event((GdkEvent *)gdk_event); } else { _entry.event((GdkEvent *)gdk_event); @@ -831,7 +951,11 @@ class GtkWindow : public Gtk::Window { } bool on_window_state_event(GdkEventWindowState *gdk_event) override { - _is_fullscreen = gdk_event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN; + // window state event is superseded by GdkWindow::state on GTK4 + GdkWindowState new_window_state; + new_window_state = gdk_event->new_window_state; + + _is_fullscreen = new_window_state & GDK_WINDOW_STATE_FULLSCREEN; if(_receiver->onFullScreen) { _receiver->onFullScreen(_is_fullscreen); } @@ -879,7 +1003,7 @@ class WindowImplGtk final : public Window { return gtkWindow.get_screen()->get_resolution(); } - int GetDevicePixelRatio() override { + double GetDevicePixelRatio() override { return gtkWindow.get_scale_factor(); } @@ -1043,7 +1167,7 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { void Open3DConnexion() {} void Close3DConnexion() {} -#if defined(HAVE_SPACEWARE) && defined(GDK_WINDOWING_X11) +#if defined(HAVE_SPACEWARE) && (defined(GDK_WINDOWING_X11) || defined(GDK_WINDOWING_WAYLAND)) static void ProcessSpnavEvent(WindowImplGtk *window, const spnav_event &spnavEvent, bool shiftDown, bool controlDown) { switch(spnavEvent.type) { case SPNAV_EVENT_MOTION: { @@ -1125,17 +1249,26 @@ void Request3DConnexionEventsForWindow(WindowRef window) { std::static_pointer_cast(window); Glib::RefPtr gdkWindow = windowImpl->gtkWindow.get_window(); - if(!GDK_IS_X11_DISPLAY(gdkWindow->get_display()->gobj())) { - return; +#if defined(GDK_WINDOWING_X11) + if(GDK_IS_X11_DISPLAY(gdkWindow->get_display()->gobj())) { + if(spnav_x11_open(gdk_x11_get_default_xdisplay(), + gdk_x11_window_get_xid(gdkWindow->gobj())) != -1) { + gdkWindow->add_filter(GdkSpnavFilter, windowImpl.get()); + } else if(spnav_open() != -1) { + g_io_add_watch(g_io_channel_unix_new(spnav_fd()), G_IO_IN, + ConsumeSpnavQueue, windowImpl.get()); + } } - - if(spnav_x11_open(gdk_x11_get_default_xdisplay(), - gdk_x11_window_get_xid(gdkWindow->gobj())) != -1) { - gdkWindow->add_filter(GdkSpnavFilter, windowImpl.get()); - } else if(spnav_open() != -1) { - g_io_add_watch(g_io_channel_unix_new(spnav_fd()), G_IO_IN, - ConsumeSpnavQueue, windowImpl.get()); +#endif +#if defined(GDK_WINDOWING_WAYLAND) + if(GDK_IS_WAYLAND_DISPLAY(gdkWindow->get_display()->gobj())) { + if(spnav_open() != -1) { + g_io_add_watch(g_io_channel_unix_new(spnav_fd()), G_IO_IN, + ConsumeSpnavQueue, windowImpl.get()); + } } +#endif + } #else void Request3DConnexionEventsForWindow(WindowRef window) {} @@ -1341,7 +1474,7 @@ class FileDialogImplGtk : public FileDialog { return; Platform::Path path = GetFilename(); - if(gtkChooser->get_action() != GTK_FILE_CHOOSER_ACTION_OPEN) { + if(gtkChooser->get_action() != Gtk::FILE_CHOOSER_ACTION_OPEN) { SetCurrentName(path.WithExtension(extension).FileName()); } } @@ -1379,6 +1512,9 @@ class FileDialogGtkImplGtk final : public FileDialogImplGtk { gtkDialog.add_button(isSave ? C_("button", "_Save") : C_("button", "_Open"), Gtk::RESPONSE_OK); gtkDialog.set_default_response(Gtk::RESPONSE_OK); + if(isSave) { + gtkDialog.set_do_overwrite_confirmation(true); + } InitFileChooser(gtkDialog); } @@ -1412,6 +1548,9 @@ class FileDialogNativeImplGtk final : public FileDialogImplGtk { isSave ? C_("button", "_Save") : C_("button", "_Open"), C_("button", "_Cancel")); + if(isSave) { + gtkNative->set_do_overwrite_confirmation(true); + } // Seriously, GTK?! InitFileChooser(*gtkNative.operator->()); } @@ -1474,7 +1613,8 @@ std::vector GetFontFiles() { } void OpenInBrowser(const std::string &url) { - gtk_show_uri(Gdk::Screen::get_default()->gobj(), url.c_str(), GDK_CURRENT_TIME, NULL); + // first param should be our window? + gtk_show_uri_on_window(NULL, url.c_str(), GDK_CURRENT_TIME, NULL); } Gtk::Main *gtkMain; diff --git a/src/platform/guihtml.cpp b/src/platform/guihtml.cpp new file mode 100644 index 000000000..2625f8e9d --- /dev/null +++ b/src/platform/guihtml.cpp @@ -0,0 +1,1449 @@ +//----------------------------------------------------------------------------- +// The Emscripten-based implementation of platform-dependent GUI functionality. +// +// Copyright 2018 whitequark +//----------------------------------------------------------------------------- +#include +#include +#include +#include +#include "config.h" +#include "solvespace.h" + +using namespace emscripten; + +EMSCRIPTEN_BINDINGS(solvespace) { + emscripten::class_>("VoidFunctor") + .constructor<>() + .function("opcall", &std::function::operator()); + + emscripten::class_>("Void1Functor") + .constructor<>() + .function("opcall", &std::function::operator()); +} + +namespace SolveSpace { +namespace Platform { + +//----------------------------------------------------------------------------- +// Emscripten API bridging +//----------------------------------------------------------------------------- + +#define sscheck(expr) do { \ + EMSCRIPTEN_RESULT emResult = (EMSCRIPTEN_RESULT)(expr); \ + if(emResult < 0) \ + HandleError(__FILE__, __LINE__, __func__, #expr, emResult); \ + } while(0) + +static void HandleError(const char *file, int line, const char *function, const char *expr, + EMSCRIPTEN_RESULT emResult) { + const char *error = "Unknown error"; + switch(emResult) { + case EMSCRIPTEN_RESULT_DEFERRED: error = "Deferred"; break; + case EMSCRIPTEN_RESULT_NOT_SUPPORTED: error = "Not supported"; break; + case EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED: error = "Failed (not deferred)"; break; + case EMSCRIPTEN_RESULT_INVALID_TARGET: error = "Invalid target"; break; + case EMSCRIPTEN_RESULT_UNKNOWN_TARGET: error = "Unknown target"; break; + case EMSCRIPTEN_RESULT_INVALID_PARAM: error = "Invalid parameter"; break; + case EMSCRIPTEN_RESULT_FAILED: error = "Failed"; break; + case EMSCRIPTEN_RESULT_NO_DATA: error = "No data"; break; + } + + std::string message; + message += ssprintf("File %s, line %u, function %s:\n", file, line, function); + message += ssprintf("Emscripten API call failed: %s.\n", expr); + message += ssprintf("Error: %s\n", error); + FatalError(message); +} + +static val Wrap(const std::function &functor) { + return val(functor)["opcall"].call("bind", val(functor)); +} + +static void RegisterEventListener(const val &target, std::string event, std::function functor) { + std::function wrapper = [functor](val) { if(functor) functor(); }; + val wrapped = val(wrapper)["opcall"].call("bind", val(wrapper)); + target.call("addEventListener", event, wrapped); +} + +//----------------------------------------------------------------------------- +// Fatal errors +//----------------------------------------------------------------------------- + +void FatalError(const std::string &message) { + dbp("%s", message.c_str()); +#ifndef NDEBUG + emscripten_debugger(); +#endif + abort(); +} + +//----------------------------------------------------------------------------- +// Settings +//----------------------------------------------------------------------------- + +class SettingsImplHtml : public Settings { +public: + void FreezeInt(const std::string &key, uint32_t value) { + val::global("localStorage").call("setItem", key, value); + } + + uint32_t ThawInt(const std::string &key, uint32_t defaultValue = 0) { + val value = val::global("localStorage").call("getItem", key); + if(value == val::null()) + return defaultValue; + return val::global("parseInt")(value, 0).as(); + } + + void FreezeFloat(const std::string &key, double value) { + val::global("localStorage").call("setItem", key, value); + } + + double ThawFloat(const std::string &key, double defaultValue = 0.0) { + val value = val::global("localStorage").call("getItem", key); + if(value == val::null()) + return defaultValue; + return val::global("parseFloat")(value).as(); + } + + void FreezeString(const std::string &key, const std::string &value) { + val::global("localStorage").call("setItem", key, value); + } + + std::string ThawString(const std::string &key, + const std::string &defaultValue = "") { + val value = val::global("localStorage").call("getItem", key); + if(value == val::null()) { + return defaultValue; + } + return value.as(); + } +}; + +SettingsRef GetSettings() { + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// Timers +//----------------------------------------------------------------------------- + +class TimerImplHtml : public Timer { +public: + static void Callback(void *arg) { + TimerImplHtml *timer = (TimerImplHtml *)arg; + if(timer->onTimeout) { + timer->onTimeout(); + } + } + + void RunAfter(unsigned milliseconds) override { + emscripten_async_call(TimerImplHtml::Callback, this, milliseconds + 1); + } + + void RunAfterNextFrame() override { + emscripten_async_call(TimerImplHtml::Callback, this, 0); + } + + void RunAfterProcessingEvents() override { + emscripten_push_uncounted_main_loop_blocker(TimerImplHtml::Callback, this); + } +}; + +TimerRef CreateTimer() { + return std::unique_ptr(new TimerImplHtml); +} + +//----------------------------------------------------------------------------- +// Menus +//----------------------------------------------------------------------------- + +class MenuItemImplHtml : public MenuItem { +public: + val htmlMenuItem; + + MenuItemImplHtml() : + htmlMenuItem(val::global("document").call("createElement", val("li"))) + {} + + void SetAccelerator(KeyboardEvent accel) override { + val htmlAccel = htmlMenuItem.call("querySelector", val(".accel")); + if(htmlAccel.as()) { + htmlAccel.call("remove"); + } + htmlAccel = val::global("document").call("createElement", val("span")); + htmlAccel.call("setAttribute", val("class"), val("accel")); + htmlAccel.set("innerText", AcceleratorDescription(accel)); + htmlMenuItem.call("appendChild", htmlAccel); + } + + void SetIndicator(Indicator type) override { + val htmlClasses = htmlMenuItem["classList"]; + htmlClasses.call("remove", val("check")); + htmlClasses.call("remove", val("radio")); + switch(type) { + case Indicator::NONE: + break; + + case Indicator::CHECK_MARK: + htmlClasses.call("add", val("check")); + break; + + case Indicator::RADIO_MARK: + htmlClasses.call("add", val("radio")); + break; + } + } + + void SetEnabled(bool enabled) override { + if(enabled) { + htmlMenuItem["classList"].call("remove", val("disabled")); + } else { + htmlMenuItem["classList"].call("add", val("disabled")); + } + } + + void SetActive(bool active) override { + if(active) { + htmlMenuItem["classList"].call("add", val("active")); + } else { + htmlMenuItem["classList"].call("remove", val("active")); + } + } +}; + +class MenuImplHtml; + +static std::shared_ptr popupMenuOnScreen; + +class MenuImplHtml : public Menu, + public std::enable_shared_from_this { +public: + val htmlMenu; + + std::vector> menuItems; + std::vector> subMenus; + + std::function popupDismissFunc; + + MenuImplHtml() : + htmlMenu(val::global("document").call("createElement", val("ul"))) + { + htmlMenu["classList"].call("add", val("menu")); + } + + MenuItemRef AddItem(const std::string &label, std::function onTrigger, + bool mnemonics = true) override { + std::shared_ptr menuItem = std::make_shared(); + menuItems.push_back(menuItem); + menuItem->onTrigger = onTrigger; + + if(mnemonics) { + val::global("window").call("setLabelWithMnemonic", menuItem->htmlMenuItem, + label); + } else { + val htmlLabel = val::global("document").call("createElement", val("span")); + htmlLabel["classList"].call("add", val("label")); + htmlLabel.set("innerText", label); + menuItem->htmlMenuItem.call("appendChild", htmlLabel); + } + RegisterEventListener(menuItem->htmlMenuItem, "trigger", [menuItem]() { + if(menuItem->onTrigger) { + menuItem->onTrigger(); + } + }); + htmlMenu.call("appendChild", menuItem->htmlMenuItem); + return menuItem; + } + + std::shared_ptr AddSubMenu(const std::string &label) override { + val htmlMenuItem = val::global("document").call("createElement", val("li")); + val::global("window").call("setLabelWithMnemonic", htmlMenuItem, label); + htmlMenuItem["classList"].call("add", val("has-submenu")); + htmlMenu.call("appendChild", htmlMenuItem); + + std::shared_ptr subMenu = std::make_shared(); + subMenus.push_back(subMenu); + htmlMenuItem.call("appendChild", subMenu->htmlMenu); + return subMenu; + } + + void AddSeparator() override { + val htmlSeparator = val::global("document").call("createElement", val("li")); + htmlSeparator["classList"].call("add", val("separator")); + htmlMenu.call("appendChild", htmlSeparator); + } + + void PopUp() override { + if(popupMenuOnScreen) { + popupMenuOnScreen->htmlMenu.call("remove"); + popupMenuOnScreen = NULL; + } + + EmscriptenMouseEvent emStatus = {}; + sscheck(emscripten_get_mouse_status(&emStatus)); + htmlMenu["classList"].call("add", val("popup")); + htmlMenu["style"].set("left", std::to_string(emStatus.clientX) + "px"); + htmlMenu["style"].set("top", std::to_string(emStatus.clientY) + "px"); + + val::global("document")["body"].call("appendChild", htmlMenu); + popupMenuOnScreen = shared_from_this(); + } + + void Clear() override { + while(htmlMenu["childElementCount"].as() > 0) { + htmlMenu["firstChild"].call("remove"); + } + } +}; + +MenuRef CreateMenu() { + return std::make_shared(); +} + +class MenuBarImplHtml final : public MenuBar { +public: + val htmlMenuBar; + + std::vector> subMenus; + + MenuBarImplHtml() : + htmlMenuBar(val::global("document").call("createElement", val("ul"))) + { + htmlMenuBar["classList"].call("add", val("menu")); + htmlMenuBar["classList"].call("add", val("menubar")); + } + + std::shared_ptr AddSubMenu(const std::string &label) override { + val htmlMenuItem = val::global("document").call("createElement", val("li")); + val::global("window").call("setLabelWithMnemonic", htmlMenuItem, label); + htmlMenuBar.call("appendChild", htmlMenuItem); + + std::shared_ptr subMenu = std::make_shared(); + subMenus.push_back(subMenu); + htmlMenuItem.call("appendChild", subMenu->htmlMenu); + return subMenu; + } + + void Clear() override { + while(htmlMenuBar["childElementCount"].as() > 0) { + htmlMenuBar["firstChild"].call("remove"); + } + } +}; + +MenuBarRef GetOrCreateMainMenu(bool *unique) { + *unique = false; + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// Windows +//----------------------------------------------------------------------------- + +class TouchEventHelper { +public: + // FIXME(emscripten): Workaround. touchstart and touchend repeats two times. + bool touchActionStarted = false; + + int previousNumTouches = 0; + + double centerX = 0; + double centerY = 0; + + // double startPinchDistance = 0; + double previousPinchDistance = 0; + + std::function onPointerDown; + std::function onPointerMove; + std::function onPointerUp; + std::function onScroll; + + void clear(void) { + touchActionStarted = false; + previousNumTouches = 0; + centerX = 0; + centerY = 0; + // startPinchDistance = 0; + previousPinchDistance = 0; + } + + void calculateCenterPosition(const EmscriptenTouchEvent& emEvent, double& dst_x, double& dst_y) { + double x = 0; + double y = 0; + for (int i = 0; i < emEvent.numTouches; i++) { + x += emEvent.touches[i].targetX; + y += emEvent.touches[i].targetY; + } + dst_x = x / emEvent.numTouches; + dst_y = y / emEvent.numTouches; + } + + void calculatePinchDistance(const EmscriptenTouchEvent& emEvent, double& dst_distance) { + if (emEvent.numTouches < 2) { + return; + } + double x1 = emEvent.touches[0].targetX; + double y1 = emEvent.touches[0].targetY; + double x2 = emEvent.touches[1].targetX; + double y2 = emEvent.touches[1].targetY; + dst_distance = std::sqrt(std::pow(x1 - x2, 2) + std::pow(y1 - y2, 2)); + } + + void createMouseEventPRESS(const EmscriptenTouchEvent& emEvent, MouseEvent& dst_mouseevent) { + double x = 0, y = 0; + this->calculateCenterPosition(emEvent, x, y); + this->centerX = x; + this->centerY = y; + this->touchActionStarted = true; + this->previousNumTouches = emEvent.numTouches; + dst_mouseevent.type = MouseEvent::Type::PRESS; + dst_mouseevent.x = x; + dst_mouseevent.y = y; + dst_mouseevent.shiftDown = emEvent.shiftKey; + dst_mouseevent.controlDown = emEvent.ctrlKey; + switch(emEvent.numTouches) { + case 1: + dst_mouseevent.button = MouseEvent::Button::LEFT; + break; + case 2: { + dst_mouseevent.button = MouseEvent::Button::RIGHT; + // double distance = 0; + this->calculatePinchDistance(emEvent, this->previousPinchDistance); + // this->startPinchDistance = distance; + // this->previousPinchDistance = distance; + break; + } + default: + dst_mouseevent.button = MouseEvent::Button::MIDDLE; + break; + } + } + + void createMouseEventRELEASE(const EmscriptenTouchEvent& emEvent, MouseEvent& dst_mouseevent) { + this->calculateCenterPosition(emEvent, this->centerX, this->centerY); + this->previousNumTouches = 0; + dst_mouseevent.type = MouseEvent::Type::RELEASE; + dst_mouseevent.x = this->centerX; + dst_mouseevent.y = this->centerY; + dst_mouseevent.shiftDown = emEvent.shiftKey; + dst_mouseevent.controlDown = emEvent.ctrlKey; + switch(this->previousNumTouches) { + case 1: + dst_mouseevent.button = MouseEvent::Button::LEFT; + break; + case 2: + dst_mouseevent.button = MouseEvent::Button::RIGHT; + break; + default: + dst_mouseevent.button = MouseEvent::Button::MIDDLE; + break; + } + } + + void createMouseEventMOTION(const EmscriptenTouchEvent& emEvent, MouseEvent& dst_mouseevent) { + dst_mouseevent.type = MouseEvent::Type::MOTION; + this->calculateCenterPosition(emEvent, this->centerX, this->centerY); + dst_mouseevent.x = this->centerX; + dst_mouseevent.y = this->centerY; + dst_mouseevent.shiftDown = emEvent.shiftKey; + dst_mouseevent.controlDown = emEvent.ctrlKey; + switch(emEvent.numTouches) { + case 1: + dst_mouseevent.button = MouseEvent::Button::LEFT; + break; + case 2: + dst_mouseevent.button = MouseEvent::Button::RIGHT; + break; + default: + dst_mouseevent.button = MouseEvent::Button::MIDDLE; + break; + } + } + + void createMouseEventSCROLL(const EmscriptenTouchEvent& emEvent, MouseEvent& event) { + event.type = MouseEvent::Type::SCROLL_VERT; + double newDistance = 0; + this->calculatePinchDistance(emEvent, newDistance); + this->calculateCenterPosition(emEvent, this->centerX, this->centerY); + event.x = this->centerX; + event.y = this->centerY; + event.shiftDown = emEvent.shiftKey; + event.controlDown = emEvent.ctrlKey; + // FIXME(emscripten): best value range for scrollDelta ? + event.scrollDelta = (newDistance - this->previousPinchDistance) / 25.0; + if (std::abs(event.scrollDelta) > 2) { + event.scrollDelta = 2; + if (std::signbit(event.scrollDelta)) { + event.scrollDelta *= -1.0; + } + } + this->previousPinchDistance = newDistance; + } + + void onTouchStart(const EmscriptenTouchEvent& emEvent, void* callbackParameter) { + if (this->touchActionStarted) { + // dbp("onTouchStart(): Break due to already started."); + return; + } + + MouseEvent event; + this->createMouseEventPRESS(emEvent, event); + this->previousNumTouches = emEvent.numTouches; + if (this->onPointerDown) { + // dbp("onPointerDown(): numTouches=%d, timestamp=%f", emEvent.numTouches, emEvent.timestamp); + this->onPointerDown(&event, callbackParameter); + } + } + + void onTouchMove(const EmscriptenTouchEvent& emEvent, void* callbackParameter) { + this->calculateCenterPosition(emEvent, this->centerX, this->centerY); + int newNumTouches = emEvent.numTouches; + if (newNumTouches != this->previousNumTouches) { + MouseEvent releaseEvent; + + this->createMouseEventRELEASE(emEvent, releaseEvent); + if (this->onPointerUp) { + // dbp("onPointerUp(): numTouches=%d, timestamp=%f", emEvent.numTouches, emEvent.timestamp); + this->onPointerUp(&releaseEvent, callbackParameter); + } + + MouseEvent pressEvent; + + this->createMouseEventPRESS(emEvent, pressEvent); + if (this->onPointerDown) { + // dbp("onPointerDown(): numTouches=%d, timestamp=%f", emEvent.numTouches, emEvent.timestamp); + this->onPointerDown(&pressEvent, callbackParameter); + } + } + + MouseEvent motionEvent = { }; + this->createMouseEventMOTION(emEvent, motionEvent); + + if (this->onPointerMove) { + // dbp("onPointerMove(): numTouches=%d, timestamp=%f", emEvent.numTouches, emEvent.timestamp); + this->onPointerMove(&motionEvent, callbackParameter); + } + + if (emEvent.numTouches == 2) { + MouseEvent scrollEvent; + this->createMouseEventSCROLL(emEvent, scrollEvent); + if (scrollEvent.scrollDelta != 0) { + if (this->onScroll) { + // dbp("Scroll %f", scrollEvent.scrollDelta); + this->onScroll(&scrollEvent, callbackParameter); + } + } + } + + this->previousNumTouches = emEvent.numTouches; + } + + void onTouchEnd(const EmscriptenTouchEvent& emEvent, void* callbackParameter) { + if (!this->touchActionStarted) { + return; + } + + MouseEvent releaseEvent = { }; + this->createMouseEventRELEASE(emEvent, releaseEvent); + + if (this->onPointerUp) { + // dbp("onPointerUp(): numTouches=%d, timestamp=%d", emEvent.numTouches, emEvent.timestamp); + this->onPointerUp(&releaseEvent, callbackParameter); + } + + this->clear(); + } + + void onTouchCancel(const EmscriptenTouchEvent& emEvent, void* callbackParameter) { + this->onTouchEnd(emEvent, callbackParameter); + } +}; + +static TouchEventHelper touchEventHelper; + +static KeyboardEvent handledKeyboardEvent; + +class WindowImplHtml final : public Window { +public: + std::string emCanvasSel; + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE emContext = 0; + + val htmlContainer; + val htmlEditor; + val scrollbarHelper; + + std::shared_ptr menuBar; + + WindowImplHtml(val htmlContainer, std::string emCanvasSel) : + emCanvasSel(emCanvasSel), + htmlContainer(htmlContainer), + htmlEditor(val::global("document").call("createElement", val("input"))) + { + htmlEditor["classList"].call("add", val("editor")); + htmlEditor["style"].set("display", "none"); + auto editingDoneFunc = [this] { + if(onEditingDone) { + onEditingDone(htmlEditor["value"].as()); + } + }; + RegisterEventListener(htmlEditor, "trigger", editingDoneFunc); + htmlContainer["parentElement"].call("appendChild", htmlEditor); + + std::string scrollbarElementQuery = emCanvasSel + "scrollbar"; + dbp("scrollbar element query: \"%s\"", scrollbarElementQuery.c_str()); + val scrollbarElement = val::global("document").call("querySelector", val(scrollbarElementQuery)); + if (scrollbarElement == val::null()) { + // dbp("scrollbar element is null."); + this->scrollbarHelper = val::null(); + } else { + dbp("scrollbar element OK."); + this->scrollbarHelper = val::global("window")["ScrollbarHelper"].new_(val(scrollbarElementQuery)); + static std::function onScrollCallback = [this] { + // dbp("onScrollCallback std::function this=%p", (void*)this); + if (this->onScrollbarAdjusted) { + double newpos = this->scrollbarHelper.call("getScrollbarPosition"); + // dbp(" call onScrollbarAdjusted(%f)", newpos); + this->onScrollbarAdjusted(newpos); + } + this->Invalidate(); + }; + this->scrollbarHelper.set("onScrollCallback", Wrap(onScrollCallback)); + } + + sscheck(emscripten_set_resize_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, this, /*useCapture=*/false, + WindowImplHtml::ResizeCallback)); + sscheck(emscripten_set_resize_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::ResizeCallback)); + sscheck(emscripten_set_mousemove_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::MouseCallback)); + sscheck(emscripten_set_mousedown_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::MouseCallback)); + sscheck(emscripten_set_click_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::MouseCallback)); + sscheck(emscripten_set_dblclick_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::MouseCallback)); + sscheck(emscripten_set_mouseup_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::MouseCallback)); + sscheck(emscripten_set_mouseleave_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::MouseCallback)); + + sscheck(emscripten_set_touchstart_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::TouchCallback)); + sscheck(emscripten_set_touchmove_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::TouchCallback)); + sscheck(emscripten_set_touchend_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::TouchCallback)); + sscheck(emscripten_set_touchcancel_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::TouchCallback)); + + sscheck(emscripten_set_wheel_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::WheelCallback)); + sscheck(emscripten_set_keydown_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, this, /*useCapture=*/false, + WindowImplHtml::KeyboardCallback)); + sscheck(emscripten_set_keyup_callback( + EMSCRIPTEN_EVENT_TARGET_WINDOW, this, /*useCapture=*/false, + WindowImplHtml::KeyboardCallback)); + sscheck(emscripten_set_webglcontextlost_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::ContextLostCallback)); + sscheck(emscripten_set_webglcontextrestored_callback( + emCanvasSel.c_str(), this, /*useCapture=*/false, + WindowImplHtml::ContextRestoredCallback)); + + ResizeCanvasElement(); + SetupWebGLContext(); + } + + ~WindowImplHtml() { + if(emContext != 0) { + sscheck(emscripten_webgl_destroy_context(emContext)); + } + } + + static EM_BOOL ResizeCallback(int emEventType, const EmscriptenUiEvent *emEvent, void *data) { + WindowImplHtml *window = (WindowImplHtml *)data; + window->Invalidate(); + return EM_TRUE; + } + + static EM_BOOL MouseCallback(int emEventType, const EmscriptenMouseEvent *emEvent, + void *data) { + if(val::global("window").call("isModal")) return EM_FALSE; + + WindowImplHtml *window = (WindowImplHtml *)data; + MouseEvent event = {}; + switch(emEventType) { + case EMSCRIPTEN_EVENT_MOUSEMOVE: + event.type = MouseEvent::Type::MOTION; + break; + case EMSCRIPTEN_EVENT_MOUSEDOWN: + event.type = MouseEvent::Type::PRESS; + break; + case EMSCRIPTEN_EVENT_DBLCLICK: + event.type = MouseEvent::Type::DBL_PRESS; + break; + case EMSCRIPTEN_EVENT_MOUSEUP: + event.type = MouseEvent::Type::RELEASE; + break; + case EMSCRIPTEN_EVENT_MOUSELEAVE: + event.type = MouseEvent::Type::LEAVE; + break; + default: + return EM_FALSE; + } + switch(emEventType) { + case EMSCRIPTEN_EVENT_MOUSEMOVE: + if(emEvent->buttons & 1) { + event.button = MouseEvent::Button::LEFT; + } else if(emEvent->buttons & 2) { + event.button = MouseEvent::Button::RIGHT; + } else if(emEvent->buttons & 4) { + event.button = MouseEvent::Button::MIDDLE; + } + break; + case EMSCRIPTEN_EVENT_MOUSEDOWN: + case EMSCRIPTEN_EVENT_DBLCLICK: + case EMSCRIPTEN_EVENT_MOUSEUP: + switch(emEvent->button) { + case 0: event.button = MouseEvent::Button::LEFT; break; + case 1: event.button = MouseEvent::Button::MIDDLE; break; + case 2: event.button = MouseEvent::Button::RIGHT; break; + } + break; + default: + return EM_FALSE; + } + event.x = emEvent->targetX; + event.y = emEvent->targetY; + event.shiftDown = emEvent->shiftKey || emEvent->altKey; + event.controlDown = emEvent->ctrlKey; + + if(window->onMouseEvent) { + return window->onMouseEvent(event); + } + return EM_FALSE; + } + + static EM_BOOL TouchCallback(int emEventType, const EmscriptenTouchEvent *emEvent, + void *data) { + + if(val::global("window").call("isModal")) return EM_FALSE; + + static bool initialized = false; + + WindowImplHtml *window = (WindowImplHtml *)data; + + if (!initialized) { + touchEventHelper.onPointerDown = [](MouseEvent* event, void* param) -> void { + WindowImplHtml* window = (WindowImplHtml*)param; + if (window->onMouseEvent) { + window->onMouseEvent(*event); + } + }; + touchEventHelper.onPointerMove = [](MouseEvent* event, void* param) -> void { + WindowImplHtml* window = (WindowImplHtml*)param; + if (window->onMouseEvent) { + window->onMouseEvent(*event); + } + }; + touchEventHelper.onPointerUp = [](MouseEvent* event, void* param) -> void { + WindowImplHtml* window = (WindowImplHtml*)param; + if (window->onMouseEvent) { + window->onMouseEvent(*event); + } + }; + touchEventHelper.onScroll = [](MouseEvent* event, void* param) -> void { + WindowImplHtml* window = (WindowImplHtml*)param; + if (window->onMouseEvent) { + window->onMouseEvent(*event); + } + }; + initialized = true; + } + + switch(emEventType) { + case EMSCRIPTEN_EVENT_TOUCHSTART: + touchEventHelper.onTouchStart(*emEvent, window); + break; + case EMSCRIPTEN_EVENT_TOUCHMOVE: + touchEventHelper.onTouchMove(*emEvent, window); + break; + case EMSCRIPTEN_EVENT_TOUCHEND: + touchEventHelper.onTouchEnd(*emEvent, window); + break; + case EMSCRIPTEN_EVENT_TOUCHCANCEL: + touchEventHelper.onTouchCancel(*emEvent, window); + break; + default: + return EM_FALSE; + } + + return true; + } + + static EM_BOOL WheelCallback(int emEventType, const EmscriptenWheelEvent *emEvent, + void *data) { + if(val::global("window").call("isModal")) return EM_FALSE; + + WindowImplHtml *window = (WindowImplHtml *)data; + MouseEvent event = {}; + if(emEvent->deltaY != 0) { + event.type = MouseEvent::Type::SCROLL_VERT; + // FIXME(emscripten): + // Pay attention to: + // dbp("Mouse wheel delta mode: %lu", emEvent->deltaMode); + // https://emscripten.org/docs/api_reference/html5.h.html#id11 + // https://www.w3.org/TR/DOM-Level-3-Events/#dom-wheelevent-deltamode + // and adjust the 0.01 below. deltaMode == 0 on a Firefox on a Windows. + event.scrollDelta = -emEvent->deltaY * 0.01; + } else { + return EM_FALSE; + } + + const EmscriptenMouseEvent &emStatus = emEvent->mouse; + event.x = emStatus.targetX; + event.y = emStatus.targetY; + event.shiftDown = emStatus.shiftKey; + event.controlDown = emStatus.ctrlKey; + + if(window->onMouseEvent) { + return window->onMouseEvent(event); + } + return EM_FALSE; + } + + static EM_BOOL KeyboardCallback(int emEventType, const EmscriptenKeyboardEvent *emEvent, + void *data) { + if(emEvent->altKey) return EM_FALSE; + if(emEvent->repeat) return EM_FALSE; + + WindowImplHtml *window = (WindowImplHtml *)data; + KeyboardEvent event = {}; + switch(emEventType) { + case EMSCRIPTEN_EVENT_KEYDOWN: + event.type = KeyboardEvent::Type::PRESS; + break; + + case EMSCRIPTEN_EVENT_KEYUP: + event.type = KeyboardEvent::Type::RELEASE; + break; + + default: + return EM_FALSE; + } + event.shiftDown = emEvent->shiftKey; + event.controlDown = emEvent->ctrlKey; + + std::string key = emEvent->key; + if(key[0] == 'F' && isdigit(key[1])) { + event.key = KeyboardEvent::Key::FUNCTION; + event.num = std::stol(key.substr(1)); + } else { + event.key = KeyboardEvent::Key::CHARACTER; + + auto utf8 = ReadUTF8(key); + if(++utf8.begin() == utf8.end()) { + event.chr = tolower(*utf8.begin()); + } else if(key == "Escape") { + event.chr = '\e'; + } else if(key == "Tab") { + event.chr = '\t'; + } else if(key == "Backspace") { + event.chr = '\b'; + } else if(key == "Delete") { + event.chr = '\x7f'; + } else { + return EM_FALSE; + } + + if(event.chr == '>' && event.shiftDown) { + event.shiftDown = false; + } + } + + if(event.Equals(handledKeyboardEvent)) return EM_FALSE; + if(val::global("window").call("isModal")) { + handledKeyboardEvent = {}; + return EM_FALSE; + } + + if(window->onKeyboardEvent) { + if(window->onKeyboardEvent(event)) { + handledKeyboardEvent = event; + return EM_TRUE; + } + } + return EM_FALSE; + } + + void SetupWebGLContext() { + EmscriptenWebGLContextAttributes emAttribs = {}; + emscripten_webgl_init_context_attributes(&emAttribs); + emAttribs.alpha = false; + emAttribs.failIfMajorPerformanceCaveat = true; + + sscheck(emContext = emscripten_webgl_create_context(emCanvasSel.c_str(), &emAttribs)); + dbp("Canvas %s: got context %d", emCanvasSel.c_str(), emContext); + } + + static bool ContextLostCallback(int eventType, const void *reserved, void *data) { + WindowImplHtml *window = (WindowImplHtml *)data; + dbp("Canvas %s: context lost", window->emCanvasSel.c_str()); + window->emContext = 0; + + if(window->onContextLost) { + window->onContextLost(); + } + return EM_TRUE; + } + + static bool ContextRestoredCallback(int eventType, const void *reserved, void *data) { + WindowImplHtml *window = (WindowImplHtml *)data; + dbp("Canvas %s: context restored", window->emCanvasSel.c_str()); + window->SetupWebGLContext(); + return EM_TRUE; + } + + void ResizeCanvasElement() { + double width, height; + std::string htmlContainerSel = "#" + htmlContainer["id"].as(); + sscheck(emscripten_get_element_css_size(htmlContainerSel.c_str(), &width, &height)); + // sscheck(emscripten_get_element_css_size(emCanvasSel.c_str(), &width, &height)); + + double devicePixelRatio = GetDevicePixelRatio(); + width *= devicePixelRatio; + height *= devicePixelRatio; + + int currentWidth = 0, currentHeight = 0; + sscheck(emscripten_get_canvas_element_size(emCanvasSel.c_str(), ¤tWidth, ¤tHeight)); + + if ((int)width != currentWidth || (int)height != currentHeight) { + // dbp("Canvas %s container current size: (%d, %d)", emCanvasSel.c_str(), (int)currentWidth, (int)currentHeight); + // dbp("Canvas %s: resizing to (%d, %d)", emCanvasSel.c_str(), (int)width, (int)height); + sscheck(emscripten_set_canvas_element_size(emCanvasSel.c_str(), (int)width, (int)height)); + } + } + + static void RenderCallback(void *data) { + WindowImplHtml *window = (WindowImplHtml *)data; + if(window->emContext == 0) { + dbp("Canvas %s: cannot render: no context", window->emCanvasSel.c_str()); + return; + } + + window->ResizeCanvasElement(); + sscheck(emscripten_webgl_make_context_current(window->emContext)); + if(window->onRender) { + window->onRender(); + } + } + + double GetPixelDensity() override { + return 96.0 * GetDevicePixelRatio(); + } + + double GetDevicePixelRatio() override { + return emscripten_get_device_pixel_ratio(); + } + + bool IsVisible() override { + // FIXME(emscripten): implement + return true; + } + + void SetVisible(bool visible) override { + // FIXME(emscripten): implement + } + + void Focus() override { + // Do nothing, we can't affect focus of browser windows. + } + + bool IsFullScreen() override { + EmscriptenFullscreenChangeEvent emEvent = {}; + sscheck(emscripten_get_fullscreen_status(&emEvent)); + return emEvent.isFullscreen; + } + + void SetFullScreen(bool fullScreen) override { + if(fullScreen) { + EmscriptenFullscreenStrategy emStrategy = {}; + emStrategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; + emStrategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF; + emStrategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; + sscheck(emscripten_request_fullscreen_strategy( + emCanvasSel.c_str(), /*deferUntilInEventHandler=*/true, &emStrategy)); + } else { + sscheck(emscripten_exit_fullscreen()); + } + } + + void SetTitle(const std::string &title) override { + // FIXME(emscripten): implement + } + + void SetMenuBar(MenuBarRef menuBar) override { + std::shared_ptr menuBarImpl = + std::static_pointer_cast(menuBar); + this->menuBar = menuBarImpl; + + val htmlMain = val::global("document").call("querySelector", val("main")); + val htmlCurrentMenuBar = htmlMain.call("querySelector", val(".menubar")); + if(htmlCurrentMenuBar.as()) { + htmlCurrentMenuBar.call("remove"); + } + htmlMain.call("insertBefore", menuBarImpl->htmlMenuBar, + htmlMain["firstChild"]); + ResizeCanvasElement(); + } + + void GetContentSize(double *width, double *height) override { + sscheck(emscripten_get_element_css_size(emCanvasSel.c_str(), width, height)); + } + + void SetMinContentSize(double width, double height) override { + // Do nothing, we can't affect sizing of browser windows. + } + + void FreezePosition(SettingsRef settings, const std::string &key) override { + // Do nothing, we can't position browser windows. + } + + void ThawPosition(SettingsRef settings, const std::string &key) override { + // Do nothing, we can't position browser windows. + } + + void SetCursor(Cursor cursor) override { + std::string htmlCursor; + switch(cursor) { + case Cursor::POINTER: htmlCursor = "default"; break; + case Cursor::HAND: htmlCursor = "pointer"; break; + } + htmlContainer["style"].set("cursor", htmlCursor); + } + + void SetTooltip(const std::string &text, double x, double y, + double width, double height) override { + val htmlCanvas = + val::global("document").call("querySelector", emCanvasSel); + htmlCanvas.set("title", text); + } + + bool IsEditorVisible() override { + return htmlEditor["style"]["display"].as() != "none"; + } + + void ShowEditor(double x, double y, double fontHeight, double minWidth, + bool isMonospace, const std::string &text) override { + htmlEditor["style"].set("display", val("")); + val canvasClientRect = val::global("document").call("querySelector", val(this->emCanvasSel)).call("getBoundingClientRect"); + double canvasLeft = canvasClientRect["left"].as(); + double canvasTop = canvasClientRect["top"].as(); + htmlEditor["style"].set("left", std::to_string(canvasLeft + x - 4) + "px"); + htmlEditor["style"].set("top", std::to_string(canvasTop + y - fontHeight - 2) + "px"); + htmlEditor["style"].set("fontSize", std::to_string(fontHeight) + "px"); + htmlEditor["style"].set("minWidth", std::to_string(minWidth) + "px"); + htmlEditor["style"].set("fontFamily", isMonospace ? "monospace" : "sans"); + htmlEditor.set("value", text); + htmlEditor.call("focus"); + } + + void HideEditor() override { + htmlEditor["style"].set("display", val("none")); + } + + void SetScrollbarVisible(bool visible) override { + // dbp("SetScrollbarVisible(): visible=%d", visible ? 1 : 0); + if (this->scrollbarHelper == val::null()) { + // dbp("scrollbarHelper is null."); + return; + } + if (!visible) { + this->scrollbarHelper.call("setScrollbarEnabled", val(false)); + } + } + + double scrollbarPos = 0.0; + double scrollbarMin = 0.0; + double scrollbarMax = 0.0; + double scrollbarPageSize = 0.0; + + void ConfigureScrollbar(double min, double max, double pageSize) override { + // dbp("ConfigureScrollbar(): min=%f, max=%f, pageSize=%f", min, max, pageSize); + if (this->scrollbarHelper == val::null()) { + // dbp("scrollbarHelper is null."); + return; + } + // FIXME(emscripten): implement + this->scrollbarMin = min; + this->scrollbarMax = max; + this->scrollbarPageSize = pageSize; + + this->scrollbarHelper.call("setRange", this->scrollbarMin, this->scrollbarMax); + this->scrollbarHelper.call("setPageSize", pageSize); + } + + double GetScrollbarPosition() override { + // dbp("GetScrollbarPosition()"); + if (this->scrollbarHelper == val::null()) { + // dbp("scrollbarHelper is null."); + return 0; + } + this->scrollbarPos = this->scrollbarHelper.call("getScrollbarPosition"); + // dbp(" GetScrollbarPosition() returns %f", this->scrollbarPos); + return scrollbarPos; + } + + void SetScrollbarPosition(double pos) override { + // dbp("SetScrollbarPosition(): pos=%f", pos); + if (this->scrollbarHelper == val::null()) { + // dbp("scrollbarHelper is null."); + return; + } + this->scrollbarHelper.call("setScrollbarPosition", pos); + scrollbarPos = pos; + } + + void Invalidate() override { + emscripten_async_call(WindowImplHtml::RenderCallback, this, -1); + } +}; + +WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { + static int windowNum; + + std::string htmlContainerId = std::string("container") + std::to_string(windowNum); + val htmlContainer = + val::global("document").call("getElementById", htmlContainerId); + std::string emCanvasSel = std::string("#canvas") + std::to_string(windowNum); + + windowNum++; + return std::make_shared(htmlContainer, emCanvasSel); +} + +//----------------------------------------------------------------------------- +// 3DConnexion support +//----------------------------------------------------------------------------- + +void Open3DConnexion() {} +void Close3DConnexion() {} +void Request3DConnexionEventsForWindow(WindowRef window) {} + +//----------------------------------------------------------------------------- +// Message dialogs +//----------------------------------------------------------------------------- + +class MessageDialogImplHtml; + +static std::vector> dialogsOnScreen; + +class MessageDialogImplHtml final : public MessageDialog, + public std::enable_shared_from_this { +public: + val htmlModal; + val htmlDialog; + val htmlMessage; + val htmlDescription; + val htmlButtons; + + bool is_shown = false; + + Response latestResponse = Response::NONE; + + MessageDialogImplHtml() : + htmlModal(val::global("document").call("createElement", val("div"))), + htmlDialog(val::global("document").call("createElement", val("div"))), + htmlMessage(val::global("document").call("createElement", val("strong"))), + htmlDescription(val::global("document").call("createElement", val("p"))), + htmlButtons(val::global("document").call("createElement", val("div"))) + { + htmlModal["classList"].call("add", val("modal")); + htmlModal.call("appendChild", htmlDialog); + htmlDialog["classList"].call("add", val("dialog")); + htmlDialog.call("appendChild", htmlMessage); + htmlDialog.call("appendChild", htmlDescription); + htmlButtons["classList"].call("add", val("buttons")); + htmlDialog.call("appendChild", htmlButtons); + } + + void SetType(Type type) { + // FIXME(emscripten): implement + } + + void SetTitle(std::string title) { + // FIXME(emscripten): implement + } + + void SetMessage(std::string message) { + htmlMessage.set("innerText", message); + } + + void SetDescription(std::string description) { + htmlDescription.set("innerText", description); + } + + void AddButton(std::string label, Response response, bool isDefault = false) { + val htmlButton = val::global("document").call("createElement", val("div")); + htmlButton["classList"].call("add", val("button")); + val::global("window").call("setLabelWithMnemonic", htmlButton, label); + if(isDefault) { + htmlButton["classList"].call("add", val("default"), val("selected")); + } + + std::function responseFunc = [this, response] { + htmlModal.call("remove"); + this->latestResponse = response; + if(onResponse) { + onResponse(response); + } + auto it = std::remove(dialogsOnScreen.begin(), dialogsOnScreen.end(), + shared_from_this()); + dialogsOnScreen.erase(it); + + this->is_shown = false; + }; + RegisterEventListener(htmlButton, "trigger", responseFunc); + + htmlButtons.call("appendChild", htmlButton); + } + + Response RunModal() { + this->ShowModal(); + //FIXME(emscripten): use val::await() with JavaScript's Promise + while (true) { + if (this->is_shown) { + emscripten_sleep(50); + } else { + break; + } + } + + if (this->latestResponse != Response::NONE) { + return this->latestResponse; + } else { + // FIXME(emscripten): + return this->latestResponse; + } + } + + void ShowModal() { + dialogsOnScreen.push_back(shared_from_this()); + val::global("document")["body"].call("appendChild", htmlModal); + + this->is_shown = true; + } +}; + +MessageDialogRef CreateMessageDialog(WindowRef parentWindow) { + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// File dialogs +//----------------------------------------------------------------------------- + +// In emscripten pseudo filesystem, all userdata will be stored in this directory. +static std::string basePathInFilesystem = "/data/"; + + +/* FileDialog that can open, save and browse. Also refer `src/platform/html/filemanagerui.js`. + */ +class FileDialogImplHtml : public FileDialog { +public: + + enum class Modes { + OPEN = 0, + SAVE, + BROWSER + }; + + Modes mode; + + std::string title; + std::string filename; + std::string filters; + + val jsFileManagerUI; + + FileDialogImplHtml(Modes mode) { + dbp("FileDialogImplHtml::FileDialogImplHtml()"); + val fileManagerUIClass = val::global("window")["FileManagerUI"]; + val dialogModeValue; + this->mode = mode; + if (mode == Modes::OPEN) { + dialogModeValue = val(0); + } else if (mode == Modes::SAVE) { + dialogModeValue = val(1); + } else { + dialogModeValue = val(2); + } + this->jsFileManagerUI = fileManagerUIClass.new_(dialogModeValue); + dbp("FileDialogImplHtml::FileDialogImplHtml() Done."); + } + + ~FileDialogImplHtml() override { + dbp("FileDialogImplHtml::~FileDialogImplHtml()"); + this->jsFileManagerUI.call("dispose"); + } + + void SetTitle(std::string title) override { + dbp("FileDialogImplHtml::SetTitle(): title=\"%s\"", title.c_str()); + this->title = title; + this->jsFileManagerUI.call("setTitle", val(title)); + } + + void SetCurrentName(std::string name) override { + dbp("FileDialogImplHtml::SetCurrentName(): name=\"%s\", parent=\"%s\"", name.c_str(), this->GetFilename().Parent().raw.c_str()); + + Path filepath = Path::From(name); + if (filepath.IsAbsolute()) { + // dbp("FileDialogImplHtml::SetCurrentName(): path is absolute."); + SetFilename(filepath); + } else { + // dbp("FileDialogImplHtml::SetCurrentName(): path is relative."); + SetFilename(GetFilename().Parent().Join(name)); + } + } + + Platform::Path GetFilename() override { + return Platform::Path::From(this->filename.c_str()); + } + + void SetFilename(Platform::Path path) override { + dbp("FileDialogImplHtml::GetFilename(): path=\"%s\"", path.raw.c_str()); + this->filename = std::string(path.raw); + std::string filename_ = Path::From(this->filename).FileName(); + this->jsFileManagerUI.call("setDefaultFilename", val(filename_)); + } + + void SuggestFilename(Platform::Path path) override { + dbp("FileDialogImplHtml::SuggestFilename(): path=\"%s\"", path.raw.c_str()); + SetFilename(Platform::Path::From(path.FileStem())); + } + + void AddFilter(std::string name, std::vector extensions) override { + if (this->filters.length() > 0) { + this->filters += ","; + } + for (size_t i = 0; i < extensions.size(); i++) { + if (i != 0) { + this->filters += ","; + } + this->filters += "." + extensions[i]; + } + dbp("FileDialogImplHtml::AddFilter(): filter=%s", this->filters.c_str()); + this->jsFileManagerUI.call("setFilter", val(this->filters)); + } + + void FreezeChoices(SettingsRef settings, const std::string &key) override { + //FIXME(emscripten): implement + } + + void ThawChoices(SettingsRef settings, const std::string &key) override { + //FIXME(emscripten): implement + } + + bool RunModal() override { + dbp("FileDialogImplHtml::RunModal()"); + + this->jsFileManagerUI.call("setBasePath", val(basePathInFilesystem)); + this->jsFileManagerUI.call("show"); + while (true) { + bool isShown = this->jsFileManagerUI.call("isShown"); + if (!isShown) { + break; + } else { + emscripten_sleep(50); + } + } + + dbp("FileSaveDialogImplHtml::RunModal() : dialog closed."); + + std::string selectedFilename = this->jsFileManagerUI.call("getSelectedFilename"); + if (selectedFilename.length() > 0) { + // Dummy call to set parent directory + this->SetFilename(Path::From(basePathInFilesystem + "/dummy")); + this->SetCurrentName(selectedFilename); + } + + + if (selectedFilename.length() > 0) { + return true; + } else { + return false; + } + } +}; + +FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) { + dbp("CreateOpenFileDialog()"); + return std::shared_ptr(new FileDialogImplHtml(FileDialogImplHtml::Modes::OPEN)); +} + +FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) { + dbp("CreateSaveFileDialog()"); + return std::shared_ptr(new FileDialogImplHtml(FileDialogImplHtml::Modes::SAVE)); +} + +//----------------------------------------------------------------------------- +// Application-wide APIs +//----------------------------------------------------------------------------- + +std::vector GetFontFiles() { + return {}; +} + +void OpenInBrowser(const std::string &url) { + val::global("window").call("open", url); +} + + +void OnSaveFinishedCallback(const Platform::Path& filename, bool is_saveAs, bool is_autosave) { + dbp("OnSaveFinished(): %s, is_saveAs=%d, is_autosave=%d\n", filename.FileName().c_str(), is_saveAs, is_autosave); + val::global("window").call("saveFileDone", filename.raw, is_saveAs, is_autosave); +} + +std::vector InitGui(int argc, char **argv) { + std::function onBeforeUnload = std::bind(&SolveSpaceUI::Exit, &SS); + RegisterEventListener(val::global("window"), "beforeunload", onBeforeUnload); + + // dbp("Set onSaveFinished"); + SS.OnSaveFinished = OnSaveFinishedCallback; + + // FIXME(emscripten): get locale from user preferences + SetLocale("en_US"); + + return {}; +} + +static void MainLoopIteration() { + // We don't do anything here, as all our work is registered via timers. +} + +void RunGui() { + emscripten_set_main_loop(MainLoopIteration, 0, /*simulate_infinite_loop=*/true); +} + +void ExitGui() { + exit(0); +} + +void ClearGui() {} + +} +} diff --git a/src/platform/guimac.mm b/src/platform/guimac.mm index b2b07a8bf..3bfd66c1f 100644 --- a/src/platform/guimac.mm +++ b/src/platform/guimac.mm @@ -286,7 +286,8 @@ void AddSeparator() override { } void PopUp() override { - [NSMenu popUpContextMenu:nsMenu withEvent:[NSApp currentEvent] forView:nil]; + NSEvent* event = [NSApp currentEvent]; + [NSMenu popUpContextMenu:nsMenu withEvent:event forView:event.window.contentView]; } void Clear() override { @@ -358,18 +359,27 @@ - (void)stopEditing; - (void)didEdit:(NSString *)text; @property double scrollerMin; -@property double scrollerMax; +@property double scrollerSize; +@property double pageSize; + @end @implementation SSView { NSTrackingArea *trackingArea; NSTextField *editor; + double magnificationGestureCurrentZ; + double rotationGestureCurrent; + Point2d trackpadPositionShift; + bool inTrackpadScrollGesture; + int activeTrackpadTouches; + bool scrollFromTrackpadTouch; + Platform::Window::Kind kind; } @synthesize acceptsFirstResponder; -- (id)initWithFrame:(NSRect)frameRect { +- (id)initWithKind:(Platform::Window::Kind)aKind { NSOpenGLPixelFormatAttribute attrs[] = { NSOpenGLPFADoubleBuffer, NSOpenGLPFAColorSize, 24, @@ -377,7 +387,7 @@ - (id)initWithFrame:(NSRect)frameRect { 0 }; NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; - if(self = [super initWithFrame:frameRect pixelFormat:pixelFormat]) { + if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0) pixelFormat:pixelFormat]) { self.wantsBestResolutionOpenGLSurface = YES; self.wantsLayer = YES; editor = [[NSTextField alloc] init]; @@ -387,6 +397,21 @@ - (id)initWithFrame:(NSRect)frameRect { editor.bezeled = NO; editor.target = self; editor.action = @selector(didEdit:); + + inTrackpadScrollGesture = false; + activeTrackpadTouches = 0; + scrollFromTrackpadTouch = false; + self.acceptsTouchEvents = YES; + kind = aKind; + if(kind == Platform::Window::Kind::TOPLEVEL) { + NSGestureRecognizer *mag = [[NSMagnificationGestureRecognizer alloc] initWithTarget:self + action:@selector(magnifyGesture:)]; + [self addGestureRecognizer:mag]; + + NSRotationGestureRecognizer* rot = [[NSRotationGestureRecognizer alloc] initWithTarget:self + action:@selector(rotateGesture:)]; + [self addGestureRecognizer:rot]; + } } return self; } @@ -427,9 +452,9 @@ - (void)updateTrackingAreas { - (Platform::MouseEvent)convertMouseEvent:(NSEvent *)nsEvent { Platform::MouseEvent event = {}; - NSPoint nsPoint = [self convertPoint:nsEvent.locationInWindow fromView:self]; + NSPoint nsPoint = [self convertPoint:nsEvent.locationInWindow fromView:nil]; event.x = nsPoint.x; - event.y = self.bounds.size.height - nsPoint.y; + event.y = nsPoint.y; NSUInteger nsFlags = [nsEvent modifierFlags]; if(nsFlags & NSEventModifierFlagShift) event.shiftDown = true; @@ -553,14 +578,78 @@ - (void)scrollWheel:(NSEvent *)nsEvent { using Platform::MouseEvent; MouseEvent event = [self convertMouseEvent:nsEvent]; + if(nsEvent.phase == NSEventPhaseBegan) { + // If this scroll began on trackpad then touchesBeganWithEvent was called prior to this + // 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 + // 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 + // shift in the same way as right-mouse-button MouseEvent does (to converts a pan to a + // rotate) so we get the rotate support for free. It's a bit ugly having to fake mouse + // events and track the deviation from the actual mouse cursor with trackpadPositionShift, + // but in lieu of an event API that allows us to request a rotate/pan with relative + // coordinates, it's the best we can do. + event.button = MouseEvent::Button::RIGHT; + // Make sure control (actually cmd) isn't passed through, ctrl-right-click-drag has special + // meaning as rotate which we don't want to inadvertently trigger. + event.controlDown = false; + if(nsEvent.scrollingDeltaX == 0 && nsEvent.scrollingDeltaY == 0) { + // Cocoa represents the point where the user lifts their fingers off (and any inertial + // scrolling has finished) by an event with scrollingDeltaX and scrollingDeltaY both 0. + // Sometimes you also get a zero scroll at the start of a two-finger-rotate (probably + // reflecting the internal implementation of that being a cancelled possible pan + // gesture), which is why this conditional is structured the way it is. + if(inTrackpadScrollGesture) { + event.x += trackpadPositionShift.x; + event.y += trackpadPositionShift.y; + event.type = MouseEvent::Type::RELEASE; + receiver->onMouseEvent(event); + inTrackpadScrollGesture = false; + trackpadPositionShift = Point2d::From(0, 0); + } + return; + } else if(!inTrackpadScrollGesture) { + inTrackpadScrollGesture = true; + trackpadPositionShift = Point2d::From(0, 0); + event.type = MouseEvent::Type::PRESS; + receiver->onMouseEvent(event); + // And drop through + } + + trackpadPositionShift.x += nsEvent.scrollingDeltaX; + trackpadPositionShift.y += nsEvent.scrollingDeltaY; + event.type = MouseEvent::Type::MOTION; + event.x += trackpadPositionShift.x; + event.y += trackpadPositionShift.y; + receiver->onMouseEvent(event); + return; + } + event.type = MouseEvent::Type::SCROLL_VERT; bool isPrecise = [nsEvent hasPreciseScrollingDeltas]; event.scrollDelta = [nsEvent scrollingDeltaY] / (isPrecise ? 50 : 5); - if(receiver->onMouseEvent) { - receiver->onMouseEvent(event); - } + receiver->onMouseEvent(event); +} + +- (void)touchesBeganWithEvent:(NSEvent *)event { + activeTrackpadTouches++; +} + +- (void)touchesEndedWithEvent:(NSEvent *)event { + activeTrackpadTouches--; +} + +- (void)touchesCancelledWithEvent:(NSEvent *)event { + activeTrackpadTouches--; } - (void)mouseExited:(NSEvent *)nsEvent { @@ -638,6 +727,50 @@ - (void)keyUp:(NSEvent *)nsEvent { [super keyUp:nsEvent]; } +- (void)magnifyGesture:(NSMagnificationGestureRecognizer *)gesture { + // The onSixDofEvent API doesn't allow us to specify the scaling's origin, so for expediency + // we fake out a scrollwheel MouseEvent with a suitably-scaled scrollDelta with a bit of + // absolute-to-relative positioning conversion tracked using magnificationGestureCurrentZ. + + if(gesture.state == NSGestureRecognizerStateBegan) { + magnificationGestureCurrentZ = 0.0; + } + + // Magic number to make gesture.magnification align roughly with what scrollDelta expects + constexpr double kScale = 10.0; + double z = ((double)gesture.magnification * kScale); + double zdelta = z - magnificationGestureCurrentZ; + magnificationGestureCurrentZ = z; + + using Platform::MouseEvent; + MouseEvent event = {}; + event.type = MouseEvent::Type::SCROLL_VERT; + NSPoint nsPoint = [gesture locationInView:self]; + event.x = nsPoint.x; + event.y = nsPoint.y; + event.scrollDelta = zdelta; + if(receiver->onMouseEvent) { + receiver->onMouseEvent(event); + } +} + +- (void)rotateGesture:(NSRotationGestureRecognizer *)gesture { + if(gesture.state == NSGestureRecognizerStateBegan) { + rotationGestureCurrent = 0.0; + } + double rotation = gesture.rotation; + double rotationDelta = rotation - rotationGestureCurrent; + rotationGestureCurrent = rotation; + + using Platform::SixDofEvent; + SixDofEvent event = {}; + event.type = SixDofEvent::Type::MOTION; + event.rotationZ = rotationDelta; + if(receiver->onSixDofEvent) { + receiver->onSixDofEvent(event); + } +} + @synthesize editing; - (void)startEditing:(NSString *)text at:(NSPoint)origin withHeight:(double)fontHeight @@ -698,11 +831,27 @@ - (void)cancelOperation:(id)sender { } @synthesize scrollerMin; -@synthesize scrollerMax; +@synthesize scrollerSize; +@synthesize pageSize; - (void)didScroll:(NSScroller *)sender { + double pos; + switch(sender.hitPart) { + case NSScrollerKnob: + case NSScrollerKnobSlot: + pos = receiver->GetScrollbarPosition(); + break; + case NSScrollerDecrementPage: + pos = receiver->GetScrollbarPosition() - pageSize; + break; + case NSScrollerIncrementPage: + pos = receiver->GetScrollbarPosition() + pageSize; + break; + default: + return; + } + if(receiver->onScrollbarAdjusted) { - double pos = scrollerMin + [sender doubleValue] * (scrollerMax - scrollerMin); receiver->onScrollbarAdjusted(pos); } } @@ -769,7 +918,7 @@ - (void)windowDidExitFullScreen:(NSNotification *)notification { NSString *nsToolTip; WindowImplCocoa(Window::Kind kind, std::shared_ptr parentWindow) { - ssView = [[SSView alloc] init]; + ssView = [[SSView alloc] initWithKind:kind]; ssView.translatesAutoresizingMaskIntoConstraints = NO; ssView.receiver = this; @@ -838,10 +987,10 @@ - (void)windowDidExitFullScreen:(NSNotification *)notification { return (displayPixelSize.width / displayPhysicalSize.width) * 25.4f; } - int GetDevicePixelRatio() override { + double GetDevicePixelRatio() override { NSSize unitSize = { 1.0f, 0.0f }; unitSize = [ssView convertSizeToBacking:unitSize]; - return (int)unitSize.width; + return unitSize.width; } bool IsVisible() override { @@ -962,21 +1111,22 @@ void SetScrollbarVisible(bool visible) override { void ConfigureScrollbar(double min, double max, double pageSize) override { ssView.scrollerMin = min; - ssView.scrollerMax = max - pageSize; - [nsScroller setKnobProportion:(pageSize / (ssView.scrollerMax - ssView.scrollerMin))]; + ssView.scrollerSize = max + 1 - min; + ssView.pageSize = pageSize; + nsScroller.knobProportion = pageSize / ssView.scrollerSize; + nsScroller.hidden = pageSize >= ssView.scrollerSize; } double GetScrollbarPosition() override { + // Platform::Window scrollbar positions are in the range [min, max+1 - pageSize] inclusive, + // and Cocoa scrollbars are from 0.0 to 1.0 inclusive, so we have to apply some scaling and + // transforming. (scrollerSize is max+1-min, see ConfigureScrollbar above) return ssView.scrollerMin + - [nsScroller doubleValue] * (ssView.scrollerMax - ssView.scrollerMin); + nsScroller.doubleValue * (ssView.scrollerSize - ssView.pageSize); } void SetScrollbarPosition(double pos) override { - if(pos > ssView.scrollerMax) - pos = ssView.scrollerMax; - if(GetScrollbarPosition() == pos) - return; - [nsScroller setDoubleValue:(pos / (ssView.scrollerMax - ssView.scrollerMin))]; + nsScroller.doubleValue = (pos - ssView.scrollerMin) / ( ssView.scrollerSize - ssView.pageSize); } void Invalidate() override { @@ -1278,8 +1428,10 @@ void SuggestFilename(Platform::Path path) override { } void FreezeChoices(SettingsRef settings, const std::string &key) override { - settings->FreezeString("Dialog_" + key + "_Folder", - [nsPanel.directoryURL.absoluteString UTF8String]); + if (nsPanel.directoryURL != nil) { + settings->FreezeString("Dialog_" + key + "_Folder", + [nsPanel.directoryURL.absoluteString UTF8String]); + } } void ThawChoices(SettingsRef settings, const std::string &key) override { @@ -1426,9 +1578,22 @@ @interface SSApplicationDelegate : NSObject - (IBAction)preferences:(id)sender; - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename; - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; + +@property BOOL exiting; + @end @implementation SSApplicationDelegate + +@synthesize exiting; + +- (id)init { + if (self = [super init]) { + self.exiting = false; + } + return self; +} + - (IBAction)preferences:(id)sender { if (!SS.GW.showTextWindow) { SolveSpace::SS.GW.MenuView(SolveSpace::Command::SHOW_TEXT_WND); @@ -1443,12 +1608,27 @@ - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filenam } - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { - [[[NSApp mainWindow] delegate] windowShouldClose:[NSApp mainWindow]]; - return NSTerminateCancel; + if(!SS.unsaved) { + return NSTerminateNow; + } else { + [self performSelectorOnMainThread:@selector(applicationTerminatePrompt) withObject:nil + waitUntilDone:NO modes:@[NSDefaultRunLoopMode, NSModalPanelRunLoopMode]]; + return NSTerminateLater; + } +} + +- (void)applicationWillTerminate:(NSNotification *)notification { + if(!exiting) { + // Prevent the Platform::ExitGui() call from SolveSpaceUI::Exit() + // triggering another terminate + exiting = true; + // Now let SS save settings etc + SS.Exit(); + } } - (void)applicationTerminatePrompt { - SolveSpace::SS.MenuFile(SolveSpace::Command::EXIT); + [NSApp replyToApplicationShouldTerminate:SS.OkayToStartNewFile()]; } @end @@ -1469,6 +1649,14 @@ - (void)applicationTerminatePrompt { ssDelegate = [[SSApplicationDelegate alloc] init]; NSApplication.sharedApplication.delegate = ssDelegate; + // Setting this prevents "Show Tab Bar" and "Show All Tabs" items from being + // automagically added to the View menu + NSWindow.allowsAutomaticWindowTabbing = NO; + + // And this prevents the duplicate "Enter Full Screen" menu item, see + // https://stackoverflow.com/questions/52154977/how-to-get-rid-of-enter-full-screen-menu-item + [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"]; + [NSBundle.mainBundle loadNibNamed:@"MainMenu" owner:nil topLevelObjects:nil]; NSArray *languages = NSLocale.preferredLanguages; @@ -1487,8 +1675,10 @@ void RunGui() { } void ExitGui() { - [NSApp setDelegate:nil]; - [NSApp terminate:nil]; + if(!ssDelegate.exiting) { + ssDelegate.exiting = true; + [NSApp terminate:nil]; + } } void ClearGui() {} diff --git a/src/platform/guiqt.cpp b/src/platform/guiqt.cpp new file mode 100644 index 000000000..518dafc90 --- /dev/null +++ b/src/platform/guiqt.cpp @@ -0,0 +1,1074 @@ +//----------------------------------------------------------------------------- +// The Qt-based implementation of platform-dependent GUI functionality. +// +// Copyright 2024 Karl Robillard +// Copyright 2023 Raed Marwan +// SPDX-License-Identifier: GPL-3.0-or-later +//----------------------------------------------------------------------------- + +#include +#include "solvespace.h" +#include "platform.h" +#include "guiqt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace SolveSpace { +namespace Platform { + +//----------------------------------------------------------------------------- +// Fatal errors +//----------------------------------------------------------------------------- + +void FatalError(const std::string& message) +{ + QMessageBox::critical(NULL, QString("Fatal Error"), + QString::fromStdString(message), + QMessageBox::StandardButton::Ok); + abort(); +} + +//----------------------------------------------------------------------------- +// Message dialogs +//----------------------------------------------------------------------------- + +class MessageDialogImplQt final : public MessageDialog { +public: + QMessageBox messageBoxQ; + + MessageDialogImplQt(QWidget* parent) : messageBoxQ(parent) { + } + +#undef ERROR + void SetType(Type type) override { + switch (type) { + case Type::INFORMATION: + messageBoxQ.setIcon(QMessageBox::Information); + break; + case Type::QUESTION: + messageBoxQ.setIcon(QMessageBox::Question); + break; + case Type::WARNING: + messageBoxQ.setIcon(QMessageBox::Warning); + break; + case Type::ERROR: + messageBoxQ.setIcon(QMessageBox::Critical); + break; + } + } + + void SetTitle(std::string title) override { + messageBoxQ.setWindowTitle(QString::fromStdString(title)); + } + + void SetMessage(std::string message) override { + messageBoxQ.setText(QString::fromStdString(message)); + } + + void SetDescription(std::string description) override { + messageBoxQ.setInformativeText(QString::fromStdString(description)); + } + + void AddButton(std::string label, Response response, bool isDefault) override { + QMessageBox::StandardButton std; + switch (response) { + case Response::NONE: ssassert(false, "Invalid response"); + case Response::OK: + std = QMessageBox::StandardButton::Ok; + break; + case Response::YES: + std = QMessageBox::StandardButton::Yes; + break; + case Response::NO: + std = QMessageBox::StandardButton::No; + break; + case Response::CANCEL: + std = QMessageBox::StandardButton::Cancel; + break; + } + QPushButton* button = messageBoxQ.addButton(std); + button->setText(QString::fromStdString(label)); + if (isDefault) + messageBoxQ.setDefaultButton(button); + } + + Response RunModal() { + switch (messageBoxQ.exec()) + { + case QMessageBox::StandardButton::Cancel: + return Response::CANCEL; + case QMessageBox::StandardButton::No: + return Response::NO; + case QMessageBox::StandardButton::Yes: + return Response::YES; + case QMessageBox::StandardButton::Ok: + return Response::OK; + default: // NONE + return Response::NONE; + } + } +}; + +//----------------------------------------------------------------------------- +// File dialogs +//----------------------------------------------------------------------------- + +class FileDialogImplQt final : public FileDialog { +public: + QFileDialog fileDialogQ; + QStringList filters; + bool filtersMod; + + FileDialogImplQt(QWidget* parent, bool save) : filtersMod(false) { + fileDialogQ.setParent(parent); + fileDialogQ.setWindowFlags(Qt::Dialog); // Reset after setParent. + + // NOTE: The file extension is automatically set by the KDE native + // dialog but not with the Qt one. +#if 0 + fileDialogQ.setOption(QFileDialog::DontUseNativeDialog); +#endif + + if (save) { + fileDialogQ.setAcceptMode(QFileDialog::AcceptSave); + } + } + + void SetTitle(std::string title) { + fileDialogQ.setWindowTitle(QString::fromStdString(title)); + } + + void SetCurrentName(std::string name) { + fileDialogQ.selectFile(QString::fromStdString(name)); + } + + Platform::Path GetFilename() { + QStringList files = fileDialogQ.selectedFiles(); + return Path::From(files.at(0).toStdString()); + } + + void SetFilename(Platform::Path path) { + fileDialogQ.selectFile(QString::fromStdString(path.raw)); + } + + void SuggestFilename(Platform::Path path) { + fileDialogQ.selectFile(QString::fromStdString(path.FileStem())); + } + + void AddFilter(std::string name, std::vector extensions) override { + QString fline(name.c_str()); + int count = 0; + for (auto& ext : extensions) { + fline.append(count ? " *." : " (*."); + fline.append(ext.c_str()); + ++count; + } + fline.append(")"); + + //printf("AddFilter \"%s\"\n", fline.toLocal8Bit().data()); + filters.append(fline); + filtersMod = true; + } + + void updateFilters() { + if (filtersMod) { + filtersMod = false; + fileDialogQ.setNameFilters(filters); + } + } + + void FreezeChoices(SettingsRef settings, const std::string& key) + { + settings->FreezeString("Dialog_" + key + "_Folder", + fileDialogQ.directory().absolutePath().toStdString()); + settings->FreezeString("Dialog_" + key + "_Filter", + fileDialogQ.selectedNameFilter().toStdString()); + } + + void ThawChoices(SettingsRef settings, const std::string &key) + { + updateFilters(); + + std::string val; + val = settings->ThawString("Dialog_" + key + "_Folder"); + fileDialogQ.setDirectory(QDir(QString::fromStdString(val))); + val = settings->ThawString("Dialog_" + key + "_Filter"); + fileDialogQ.selectNameFilter(QString::fromStdString(val)); + } + + bool RunModal() override { + updateFilters(); + return fileDialogQ.exec(); + } +}; + +//----------------------------------------------------------------------------- +// Settings +//----------------------------------------------------------------------------- + +class SettingsImplQt final : public Settings { +public: + QSettings qset; + + SettingsImplQt() : qset("SolveSpace", "solvespace") { + } + + void FreezeInt(const std::string& key, uint32_t value) override { + qset.setValue(QString::fromStdString(key), value); + } + + uint32_t ThawInt(const std::string& key, uint32_t defaultValue = 0) override { + return qset.value(QString::fromStdString(key), defaultValue).toInt(); + } + + void FreezeFloat(const std::string& key, double value) override{ + qset.setValue(QString::fromStdString(key), value); + } + + double ThawFloat(const std::string& key, double defaultValue = 0.0) override { + return qset.value(QString::fromStdString(key), defaultValue).toDouble(); + } + + void FreezeString(const std::string& key, const std::string& value) override { + qset.setValue(QString::fromStdString(key), + QString::fromStdString(value)); + } + + std::string ThawString(const std::string& key, const std::string& defaultValue = "") override { + return qset.value(QString::fromStdString(key), + QString::fromStdString(defaultValue)).toString().toStdString(); + } +}; + +SettingsRef GetSettings() +{ + static std::shared_ptr settings; + if (!settings) { + settings = std::make_shared(); + } + return settings; +} + +//----------------------------------------------------------------------------- +// Timers +//----------------------------------------------------------------------------- + +class TimerImplQt final : public Timer, public QObject +{ +public: + TimerImplQt() {} + + void RunAfter(unsigned milliseconds) override { + QTimer::singleShot(milliseconds, this, &TimerImplQt::runOnTimeOut); + } + + void runOnTimeOut() { + if(onTimeout) { + onTimeout(); + } + } +}; + +TimerRef CreateTimer() { + std::shared_ptr timer = std::make_shared(); + return timer; +} + +//----------------------------------------------------------------------------- +// Menus +//----------------------------------------------------------------------------- + +class MenuItemImplQt final : public MenuItem, public QObject { +public: + QAction action; + + MenuItemImplQt() { + connect(&action, &QAction::triggered, this, &MenuItemImplQt::onTriggered); + } + + void SetAccelerator(KeyboardEvent accel) override { + int key = 0; + + if (accel.key == KeyboardEvent::Key::CHARACTER) { + if (accel.chr == '\t') { + key = Qt::Key_Tab; + } else if (accel.chr == '\x1b') { + key = Qt::Key_Escape; + } else if (accel.chr == '\x7f') { + key = Qt::Key_Delete; + } else { + key = std::toupper(accel.chr); + } + } else if (accel.key == KeyboardEvent::Key::FUNCTION) { + key = Qt::Key_F1 + accel.num - 1; + } + + if (key) { + if (accel.controlDown) { + key |= Qt::CTRL; + } + if (accel.shiftDown) { + key |= Qt::SHIFT; + } + action.setShortcut(key); + } + } + + /* + RADIO_MARK must be implemented using QActionGroup so we scan for + sequential actions with the "radio_mark" property to form these groups. + It would be safer if the Indicator type was an argument to + Platform::Menu::AddItem rather than a set by a separate method to guarantee + that the Indicator type can never change. + */ + void SetIndicator(Indicator type) override { + switch (type) + { + case Indicator::NONE: + action.setCheckable(false); + break; + case Indicator::CHECK_MARK: + action.setCheckable(true); + break; + case Indicator::RADIO_MARK: + action.setCheckable(true); + // Added to QActionGroup later by formActionGroups(). + action.setProperty("radio_mark", true); + break; + } + } + + void SetEnabled(bool enabled) override { + action.setEnabled(enabled); + } + + void SetActive(bool active) override { + action.setChecked(active); + } + +public slots: + void onTriggered(bool value) + { + if (onTrigger) + onTrigger(); + } +}; + +class MenuImplQt final : public Menu { +public: + QMenu* menuQ; + bool hasParent; + std::vector> menuItems; + std::vector> subMenus; + + MenuImplQt(bool hasParentParam = true) { + menuQ = new QMenu(); + hasParent = hasParentParam; + } + + MenuImplQt(QMenu* menuQParam) { + menuQ = menuQParam; + hasParent = true; + } + + std::shared_ptr AddItem(const std::string& label, std::function onTrigger, + bool /*mnemonics*/) override { + + std::shared_ptr menuItem = std::make_shared(); + menuItem->onTrigger = onTrigger; + menuItem->action.setText(QString::fromStdString(label)); + // I do not think anything is needed for mnemonics flag. + // the shortcut key sequence is set in the menuItem class and Qt acts + // accordingly with no further activation ... I think + menuItems.push_back(menuItem); + + // The QWidget::addAction method does not take ownership of the action. + menuQ->addAction(&menuItem->action); + return menuItem; + } + + std::shared_ptr AddSubMenu(const std::string& label) override { + std::shared_ptr subMenu = std::make_shared(menuQ->addMenu(QString::fromStdString(label))); + subMenus.push_back(subMenu); + return subMenu; + } + + void AddSeparator() override { + menuQ->addSeparator(); + } + + void PopUp() override { + if (true == this->hasParent) + return; + + menuQ->popup(menuQ->mapFromGlobal(QCursor::pos())); + menuQ->exec(); + } + + void Clear() override { + menuQ->clear(); + menuItems.clear(); + subMenus.clear(); + } +}; + +MenuRef CreateMenu() { + return std::make_shared(false); // false says -> menu has not Qtwidget as a parent +} + +class MenuBarImpQt final : public MenuBar { +public: + QMenuBar* menuBarQ; + std::vector> subMenus; + + MenuBarImpQt() { + // The QMenuBar is deleted by the QMainWindow its attached to. + menuBarQ = new QMenuBar; + menuBarQ->setNativeMenuBar(false); + } + + std::shared_ptr AddSubMenu(const std::string& label) override { + std::shared_ptr menu = std::make_shared(); + menuBarQ->addMenu(menu->menuQ); + menu->menuQ->setTitle(QString::fromStdString(label)); + subMenus.push_back(menu); + return menu; + } + + void Clear() override { + menuBarQ->clear(); + subMenus.clear(); + } + + static void _formActionGroups(QMenu *menu) { + QActionGroup* group = nullptr; + foreach (QAction* act, menu->actions()) { + if(act->menu()) { + _formActionGroups(act->menu()); + } else { + QVariant var = act->property("radio_mark"); + if(var.isValid()) { + if(! group) + group = new QActionGroup(menu); + //printf("radio_mark on menu %p\n", menu); + group->addAction(act); + } else { + group = nullptr; // End current group. + } + } + } + } + + void formActionGroups() { + foreach (QAction* act, menuBarQ->actions()) { + if (act->menu()) { + _formActionGroups(act->menu()); + } + } + } +}; + +MenuBarRef GetOrCreateMainMenu(bool* unique) { + *unique = false; + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// Windows +//----------------------------------------------------------------------------- + +class SSApplication : public QApplication { +public: + SSApplication(int &argc, char **argv) : QApplication(argc, argv) + { + // Setup fonts once for SSView QLineEdit. +#ifdef _WIN32 + fontEdit = QFont("Arial"); + fontEditMono = QFont("Lucida Console"); +#else + fontEdit = QFontDatabase::systemFont(QFontDatabase::GeneralFont); + fontEditMono = QFontDatabase::systemFont(QFontDatabase::FixedFont); + /* + printf("Font general %s\n", fontEdit.family().toLocal8Bit().data()); + printf("Font mono %s\n", fontEditMono.family().toLocal8Bit().data()); + */ +#endif + + // Set language locale. + std::string localeStr("en_US"); + QStringList list = QLocale::system().uiLanguages(); + if (! list.empty()) { + QString lang = list[0]; + if (lang.size() == 5 && lang[2] == '-') { + lang[2] = '_'; + localeStr = lang.toStdString(); + } + } + SetLocale(localeStr); + setlocale(LC_NUMERIC, "C"); + +#ifdef __linux + QIcon icon("/usr/share/icons/hicolor/48x48/apps/solvespace.png"); + setWindowIcon(icon); +#endif + } + + QFont fontEdit; + QFont fontEditMono; +}; + +SSView::SSView(QWidget* parent) : QOpenGLWidget(parent) { + entry = new QLineEdit(this); + entry->setVisible(false); + connect(entry, SIGNAL(returnPressed()), SLOT(entryFinished())); + + setFocusPolicy(Qt::FocusPolicy::StrongFocus); + setMouseTracking(true); + pixelRatio = pixelRatioI = 1.0; +} + +void SSView::startEditing(double xd, double yd, int fontHeight, int minWidth, + bool isMonoSpace, const std::string& val) +{ + int x = int(xd / pixelRatio); + int y = int(yd / pixelRatio); + //printf("startEditing %d,%d, %d,%d %d\n", x, y, fontHeight, minWidth, isMonoSpace); + + SSApplication* app = static_cast(qApp); + QFont font = isMonoSpace ? app->fontEditMono : app->fontEdit; + font.setPixelSize(fontHeight); + entry->setFont(font); + + QFontMetrics fm(font); + int valWidth = fm.averageCharWidth() * val.size(); + if (minWidth < valWidth) + minWidth = valWidth; + if (x < 0) + x = 0; + + entry->setGeometry(QRect(x, y - fm.ascent(), minWidth, fm.height())); + entry->setText(QString::fromStdString(val)); + + if (! entry->isVisible()) { + entry->selectAll(); + entry->show(); + entry->setFocus(); + } +} + +void SSView::stopEditing() { + if (entry->isVisible()) { + entry->hide(); + setFocus(); + } +} + +void SSView::entryFinished() { + std::string entryText = entry->text().toStdString(); + receiver->onEditingDone(entryText); +} + +void SSView::paintGL() { + // pixelRatio is set here as devicePixelRatioF has been seen to change + // after the resizeGL() & showEvent() methods are called. + pixelRatio = devicePixelRatioF(); + pixelRatioI = floor(pixelRatio); + + receiver->onRender(); +} + +static inline void setMouseKeyModifiers(MouseEvent& me, const QInputEvent* qe) +{ + Qt::KeyboardModifiers mod = qe->modifiers(); + me.shiftDown = mod & Qt::ShiftModifier ? true : false; + me.controlDown = mod & Qt::ControlModifier ? true : false; +} + +void SSView::wheelEvent(QWheelEvent* event) +{ + const double wheelDelta=120.0; + slvMouseEvent.button = MouseEvent::Button::NONE; + slvMouseEvent.type = MouseEvent::Type::SCROLL_VERT; + slvMouseEvent.scrollDelta = (double)event->angleDelta().y() / wheelDelta; + setMouseKeyModifiers(slvMouseEvent, event); + + receiver->onMouseEvent(slvMouseEvent); +} + +void SSView::updateSlvSpaceMouseEvent(QMouseEvent* event) +{ + switch (event->type()) + { + case QEvent::MouseMove: + slvMouseEvent.type = MouseEvent::Type::MOTION; + break; + case QEvent::MouseButtonPress: + slvMouseEvent.type = MouseEvent::Type::PRESS; + break; + case QEvent::MouseButtonDblClick: + slvMouseEvent.type = MouseEvent::Type::DBL_PRESS; + break; + case QEvent::MouseButtonRelease: + slvMouseEvent.type = MouseEvent::Type::RELEASE; + slvMouseEvent.button = MouseEvent::Button::NONE; + break; + case QEvent::Wheel: + slvMouseEvent.type = MouseEvent::Type::SCROLL_VERT; + //slvMouseEvent.scrollDelta = event-> + break; + case QEvent::Leave: + slvMouseEvent.type = MouseEvent::Type::LEAVE; + slvMouseEvent.button = MouseEvent::Button::NONE; + break; + default: + slvMouseEvent.type = MouseEvent::Type::RELEASE; + slvMouseEvent.button = MouseEvent::Button::NONE; + } + + Qt::MouseButtons buttonState = (slvMouseEvent.type == MouseEvent::Type::MOTION) ? event->buttons() : event->button(); + + switch (buttonState) + { + case Qt::MouseButton::LeftButton: + slvMouseEvent.button = MouseEvent::Button::LEFT; + break; + case Qt::MouseButton::RightButton: + slvMouseEvent.button = MouseEvent::Button::RIGHT; + break; + case Qt::MouseButton::MiddleButton: + slvMouseEvent.button = MouseEvent::Button::MIDDLE; + break; + default: + slvMouseEvent.button = MouseEvent::Button::NONE; + //slvMouseEvent.button = slvMouseEvent.button; + } + +#if QT_VERSION >= 0x060000 + QPointF pos = event->position(); + slvMouseEvent.x = pos.x() * pixelRatio; + slvMouseEvent.y = pos.y() * pixelRatio; +#else + slvMouseEvent.x = double(event->x()); + slvMouseEvent.y = double(event->y()); +#endif + + setMouseKeyModifiers(slvMouseEvent, event); + + receiver->onMouseEvent(slvMouseEvent); +} + +void SSView::updateSlvSpaceKeyEvent(QKeyEvent* event) +{ + slvKeyEvent.key = KeyboardEvent::Key(-1); + + Qt::KeyboardModifiers mod = event->modifiers(); + slvKeyEvent.shiftDown = mod & Qt::ShiftModifier ? true : false; + slvKeyEvent.controlDown = mod & Qt::ControlModifier ? true : false; + + if (event->key() >= Qt::Key_F1 && event->key() <= Qt::Key_F35) { + slvKeyEvent.key = KeyboardEvent::Key::FUNCTION; + slvKeyEvent.num = event->key() - Qt::Key_F1 + 1; + } + if (event->key() >= Qt::Key_Space && event->key() <= Qt::Key_yen) { + slvKeyEvent.key = KeyboardEvent::Key::CHARACTER; + QString keyLower = event->text().toLower(); + slvKeyEvent.chr = keyLower.toUcs4().at(0); + } + + receiver->onKeyboardEvent(slvKeyEvent); +} + +SSTextWindow::SSTextWindow(QWidget* parent) + : QDockWidget(parent) +{ + ssView = new SSView; + + QWidget* group = new QWidget; + scrollBar = new QScrollBar(Qt::Vertical, group); + connect(scrollBar, SIGNAL(valueChanged(int)), SLOT(sliderSlot(int))); + + QHBoxLayout* lo = new QHBoxLayout(group); + lo->setContentsMargins(0, 0, 0, 0); + lo->setSpacing(2); + lo->addWidget(ssView); + lo->addWidget(scrollBar); + group->setLayout(lo); + + setWidget(group); + setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); +} + +void SSTextWindow::sliderSlot(int value) +{ + std::function adjust = ssView->receiver->onScrollbarAdjusted; + if(adjust) + adjust(double(value)); +} + +void SSTextWindow::closeEvent(QCloseEvent* ev) +{ + ssView->receiver->onClose(); + ev->ignore(); +} + +SSMainWindow::SSMainWindow() +{ + ssView = new SSView; + setCentralWidget(ssView); +} + +void SSMainWindow::closeEvent(QCloseEvent* ev) +{ + ssView->receiver->onClose(); + ev->ignore(); +} + +class WindowImplQt final : public Window { +public: + QWidget* ssWindow; // SSMainWindow or SSTextWindow. + SSView* view; // Copy of ssWindow ssView. + + // NOTE: Only TextWindow has a scroll bar and only GraphicsWindow has menus, + // but Platform::Window squishes both together in a single interface. + + QScrollBar* scrollBar; // Copy of SSTextWindow::scrollBar or nullptr. + std::shared_ptr menuBar; + + WindowImplQt(Window::Kind kind, std::shared_ptr parent) + { + if(kind == Platform::Window::Kind::TOOL) { + QWidget* qparent = parent ? parent->ssWindow : nullptr; + + SSTextWindow* twin = new SSTextWindow(qparent); + ssWindow = twin; + scrollBar = twin->scrollBar; + view = twin->ssView; + + // Set object name for saveState settings. + twin->setObjectName("TextWindow"); + + QMainWindow* mwin = qobject_cast(qparent); + if(mwin) { + mwin->addDockWidget(Qt::RightDockWidgetArea, twin); + } + } else { + SSMainWindow* mwin = new SSMainWindow; + ssWindow = mwin; + scrollBar = nullptr; + view = mwin->ssView; + } + + // Have the view call our Platform::Window event methods. + view->receiver = this; + } + + // Returns physical display DPI. + double GetPixelDensity() override { + return QGuiApplication::primaryScreen()->logicalDotsPerInch(); + } + + // 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. + double GetDevicePixelRatio() override { + return view->pixelRatioI; + } + + // Returns (fractional) font scale, to be applied on top of (integral) device pixel ratio. + double GetDeviceFontScale() { + return GetPixelDensity() / GetDevicePixelRatio() / 96.0; + } + + bool IsVisible() override { + return ssWindow->isVisible(); + } + + void SetVisible(bool visible) override { + ssWindow->setVisible(visible); + ssWindow->raise(); + } + + void Focus() override { + ssWindow->setFocus(); + } + + bool IsFullScreen() override { + return ssWindow->isFullScreen(); + } + + void SetFullScreen(bool fullScreen) override { + // Qt::WindowNoState is "normal". + ssWindow->setWindowState(fullScreen ? Qt::WindowFullScreen + : Qt::WindowNoState); + } + + void SetTitle(const std::string& title) override { + ssWindow->setWindowTitle(QString::fromStdString(title)); + } + + void SetMenuBar(MenuBarRef menus) override { + QMainWindow* mwin = qobject_cast(ssWindow); + if (mwin) { + // We must take ownership of the new menus. + menuBar = std::static_pointer_cast(menus); + menuBar->formActionGroups(); + + mwin->setMenuBar(menuBar->menuBarQ); + } + } + + void GetContentSize(double* width, double* height) override { +#if QT_VERSION >= 0x060000 + double pixelRatio = view->pixelRatio; + QSize rc = view->size(); + *width = rc.width() * pixelRatio; + *height = rc.height() * pixelRatio; + //printf("KR cont %f %d,%d %f,%f\n", pixelRatio, rc.width(), rc.height(), *width, *height); +#else + *width = view->width(); + *height = view->height(); + //printf("KR cont %f,%f\n", *width, *height); +#endif + } + + void SetMinContentSize(double width, double height) override { + ssWindow->resize(int(width), int(height)); + } + + void FreezePosition(SettingsRef settings, const std::string& key) override { + // Geometry is only saved for the main window; the TextWindow dock + // widget is part of the saveState(). + QMainWindow* mwin = qobject_cast(ssWindow); + if (mwin) { + QSettings& qset = std::static_pointer_cast(settings)->qset; + QString qkey = QString::fromStdString(key); + qset.setValue(qkey + "_Geometry", mwin->saveGeometry()); + qset.setValue(qkey + "_State", mwin->saveState()); + } + } + + void ThawPosition(SettingsRef settings, const std::string& key) override { + QMainWindow* mwin = qobject_cast(ssWindow); + if (mwin) { + QSettings& qset = std::static_pointer_cast(settings)->qset; + QString qkey = QString::fromStdString(key); + mwin->restoreGeometry(qset.value(qkey + "_Geometry").toByteArray()); + mwin->restoreState(qset.value(qkey + "_State").toByteArray()); + } + } + + void SetCursor(Cursor cursor) override { + + switch (cursor) + { + case Cursor::POINTER: + ssWindow->setCursor(Qt::ArrowCursor); + break; + case Cursor::HAND: + ssWindow->setCursor(Qt::PointingHandCursor); + break; + default: + ssWindow->setCursor(Qt::ArrowCursor); + } + } + + void SetTooltip(const std::string& text, double x, double y, + double width, double height) override { + view->setToolTip(QString::fromStdString(text)); + } + + bool IsEditorVisible() override { + return view->entry->isVisible(); + } + + void ShowEditor(double x, double y, double fontHeight, double minWidth, + bool isMonospace, const std::string& text) override { + view->startEditing(x, y, fontHeight, minWidth, isMonospace, text); + } + + void HideEditor() override { + view->stopEditing(); + } + + void SetScrollbarVisible(bool visible) override { + //printf("Scroll vis %d\n", int(visible)); + if (scrollBar) + scrollBar->setVisible(visible); + } + + void ConfigureScrollbar(double min, double max, double pageSize) override { + //printf("Scroll bar %f %f %f\n", min, max, pageSize); + if (scrollBar) { + scrollBar->setRange(int(min), int(max - pageSize)); + scrollBar->setPageStep(int(pageSize)); + } + } + + double GetScrollbarPosition() override { + if (scrollBar) + return (double) scrollBar->value(); + return 0.0; + } + + void SetScrollbarPosition(double pos) override { + //printf("Scroll pos %f\n", pos); + if (scrollBar) + scrollBar->setValue((int) pos); + } + + void Invalidate() override { + view->update(); + } +}; + +WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { + return std::make_shared(kind, + std::static_pointer_cast(parentWindow)); +} + +MessageDialogRef CreateMessageDialog(WindowRef parentWindow) { + return std::make_shared( + std::static_pointer_cast(parentWindow)->ssWindow); +} + +FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) { + return std::make_shared( + std::static_pointer_cast(parentWindow)->ssWindow, + false); +} + +FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) { + return std::make_shared( + std::static_pointer_cast(parentWindow)->ssWindow, + true); +} + +//----------------------------------------------------------------------------- +// 3DConnexion support +//----------------------------------------------------------------------------- + +void Open3DConnexion() {} +void Close3DConnexion() {} +void Request3DConnexionEventsForWindow(WindowRef window) {} + +//----------------------------------------------------------------------------- +// Application-wide APIs +//----------------------------------------------------------------------------- + +std::vector GetFontFiles() { +#if WIN32 + std::vector fonts; + + std::wstring fontsDirW(MAX_PATH, '\0'); + fontsDirW.resize(GetWindowsDirectoryW(&fontsDirW[0], fontsDirW.length())); + fontsDirW += L"\\fonts\\"; + Platform::Path fontsDir = Platform::Path::From(SolveSpace::Platform::Narrow(fontsDirW)); + + WIN32_FIND_DATAW wfd; + HANDLE h = FindFirstFileW((fontsDirW + L"*").c_str(), &wfd); + while (h != INVALID_HANDLE_VALUE) { + fonts.push_back(fontsDir.Join(SolveSpace::Platform::Narrow(wfd.cFileName))); + if (!FindNextFileW(h, &wfd)) break; + } + + return fonts; +#endif +#if UNIX + std::vector fonts; + + // fontconfig is already initialized by GTK + FcPattern* pat = FcPatternCreate(); + FcObjectSet* os = FcObjectSetBuild(FC_FILE, (char*)0); + FcFontSet* fs = FcFontList(0, pat, os); + + for (int i = 0; i < fs->nfont; i++) { + FcChar8* filenameFC = FcPatternFormat(fs->fonts[i], (const FcChar8*)"%{file}"); + fonts.push_back(Platform::Path::From((const char*)filenameFC)); + FcStrFree(filenameFC); + } + + FcFontSetDestroy(fs); + FcObjectSetDestroy(os); + FcPatternDestroy(pat); + + return fonts; + +#endif + + return std::vector(); +} + +void OpenInBrowser(const std::string& url) { + + std::string urlTemp = url; + + if (urlTemp.find("http") == std::string::npos) + { + urlTemp = QApplication::applicationDirPath().toStdString(); + urlTemp += "/" + url; + } + + if (false == QDesktopServices::openUrl(QUrl(urlTemp.c_str(), QUrl::TolerantMode))) + { + std::string errorStr = "Error: unable to open URL path : " + urlTemp; + QMessageBox::critical(0,QString("Manual Open Error"),QString::fromStdString(errorStr),QMessageBox::Ok); + } +} + +void ExitGui() { + // Using exit() rather than quit() to avoid calling window closeEvent() methods. + qApp->exit(); +} + +} +} + +using namespace SolveSpace; + +int main(int argc, char** argv) { + // Must share GL contexts or an undocked QDockWidget is black. + // See https://bugreports.qt.io/browse/QTBUG-89812 + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); + + // Specify the GL version so Qt doesn't use GLES on Wayland, which + // doesn't handle "#version 120" shaders. + { + QSurfaceFormat fmt; + fmt.setRenderableType(QSurfaceFormat::OpenGL); + fmt.setVersion(3, 2); + fmt.setProfile(QSurfaceFormat::CoreProfile); + QSurfaceFormat::setDefaultFormat(fmt); + } + + Platform::SSApplication app(argc, argv); + Platform::Open3DConnexion(); + SS.Init(); + + if(argc >= 2) { + if(argc > 2) { + dbp("Only the first file passed on command line will be opened."); + } + SS.Load(Platform::Path::From(argv[1])); + } + + app.exec(); + + Platform::Close3DConnexion(); + SS.Clear(); + SK.Clear(); + return 0; +} diff --git a/src/platform/guiqt.h b/src/platform/guiqt.h new file mode 100644 index 000000000..b34d88362 --- /dev/null +++ b/src/platform/guiqt.h @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------------- +// The Qt-based implementation of platform-dependent GUI functionality. +// +// Copyright 2024 Karl Robillard +// Copyright 2023 Raed Marwan +// SPDX-License-Identifier: GPL-3.0-or-later +//----------------------------------------------------------------------------- + +#ifndef SOLVESPACE_GUIQT_H +#define SOLVESPACE_GUIQT_H + +#include + +#include +#include +#include +#include +#include +#include + + +namespace SolveSpace { +namespace Platform { + +class SSView : public QOpenGLWidget +{ + Q_OBJECT + +public: + SSView(QWidget* parent = 0); + + void startEditing(double xd, double yd, int fontHeight, int minWidth, + bool isMonoSpace, const std::string& val); + void stopEditing(); + + QSize minimumSizeHint() const { + return QSize(50, 50); + } + + QSize sizeHint() const { + return QSize(400, 400); + } + +protected slots: + void entryFinished(); + +protected: + //void initializeGL() {} + //void resizeGL(int width, int height) {} + + void paintGL(); + + //----Mouse Events-------- + void wheelEvent(QWheelEvent* event); + + void mouseReleaseEvent(QMouseEvent* event) override{ + updateSlvSpaceMouseEvent(event); + } + + void mousePressEvent(QMouseEvent* event) override{ + updateSlvSpaceMouseEvent(event); + } + + void mouseMoveEvent(QMouseEvent* event) override{ + updateSlvSpaceMouseEvent(event); + } + + void updateSlvSpaceMouseEvent(QMouseEvent* event); + + //----Keyboard Events-------- + + void keyPressEvent(QKeyEvent* keyEvent) + { + slvKeyEvent.type = KeyboardEvent::Type::PRESS; + updateSlvSpaceKeyEvent(keyEvent); + QWidget::keyPressEvent(keyEvent); + } + + void keyReleaseEvent(QKeyEvent* keyEvent) + { + slvKeyEvent.type = KeyboardEvent::Type::RELEASE; + updateSlvSpaceKeyEvent(keyEvent); + QWidget::keyReleaseEvent(keyEvent); + } + + void updateSlvSpaceKeyEvent(QKeyEvent* event); + +public: + Platform::Window* receiver; + QLineEdit* entry; + double pixelRatio; + double pixelRatioI; + MouseEvent slvMouseEvent; + KeyboardEvent slvKeyEvent; +}; + +class SSTextWindow : public QDockWidget +{ + Q_OBJECT + +public: + SSTextWindow(QWidget* parent = 0); + +protected slots: + void sliderSlot(int value); + +protected: + void closeEvent(QCloseEvent* ev); + +public: + SSView* ssView; + QScrollBar* scrollBar; +}; + +class SSMainWindow : public QMainWindow +{ + Q_OBJECT + +public: + SSMainWindow(); + +protected: + void closeEvent(QCloseEvent* ev); + +public: + SSView* ssView; +}; + +} +} + +#endif diff --git a/src/platform/guiwin.cpp b/src/platform/guiwin.cpp index e98e87388..4ddfdc370 100644 --- a/src/platform/guiwin.cpp +++ b/src/platform/guiwin.cpp @@ -793,7 +793,7 @@ class WindowImplWin32 final : public Window { break; case WM_SIZING: { - int pixelRatio = window->GetDevicePixelRatio(); + double pixelRatio = window->GetDevicePixelRatio(); RECT rcw, rcc; sscheck(GetWindowRect(window->hWindow, &rcw)); @@ -806,10 +806,10 @@ class WindowImplWin32 final : public Window { int adjHeight = rc->bottom - rc->top; adjWidth -= nonClientWidth; - adjWidth = max(window->minWidth * pixelRatio, adjWidth); - adjWidth += nonClientWidth; + adjWidth = max((int)(window->minWidth * pixelRatio), adjWidth); + adjWidth += nonClientWidth; adjHeight -= nonClientHeight; - adjHeight = max(window->minHeight * pixelRatio, adjHeight); + adjHeight = max((int)(window->minHeight * pixelRatio), adjHeight); adjHeight += nonClientHeight; switch(wParam) { case WMSZ_RIGHT: @@ -868,7 +868,7 @@ class WindowImplWin32 final : public Window { case WM_MOUSEMOVE: case WM_MOUSEWHEEL: case WM_MOUSELEAVE: { - int pixelRatio = window->GetDevicePixelRatio(); + double pixelRatio = window->GetDevicePixelRatio(); MouseEvent event = {}; event.x = GET_X_LPARAM(lParam) / pixelRatio; @@ -941,7 +941,7 @@ class WindowImplWin32 final : public Window { event.y = pt.y / pixelRatio; 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; case WM_MOUSELEAVE: @@ -1109,10 +1109,10 @@ class WindowImplWin32 final : public Window { return (double)dpi; } - int GetDevicePixelRatio() override { + double GetDevicePixelRatio() override { UINT dpi; sscheck(dpi = ssGetDpiForWindow(hWindow)); - return dpi / USER_DEFAULT_SCREEN_DPI; + return (double)dpi / USER_DEFAULT_SCREEN_DPI; } bool IsVisible() override { @@ -1146,7 +1146,7 @@ class WindowImplWin32 final : public Window { sscheck(GetMonitorInfo(MonitorFromWindow(hWindow, MONITOR_DEFAULTTONEAREST), &mi)); sscheck(SetWindowLong(hWindow, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW)); - sscheck(SetWindowPos(hWindow, HWND_TOP, + sscheck(SetWindowPos(hWindow, HWND_NOTOPMOST, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top, @@ -1177,27 +1177,27 @@ class WindowImplWin32 final : public Window { } void GetContentSize(double *width, double *height) override { - int pixelRatio = GetDevicePixelRatio(); + double pixelRatio = GetDevicePixelRatio(); RECT rc; sscheck(GetClientRect(hWindow, &rc)); - *width = (rc.right - rc.left) / pixelRatio; - *height = (rc.bottom - rc.top) / pixelRatio; + *width = (rc.right - rc.left) / pixelRatio; + *height = (rc.bottom - rc.top) / pixelRatio; } - void SetMinContentSize(double width, double height) { + void SetMinContentSize(double width, double height) override { minWidth = (int)width; minHeight = (int)height; - int pixelRatio = GetDevicePixelRatio(); + double pixelRatio = GetDevicePixelRatio(); RECT rc; sscheck(GetClientRect(hWindow, &rc)); 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) { - rc.bottom = rc.top + minHeight * pixelRatio; + rc.bottom = rc.top + (LONG)(minHeight * pixelRatio); } } @@ -1229,7 +1229,7 @@ class WindowImplWin32 final : public Window { sscheck(GetMonitorInfo(MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST), &mi)); // If it somehow ended up off-screen, then put it back. - // and make it visible by at least this portion of the scrren + // and make it visible by at least this portion of the screen const LONG movein = 40; RECT mrc = mi.rcMonitor; @@ -1270,7 +1270,7 @@ class WindowImplWin32 final : public Window { tooltipText = newText; if(!newText.empty()) { - int pixelRatio = GetDevicePixelRatio(); + double pixelRatio = GetDevicePixelRatio(); RECT toolRect; toolRect.left = (int)(x * pixelRatio); toolRect.top = (int)(y * pixelRatio); @@ -1301,9 +1301,9 @@ class WindowImplWin32 final : public Window { bool isMonospace, const std::string &text) override { 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, DEFAULT_QUALITY, FF_DONTCARE, isMonospace ? L"Lucida Console" : L"Arial"); if(hFont == NULL) { @@ -1324,12 +1324,12 @@ class WindowImplWin32 final : public Window { sscheck(ReleaseDC(hEditor, hDc)); RECT rc; - rc.left = (LONG)x * pixelRatio; - rc.top = (LONG)y * pixelRatio - tm.tmAscent; + rc.left = (LONG)(x * pixelRatio); + rc.top = (LONG)(y * pixelRatio) - tm.tmAscent; // Add one extra char width to avoid scrolling. - rc.right = (LONG)x * pixelRatio + - std::max((LONG)minWidth * pixelRatio, ts.cx + tm.tmAveCharWidth); - rc.bottom = (LONG)y * pixelRatio + tm.tmDescent; + rc.right = (LONG)(x * pixelRatio) + + std::max((LONG)(minWidth * pixelRatio), ts.cx + tm.tmAveCharWidth); + rc.bottom = (LONG)(y * pixelRatio) + tm.tmDescent; sscheck(ssAdjustWindowRectExForDpi(&rc, 0, /*bMenu=*/FALSE, WS_EX_CLIENTEDGE, ssGetDpiForWindow(hWindow))); @@ -1515,6 +1515,7 @@ class MessageDialogImplWin32 final : public MessageDialog { void AddButton(std::string _label, Response response, bool isDefault) override { int button; switch(response) { + default: case Response::NONE: ssassert(false, "Invalid response"); case Response::OK: button = IDOK; break; case Response::YES: button = IDYES; break; @@ -1608,7 +1609,7 @@ class FileDialogImplWin32 final : public FileDialog { void AddFilter(std::string name, std::vector extensions) override { std::string desc, patterns; - for(auto extension : extensions) { + for(auto &extension : extensions) { std::string pattern = "*." + extension; if(!desc.empty()) desc += ", "; desc += pattern; diff --git a/src/platform/html/emshell.html b/src/platform/html/emshell.html new file mode 100644 index 000000000..951d588e8 --- /dev/null +++ b/src/platform/html/emshell.html @@ -0,0 +1,91 @@ + +SolveSpace Web Edition (EXPERIMENTAL)
+
+
+
Downloading...
+ + +
+
{{{ SCRIPT }}} diff --git a/src/platform/html/filemanagerui.js b/src/platform/html/filemanagerui.js new file mode 100644 index 000000000..c48e1a582 --- /dev/null +++ b/src/platform/html/filemanagerui.js @@ -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 pseudo 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 pseudo 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; diff --git a/src/platform/html/solvespaceui.css b/src/platform/html/solvespaceui.css new file mode 100644 index 000000000..3d2f44bb1 --- /dev/null +++ b/src/platform/html/solvespaceui.css @@ -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%); +} diff --git a/src/platform/html/solvespaceui.js b/src/platform/html/solvespaceui.js new file mode 100644 index 000000000..80af24594 --- /dev/null +++ b/src/platform/html/solvespaceui.js @@ -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 implemented 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 { + 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 implemented 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; diff --git a/src/platform/platform.cpp b/src/platform/platform.cpp index 008541319..a57ff5ec3 100644 --- a/src/platform/platform.cpp +++ b/src/platform/platform.cpp @@ -9,9 +9,13 @@ # include # include #endif -#include "solvespace.h" -#include "mimalloc.h" -#include "config.h" + +#include +#include +#include + +#include + #if defined(WIN32) // Conversely, include Microsoft headers after solvespace.h to avoid clashes. # include @@ -21,6 +25,10 @@ # include #endif +#include "platform.h" +#include "util.h" +#include "config.h" + namespace SolveSpace { namespace Platform { @@ -93,9 +101,7 @@ static std::vector Split(const std::string &joined, char separator) pos += 1; } - if(oldpos != joined.length() - 1) { - parts.push_back(joined.substr(oldpos)); - } + parts.push_back(joined.substr(oldpos)); return parts; } @@ -239,7 +245,8 @@ Path Path::Parent() const { } // Concatenates a component to this path. -// Returns an empty path if this path or the component is empty. +// Returns a relative path if this path is empty. +// Returns an empty path if the component is absolute. Path Path::Join(const std::string &component) const { ssassert(component.find(SEPARATOR) == std::string::npos, "Use the Path::Join(const Path &) overload to append an entire path"); @@ -247,13 +254,20 @@ Path Path::Join(const std::string &component) const { } // Concatenates a relative path to this path. -// Returns an empty path if either path is empty, or the other path is absolute. +// Returns a relative path if this path is empty. +// Returns an empty path if the other path is absolute. Path Path::Join(const Path &other) const { - if(IsEmpty() || other.IsEmpty() || other.IsAbsolute()) { + if(other.IsAbsolute()) { return From(""); } - Path joined = { raw }; + Path joined; + if(IsEmpty()) { + joined.raw = "."; + } else { + joined.raw = raw; + } + if(joined.raw.back() != SEPARATOR) { joined.raw += SEPARATOR; } @@ -516,6 +530,12 @@ static Platform::Path ResourcePath(const std::string &name) { return path; } +#elif defined(__EMSCRIPTEN__) + +static Platform::Path ResourcePath(const std::string &name) { + return Path::From("res/" + name); +} + #elif !defined(WIN32) # if defined(__linux__) @@ -633,83 +653,5 @@ std::vector InitCli(int argc, char **argv) { #endif -//----------------------------------------------------------------------------- -// Debug output, on Windows. -//----------------------------------------------------------------------------- - -#if defined(WIN32) - -void DebugPrint(const char *fmt, ...) -{ - va_list va; - va_start(va, fmt); - int len = _vscprintf(fmt, va) + 1; - va_end(va); - - va_start(va, fmt); - char *buf = (char *)_alloca(len); - _vsnprintf(buf, len, fmt, va); - va_end(va); - - // The native version of OutputDebugString, unlike most others, - // is OutputDebugStringA. - OutputDebugStringA(buf); - OutputDebugStringA("\n"); - -#ifndef NDEBUG - // Duplicate to stderr in debug builds, but not in release; this is slow. - fputs(buf, stderr); - fputc('\n', stderr); -#endif -} - -#endif - -//----------------------------------------------------------------------------- -// Debug output, on *nix. -//----------------------------------------------------------------------------- - -#if !defined(WIN32) - -void DebugPrint(const char *fmt, ...) { - va_list va; - va_start(va, fmt); - vfprintf(stderr, fmt, va); - fputc('\n', stderr); - va_end(va); -} - -#endif - -//----------------------------------------------------------------------------- -// Temporary arena. -//----------------------------------------------------------------------------- - -struct MimallocHeap { - mi_heap_t *heap = NULL; - - ~MimallocHeap() { - if(heap != NULL) - mi_heap_destroy(heap); - } -}; - -static thread_local MimallocHeap TempArena; - -void *AllocTemporary(size_t size) { - if(TempArena.heap == NULL) { - TempArena.heap = mi_heap_new(); - ssassert(TempArena.heap != NULL, "out of memory"); - } - void *ptr = mi_heap_zalloc(TempArena.heap, size); - ssassert(ptr != NULL, "out of memory"); - return ptr; -} - -void FreeAllTemporary() { - MimallocHeap temp; - std::swap(TempArena.heap, temp.heap); -} - } } diff --git a/src/platform/platform.h b/src/platform/platform.h index 21c2b2bfa..771a4cf0b 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -7,6 +7,10 @@ #ifndef SOLVESPACE_PLATFORM_H #define SOLVESPACE_PLATFORM_H +#include +#include + +namespace SolveSpace { namespace Platform { // UTF-8 ⟷ UTF-16 conversion, for Windows. @@ -73,13 +77,11 @@ const void *LoadResource(const std::string &name, size_t *size); // Startup and command-line argument handling. std::vector InitCli(int argc, char **argv); -// Debug print function. -void DebugPrint(const char *fmt, ...); - // Temporary arena functions. void *AllocTemporary(size_t size); void FreeAllTemporary(); -} +} // namespace Platform +} // namespace SolveSpace #endif diff --git a/src/platform/platformbase.cpp b/src/platform/platformbase.cpp new file mode 100644 index 000000000..6e3bb8aa0 --- /dev/null +++ b/src/platform/platformbase.cpp @@ -0,0 +1,101 @@ +#include +#include + +#include + +#if defined(WIN32) +# include +#endif // defined(WIN32) + +#include "util.h" +#include "platform.h" + +namespace SolveSpace { +namespace Platform { + +//----------------------------------------------------------------------------- +// Debug output, on Windows. +//----------------------------------------------------------------------------- + +#if defined(WIN32) + +#if !defined(_alloca) +// Fix for compiling with MinGW.org GCC-6.3.0-1 +#define _alloca alloca +#include +#endif + +void DebugPrint(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + int len = _vscprintf(fmt, va) + 1; + va_end(va); + + va_start(va, fmt); + char *buf = (char *)_alloca(len); + _vsnprintf(buf, len, fmt, va); + va_end(va); + + // The native version of OutputDebugString, unlike most others, + // is OutputDebugStringA. + OutputDebugStringA(buf); + OutputDebugStringA("\n"); + +#ifndef NDEBUG + // Duplicate to stderr in debug builds, but not in release; this is slow. + fputs(buf, stderr); + fputc('\n', stderr); +#endif +} + +#endif + +//----------------------------------------------------------------------------- +// Debug output, on *nix. +//----------------------------------------------------------------------------- + +#if !defined(WIN32) + +void DebugPrint(const char *fmt, ...) { + va_list va; + va_start(va, fmt); + vfprintf(stderr, fmt, va); + fputc('\n', stderr); + va_end(va); +} + +#endif + +//----------------------------------------------------------------------------- +// Temporary arena. +//----------------------------------------------------------------------------- + +struct MimallocHeap { + mi_heap_t *heap = NULL; + + ~MimallocHeap() { + if(heap != NULL) + mi_heap_destroy(heap); + } +}; + +static thread_local MimallocHeap TempArena; + +void *AllocTemporary(size_t size) { + if(TempArena.heap == NULL) { + TempArena.heap = mi_heap_new(); + ssassert(TempArena.heap != NULL, "out of memory"); + } + void *ptr = mi_heap_zalloc(TempArena.heap, size); + ssassert(ptr != NULL, "out of memory"); + return ptr; +} + +void FreeAllTemporary() { + MimallocHeap temp; + std::swap(TempArena.heap, temp.heap); +} + +} +} diff --git a/src/polygon.cpp b/src/polygon.cpp index f325fd9b4..211f6a690 100644 --- a/src/polygon.cpp +++ b/src/polygon.cpp @@ -5,6 +5,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + Vector STriangle::Normal() const { Vector ab = b.Minus(a), bc = c.Minus(b); return ab.Cross(bc); @@ -353,12 +355,12 @@ void SEdgeList::CullExtraneousEdges(bool both) { // that would naively be O(n). //----------------------------------------------------------------------------- SKdNodeEdges *SKdNodeEdges::Alloc() { - SKdNodeEdges *ne = (SKdNodeEdges *)AllocTemporary(sizeof(SKdNodeEdges)); + SKdNodeEdges *ne = (SKdNodeEdges *)Platform::AllocTemporary(sizeof(SKdNodeEdges)); *ne = {}; return ne; } SEdgeLl *SEdgeLl::Alloc() { - SEdgeLl *sell = (SEdgeLl *)AllocTemporary(sizeof(SEdgeLl)); + SEdgeLl *sell = (SEdgeLl *)Platform::AllocTemporary(sizeof(SEdgeLl)); *sell = {}; return sell; } @@ -919,3 +921,4 @@ void SContour::OffsetInto(SContour *dest, double r) const { } } +} // namespace SolveSpace diff --git a/src/polygon.h b/src/polygon.h index cf0f32283..4668421b2 100644 --- a/src/polygon.h +++ b/src/polygon.h @@ -8,10 +8,21 @@ #ifndef SOLVESPACE_POLYGON_H #define SOLVESPACE_POLYGON_H +#include +#include +#include +#include + +#include "dsc.h" + +namespace SolveSpace { + +class Group; class SPointList; class SPolygon; class SContour; class SMesh; +class SSurface; class SBsp3; class SOutlineList; @@ -416,5 +427,6 @@ class PolylineBuilder { void GenerateOutlines(SOutlineList *sol); }; -#endif +} // namespace SolveSpace +#endif diff --git a/src/polyline.cpp b/src/polyline.cpp index 64aef4e98..0b7e2e955 100644 --- a/src/polyline.cpp +++ b/src/polyline.cpp @@ -6,6 +6,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + bool PolylineBuilder::Vertex::GetNext(uint32_t kind, Vertex **next, Edge **nextEdge) { auto it = std::find_if(edges.begin(), edges.end(), [&](const Edge *e) { return e->tag == 0 && e->kind == kind; @@ -241,3 +243,5 @@ void PolylineBuilder::GenerateOutlines(SOutlineList *sol) { Generate(startFunc, nextFunc, aloneFunc); } + +} // namespace SolveSpace diff --git a/src/render/gl3shader.cpp b/src/render/gl3shader.cpp index a30904685..7c4552e61 100644 --- a/src/render/gl3shader.cpp +++ b/src/render/gl3shader.cpp @@ -108,7 +108,7 @@ precision highp float; glShaderSource(shader, 1, glSource, glSize); glCompileShader(shader); - GLint infoLen; + GLint infoLen = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); if(infoLen > 1) { std::string infoStr(infoLen, '\0'); @@ -116,7 +116,7 @@ precision highp float; dbp(infoStr.c_str()); } - GLint compiled; + GLint compiled = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); if(!compiled) { dbp("Failed to compile shader:\n" @@ -145,7 +145,7 @@ void Shader::Init(const std::string &vertexRes, const std::string &fragmentRes, } glLinkProgram(program); - GLint infoLen; + GLint infoLen = 0; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen); if(infoLen > 1) { std::string infoStr(infoLen, '\0'); @@ -153,7 +153,7 @@ void Shader::Init(const std::string &vertexRes, const std::string &fragmentRes, dbp(infoStr.c_str()); } - GLint linked; + GLint linked = 0; glGetProgramiv(program, GL_LINK_STATUS, &linked); ssassert(linked, "Cannot link shader"); } @@ -386,7 +386,7 @@ GLuint Generate(const std::vector &pattern) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - GLint size; + GLint size = 0; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &size); size /= 2; diff --git a/src/render/gl3shader.h b/src/render/gl3shader.h index 1831e7925..19f73128b 100644 --- a/src/render/gl3shader.h +++ b/src/render/gl3shader.h @@ -6,7 +6,7 @@ #ifndef SOLVESPACE_GL3SHADER_H #define SOLVESPACE_GL3SHADER_H -#if defined(WIN32) +#if defined(WIN32) || defined(__EMSCRIPTEN__) # define GL_APICALL /*static linkage*/ # define GL_GLEXT_PROTOTYPES # include @@ -20,6 +20,11 @@ # include #endif +#include +#include + +#include "sketch.h" + #if !defined(HAVE_GLES) // glDepthRange is in GL1+ but not GLES2, glDepthRangef is in GL4.1+ and GLES2. // Consistency! diff --git a/src/render/render.h b/src/render/render.h index 9601a5e7d..6a4ef62a8 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -7,10 +7,28 @@ #ifndef SOLVESPACE_RENDER_H #define SOLVESPACE_RENDER_H +#include +#include +#include +#include +#include +#include + +#include "dsc.h" +#include "resource.h" +#include "srf/surface.h" + +typedef struct _cairo cairo_t; +typedef struct _cairo_surface cairo_surface_t; + +namespace SolveSpace { + //----------------------------------------------------------------------------- // Interfaces common for all renderers //----------------------------------------------------------------------------- +class Pixmap; + enum class StipplePattern : uint32_t; // A mapping from 3d sketch coordinates to 2d screen coordinates, using @@ -265,6 +283,9 @@ class ObjectPicker : public Canvas { bool Pick(const std::function &drawFn); }; +template +using handle_map = std::map; + // A canvas that renders onto a 2d surface, performing z-index sorting, occlusion testing, etc, // on the CPU. class SurfaceRenderer : public ViewportCanvas { @@ -382,4 +403,6 @@ class CairoPixmapRenderer final : public CairoRenderer { std::shared_ptr CreateRenderer(); +} // namespace SolveSpace + #endif diff --git a/src/render/render2d.cpp b/src/render/render2d.cpp index fd948e805..732e3ca33 100644 --- a/src/render/render2d.cpp +++ b/src/render/render2d.cpp @@ -354,7 +354,7 @@ void SurfaceRenderer::OutputInPaintOrder() { mp.Clear(); } - for(auto eit : edges) { + for(const auto &eit : edges) { hStroke hcs = eit.first; const SEdgeList &el = eit.second; diff --git a/src/render/rendergl1.cpp b/src/render/rendergl1.cpp index ef34bc527..a915c2ba0 100644 --- a/src/render/rendergl1.cpp +++ b/src/render/rendergl1.cpp @@ -557,34 +557,39 @@ void OpenGl1Renderer::DrawPoint(const Vector &o, Canvas::hStroke hcs) { #endif typedef void(SSGL_CALLBACK *GLUCallback)(); +using CombineVec = std::vector>; + static void SSGL_CALLBACK Vertex(Vector *p) { ssglVertex3v(*p); } static void SSGL_CALLBACK Combine(double coords[3], void *vertexData[4], - float weight[4], void **outData) { - Vector *n = (Vector *)AllocTemporary(sizeof(Vector)); + float weight[4], void **outData, void *inData) { + Vector *n = new Vector; n->x = coords[0]; n->y = coords[1]; n->z = coords[2]; *outData = n; + CombineVec *vec = static_cast(inData); + vec->emplace_back(n); } void OpenGl1Renderer::DrawPolygon(const SPolygon &p, hFill hcf) { UnSelectPrimitive(); SelectFill(hcf); GLUtesselator *gt = gluNewTess(); - gluTessCallback(gt, GLU_TESS_BEGIN, (GLUCallback) glBegin); - gluTessCallback(gt, GLU_TESS_VERTEX, (GLUCallback) Vertex); - gluTessCallback(gt, GLU_TESS_END, (GLUCallback) glEnd); - gluTessCallback(gt, GLU_TESS_COMBINE, (GLUCallback) Combine); + gluTessCallback(gt, GLU_TESS_BEGIN, (GLUCallback) glBegin); + gluTessCallback(gt, GLU_TESS_VERTEX, (GLUCallback) Vertex); + gluTessCallback(gt, GLU_TESS_END, (GLUCallback) glEnd); + gluTessCallback(gt, GLU_TESS_COMBINE_DATA, (GLUCallback) Combine); gluTessProperty(gt, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); ssglNormal3v(p.normal); gluTessNormal(gt, p.normal.x, p.normal.y, p.normal.z); - gluTessBeginPolygon(gt, NULL); + CombineVec vecs; + gluTessBeginPolygon(gt, &vecs); for(const SContour &sc : p.l) { gluTessBeginContour(gt); for(const SPoint &sp : sc.l) { diff --git a/src/request.cpp b/src/request.cpp index 3614cec95..cb5e3f3bb 100644 --- a/src/request.cpp +++ b/src/request.cpp @@ -8,6 +8,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + const hRequest Request::HREQUEST_REFERENCE_XY = { 1 }; const hRequest Request::HREQUEST_REFERENCE_YZ = { 2 }; const hRequest Request::HREQUEST_REFERENCE_ZX = { 3 }; @@ -47,16 +49,16 @@ static void CopyEntityInfo(const EntReqMapping *te, int extraPoints, if(hasDistance) *hasDistance = te->hasDistance; } -bool EntReqTable::GetRequestInfo(Request::Type req, int extraPoints, +void EntReqTable::GetRequestInfo(Request::Type req, int extraPoints, Entity::Type *ent, int *pts, bool *hasNormal, bool *hasDistance) { for(const EntReqMapping &te : EntReqMap) { if(req == te.reqType) { CopyEntityInfo(&te, extraPoints, ent, NULL, pts, hasNormal, hasDistance); - return true; + return; } } - return false; + ssassert(false, "No request info"); } bool EntReqTable::GetEntityInfo(Entity::Type ent, int extraPoints, @@ -78,8 +80,7 @@ Request::Type EntReqTable::GetRequestForEntity(Entity::Type ent) { return req; } -void Request::Generate(IdList *entity, - IdList *param) +void Request::Generate(EntityList *entity, ParamList *param) { int points = 0; Entity::Type et = (Entity::Type)0; @@ -90,7 +91,8 @@ void Request::Generate(IdList *entity, // Request-specific generation. switch(type) { case Type::TTF_TEXT: { - double actualAspectRatio = SS.fonts.AspectRatio(font, str); + // `extraPoints` is storing kerning boolean + double actualAspectRatio = SS.fonts.AspectRatio(font, str, extraPoints); if(EXACT(actualAspectRatio != 0.0)) { // We could load the font, so use the actual value. aspectRatio = actualAspectRatio; @@ -238,10 +240,11 @@ int Request::IndexOfPoint(hEntity he) const { return -1; } -hParam Request::AddParam(IdList *param, hParam hp) { +hParam Request::AddParam(ParamList *param, hParam hp) { Param pa = {}; pa.h = hp; param->Add(&pa); return hp; } +} // namespace SolveSpace diff --git a/src/resource.cpp b/src/resource.cpp index 7b19081e1..8921d7abe 100644 --- a/src/resource.cpp +++ b/src/resource.cpp @@ -179,7 +179,8 @@ void Pixmap::ConvertTo(Format newFormat) { static std::shared_ptr ReadPngIntoPixmap(png_struct *png_ptr, png_info *info_ptr, bool flip) { - png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_GRAY_TO_RGB, NULL); + png_read_png(png_ptr, info_ptr, + PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_GRAY_TO_RGB | PNG_TRANSFORM_SCALE_16, NULL); std::shared_ptr pixmap = std::make_shared(); pixmap->width = png_get_image_width(png_ptr, info_ptr); @@ -272,8 +273,8 @@ std::shared_ptr Pixmap::ReadPng(const Platform::Path &filename, bool fli } bool Pixmap::WritePng(FILE *f, bool flip) { - int colorType = 0; - bool bgr = false; + colorType = 0; + bgr = false; switch(format) { case Format::RGBA: colorType = PNG_COLOR_TYPE_RGBA; bgr = false; break; case Format::BGRA: colorType = PNG_COLOR_TYPE_RGBA; bgr = true; break; @@ -564,7 +565,7 @@ const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) { // Find the hex representation in the (sorted) Unifont file. auto first = unifontData.cbegin(), last = unifontData.cend(); - while(first <= last) { + while(first < last) { auto mid = first + (last - first) / 2; while(mid != unifontData.cbegin()) { if(*mid == '\n') { @@ -588,7 +589,10 @@ const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) { if(foundCodepoint < codepoint) { first = mid + 1; while(first != unifontData.cend()) { - if(*first == '\n') break; + if(*first == '\n') { + first++; + break; + } first++; } continue; // and last stays the same diff --git a/src/resource.h b/src/resource.h index 18c1e5821..35ed28907 100644 --- a/src/resource.h +++ b/src/resource.h @@ -7,11 +7,23 @@ #ifndef SOLVESPACE_RESOURCE_H #define SOLVESPACE_RESOURCE_H +#include +#include +#include +#include +#include +#include + +namespace SolveSpace { + class Camera; class Point2d; class Pixmap; class Vector; class RgbaColor; +namespace Platform { + class Path; +} // namespace Platform std::string LoadString(const std::string &name); std::string LoadStringFromGzip(const std::string &name); @@ -26,6 +38,8 @@ class Pixmap { size_t height; size_t stride; std::vector data; + int colorType; + bool bgr; static std::shared_ptr Create(Format format, size_t width, size_t height); static std::shared_ptr FromPng(const uint8_t *data, size_t size, bool flip = false); @@ -109,4 +123,5 @@ class VectorFont { const std::function &traceEdge, const Camera &camera); }; +} #endif diff --git a/src/sketch.h b/src/sketch.h index bda0a983c..324d56644 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -8,18 +8,29 @@ #ifndef SOLVESPACE_SKETCH_H #define SOLVESPACE_SKETCH_H -class hGroup; -class hRequest; -class hEntity; -class hParam; -class hStyle; +#include +#include +#include +#include +#include + +#include "dsc.h" +#include "param.h" +#include "polygon.h" +#include "platform/platform.h" +#include "platform/gui.h" +#include "srf/surface.h" +#include "render/render.h" + +namespace SolveSpace { + class hConstraint; -class hEquation; -class Entity; -class Param; -class Equation; -class Style; +class Expr; +class ExprVector; +class ExprQuaternion; + +enum class SolveResult : uint32_t; enum class PolyError : uint32_t { GOOD = 0, @@ -50,6 +61,17 @@ enum class Command : uint32_t; // All of the hWhatever handles are a 32-bit ID, that is used to represent // some data structure in the sketch. +class hEquation { +public: + uint32_t v; + + inline bool isFromConstraint() const; + inline hConstraint constraint() const; +}; + +template<> +struct IsHandleOracle : std::true_type {}; + class hGroup { public: // bits 15: 0 -- group index @@ -92,17 +114,15 @@ class hEntity { template<> struct IsHandleOracle : std::true_type {}; -class hParam { +class Equation { public: - // bits 15: 0 -- param index - // 31:16 -- request index - uint32_t v; + int tag; + hEquation h; - inline hRequest request() const; -}; + Expr *e; -template<> -struct IsHandleOracle : std::true_type {}; + void Clear() {} +}; class hStyle { public: @@ -112,6 +132,9 @@ class hStyle { template<> struct IsHandleOracle : std::true_type {}; +class Entity; +using EntityList = IdList; + struct EntityId { uint32_t v; // entity ID, starting from 0 }; @@ -122,7 +145,7 @@ struct IsHandleOracle : std::true_type {}; struct EntityKey { hEntity input; int copyNumber; - // (input, copyNumber) gets mapped to ((Request)xxx).entity(h.v) + // (input, copyNumber) gets mapped to hGroup::entity(i) }; struct EntityKeyHash { size_t operator()(const EntityKey &k) const { @@ -175,6 +198,7 @@ class Group { bool suppress; bool relaxConstraints; bool allowRedundant; + bool suppressDofCalculation; bool allDimsReference; double scale; @@ -198,9 +222,14 @@ class Group { // For drawings in 2d WORKPLANE_BY_POINT_ORTHO = 6000, WORKPLANE_BY_LINE_SEGMENTS = 6001, + WORKPLANE_BY_POINT_NORMAL = 6002, + //WORKPLANE_BY_POINT_FACE = 6003, + //WORKPLANE_BY_FACE = 6004, // For extrudes, translates, and rotates ONE_SIDED = 7000, - TWO_SIDED = 7001 + TWO_SIDED = 7001, + ONE_SKEWED = 7004, + TWO_SKEWED = 7005 }; Group::Subtype subtype; @@ -266,6 +295,7 @@ class Group { void Generate(EntityList *entity, ParamList *param); bool IsSolvedOkay(); void TransformImportedBy(Vector t, Quaternion q); + bool IsTriangleMeshAssembly() const; bool IsForcedToMeshBySource() const; bool IsForcedToMesh() const; // When a request generates entities from entities, and the source @@ -286,9 +316,9 @@ class Group { }; hEntity Remap(hEntity in, int copyNumber); void MakeExtrusionLines(EntityList *el, hEntity in); - void MakeLatheCircles(IdList *el, IdList *param, hEntity in, Vector pt, Vector axis); - void MakeLatheSurfacesSelectable(IdList *el, hEntity in, Vector axis); - void MakeRevolveEndFaces(IdList *el, hEntity pt, int ai, int af); + void MakeLatheCircles(EntityList *el, ParamList *param, hEntity in, Vector pt, Vector axis); + void MakeLatheSurfacesSelectable(EntityList *el, hEntity in, Vector axis); + void MakeRevolveEndFaces(EntityList *el, hEntity pt, int ai, int af); void MakeExtrusionTopBottomFaces(EntityList *el, hEntity pt); void CopyEntity(EntityList *el, Entity *ep, int timesApplied, int remap, @@ -323,6 +353,7 @@ class Group { void DrawPolyError(Canvas *canvas); void DrawFilledPaths(Canvas *canvas); void DrawContourAreaLabels(Canvas *canvas); + bool ShouldDrawExploded() const; SPolygon GetPolygon(); @@ -368,6 +399,7 @@ class Request { std::string font; Platform::Path file; double aspectRatio; + int groupRequestIndex; static hParam AddParam(ParamList *param, hParam hp); void Generate(EntityList *entity, ParamList *param); @@ -591,35 +623,21 @@ class Entity : public EntityBase { beziers.l.Clear(); edges.l.Clear(); } + + bool ShouldDrawExploded() const; + Vector ExplodeOffset() const; + Vector PointGetDrawNum() const; }; class EntReqTable { public: - static bool GetRequestInfo(Request::Type req, int extraPoints, + static void GetRequestInfo(Request::Type req, int extraPoints, EntityBase::Type *ent, int *pts, bool *hasNormal, bool *hasDistance); static bool GetEntityInfo(EntityBase::Type ent, int extraPoints, Request::Type *req, int *pts, bool *hasNormal, bool *hasDistance); static Request::Type GetRequestForEntity(EntityBase::Type ent); }; -class Param { -public: - int tag; - hParam h; - - double val; - bool known; - bool free; - - // Used only in the solver - hParam substd; - - static const hParam NO_PARAM; - - void Clear() {} -}; - - class hConstraint { public: uint32_t v; @@ -673,7 +691,10 @@ class ConstraintBase { CURVE_CURVE_TANGENT = 125, EQUAL_RADIUS = 130, WHERE_DRAGGED = 200, - + ARC_ARC_LEN_RATIO = 210, + ARC_LINE_LEN_RATIO = 211, + ARC_ARC_DIFFERENCE = 212, + ARC_LINE_DIFFERENCE = 213, COMMENT = 1000 }; @@ -709,7 +730,7 @@ class ConstraintBase { bool HasLabel() const; bool IsProjectible() const; - void Generate(IdList *param); + void Generate(ParamList *param); void GenerateEquations(IdList *entity, bool forReference = false) const; @@ -757,7 +778,7 @@ class Constraint : public ConstraintBase { Vector p0, Vector p1, Vector pt, double salient); void DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs, Vector a0, Vector da, Vector b0, Vector db, - Vector offset, Vector *ref, bool trim); + Vector offset, Vector *ref, bool trim, Vector explodeOffset); void DoArrow(Canvas *canvas, Canvas::hStroke hcs, Vector p, Vector dir, Vector n, double width, double angle, double da); void DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs, @@ -779,6 +800,8 @@ class Constraint : public ConstraintBase { std::string DescriptionString() const; + bool ShouldDrawExploded() const; + static hConstraint AddConstraint(Constraint *c, bool rememberForUndo = true); static void MenuConstrain(Command id); static void DeleteAllConstraintsFor(Constraint::Type type, hEntity entityA, hEntity ptA); @@ -790,33 +813,14 @@ class Constraint : public ConstraintBase { static hConstraint TryConstrain(Constraint::Type type, hEntity ptA, hEntity ptB, hEntity entityA, hEntity entityB = Entity::NO_ENTITY, bool other = false, bool other2 = false); - static bool ConstrainArcLineTangent(Constraint *c, Entity *line, Entity *arc); - static bool ConstrainCubicLineTangent(Constraint *c, Entity *line, Entity *cubic); - static bool ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *eB); -}; - -class hEquation { -public: - uint32_t v; - - inline bool isFromConstraint() const; - inline hConstraint constraint() const; + static bool ConstrainArcLineTangent(Constraint *c, Entity *line, Entity *arc, + Entity *arcendpoint); + static bool ConstrainCubicLineTangent(Constraint *c, Entity *line, Entity *cubic, + Entity *curveendpoint); + static bool ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *eB, Entity *p1, + Entity *p2); }; -template<> -struct IsHandleOracle : std::true_type {}; - -class Equation { -public: - int tag; - hEquation h; - - Expr *e; - - void Clear() {} -}; - - class Style { public: int tag; @@ -924,6 +928,7 @@ class Style { static double StippleScale(hStyle hs); static double StippleScaleMm(hStyle hs); static std::string StipplePatternName(hStyle hs); + static std::string StipplePatternName(StipplePattern stippleType); static StipplePattern StipplePatternFromString(std::string name); std::string DescriptionString() const; @@ -992,4 +997,6 @@ class ClipboardRequest { hRequest newReq; }; +} // namespace SolveSpace + #endif diff --git a/src/slvs/CMakeLists.txt b/src/slvs/CMakeLists.txt new file mode 100644 index 000000000..663c55683 --- /dev/null +++ b/src/slvs/CMakeLists.txt @@ -0,0 +1,79 @@ +include(GNUInstallDirs) + +# libslvs +add_library(slvs-interface INTERFACE) +target_sources(slvs-interface INTERFACE lib.cpp) +target_compile_definitions(slvs-interface INTERFACE -DLIBRARY) +target_include_directories(slvs-interface INTERFACE ${CMAKE_SOURCE_DIR}/include) +target_link_libraries(slvs-interface INTERFACE slvs-solver mimalloc-static) + +if(ENABLE_PYTHON_LIB) + add_custom_command( + OUTPUT cylib.c + COMMAND cython lib.pyx -o ${CMAKE_CURRENT_BINARY_DIR}/cylib.c --include-dir ${CMAKE_SOURCE_DIR}/include --module-name solvespace + DEPENDS lib.pyx + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) + Python_add_library(slvs-py MODULE cylib.c) + set_target_properties(slvs-py PROPERTIES POSITION_INDEPENDENT_CODE ON) + target_compile_definitions(slvs-py PRIVATE -DSTATIC_LIB) + target_link_libraries( + slvs-py + PRIVATE + slvs-interface + Python::Module + ) + set_target_properties(slvs-py PROPERTIES LIBRARY_OUTPUT_NAME solvespace) + if(SKBUILD) + install(TARGETS slvs-py + DESTINATION ${SKBUILD_PROJECT_NAME} + COMPONENT python + ) + else() + install(TARGETS slvs-py + DESTINATION "slvs" + COMPONENT python + ) + install( + DIRECTORY ${CMAKE_SOURCE_DIR}/python/slvs/ + DESTINATION "slvs" + COMPONENT python + ) + endif() +endif() + +if(EMSCRIPTEN) + add_executable(slvs-wasm jslib.cpp) + target_link_libraries(slvs-wasm PRIVATE slvs-interface embind) + target_link_options(slvs-wasm PRIVATE + "SHELL:-s MODULARIZE=1" + "SHELL:-s EXPORT_NAME=solvespace" + "SHELL:-s SINGLE_FILE=1" + "SHELL:-s INITIAL_MEMORY=512MB" + "SHELL:-s ALLOW_MEMORY_GROWTH" + -O3 + -flto + --closure 1) + set_target_properties(slvs-wasm PROPERTIES OUTPUT_NAME "slvs" SUFFIX ".js") +endif() + +add_library(slvs SHARED) +target_compile_definitions(slvs PRIVATE -DEXPORT_DLL) +target_link_libraries(slvs PRIVATE slvs-interface) +set_target_properties(slvs PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(slvs PUBLIC ${CMAKE_SOURCE_DIR}/include) + +set_target_properties(slvs PROPERTIES + PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/include/slvs.h + VERSION ${PROJECT_VERSION} + SOVERSION 1 +) + +# if(NOT WIN32) +install(TARGETS slvs + COMPONENT slvs + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +# endif() diff --git a/src/slvs/jslib.cpp b/src/slvs/jslib.cpp new file mode 100644 index 000000000..82074ffc5 --- /dev/null +++ b/src/slvs/jslib.cpp @@ -0,0 +1,210 @@ +#include "slvs.h" +#include + +#include "solvespace.h" + +using namespace SolveSpace; + +struct JsSolveResult { + int result; + int dof; + size_t nbad; + emscripten::val bad; +}; + +static JsSolveResult solveSketch(Slvs_hGroup g, bool calculateFaileds) { + JsSolveResult jsResult = {}; + Slvs_hConstraint *c = nullptr; + Slvs_SolveResult ret = Slvs_SolveSketch(g, calculateFaileds ? &c : nullptr); + if(c) { + jsResult.bad = emscripten::val::global("Uint32Array").new_(ret.nbad); + jsResult.bad.call("set", emscripten::typed_memory_view(ret.nbad, c)); + free(c); + } + jsResult.result = ret.result; + jsResult.dof = ret.dof; + jsResult.nbad = ret.nbad; + return jsResult; +} + +EMSCRIPTEN_BINDINGS(slvs) { + emscripten::constant("C_POINTS_COINCIDENT", SLVS_C_POINTS_COINCIDENT); + emscripten::constant("C_PT_PT_DISTANCE", SLVS_C_PT_PT_DISTANCE); + emscripten::constant("C_PT_PLANE_DISTANCE", SLVS_C_PT_PLANE_DISTANCE); + emscripten::constant("C_PT_LINE_DISTANCE", SLVS_C_PT_LINE_DISTANCE); + emscripten::constant("C_PT_FACE_DISTANCE", SLVS_C_PT_FACE_DISTANCE); + emscripten::constant("C_PT_IN_PLANE", SLVS_C_PT_IN_PLANE); + emscripten::constant("C_PT_ON_LINE", SLVS_C_PT_ON_LINE); + emscripten::constant("C_PT_ON_FACE", SLVS_C_PT_ON_FACE); + emscripten::constant("C_EQUAL_LENGTH_LINES", SLVS_C_EQUAL_LENGTH_LINES); + emscripten::constant("C_LENGTH_RATIO", SLVS_C_LENGTH_RATIO); + emscripten::constant("C_EQ_LEN_PT_LINE_D", SLVS_C_EQ_LEN_PT_LINE_D); + emscripten::constant("C_EQ_PT_LN_DISTANCES", SLVS_C_EQ_PT_LN_DISTANCES); + emscripten::constant("C_EQUAL_ANGLE", SLVS_C_EQUAL_ANGLE); + emscripten::constant("C_EQUAL_LINE_ARC_LEN", SLVS_C_EQUAL_LINE_ARC_LEN); + emscripten::constant("C_SYMMETRIC", SLVS_C_SYMMETRIC); + emscripten::constant("C_SYMMETRIC_HORIZ", SLVS_C_SYMMETRIC_HORIZ); + emscripten::constant("C_SYMMETRIC_VERT", SLVS_C_SYMMETRIC_VERT); + emscripten::constant("C_SYMMETRIC_LINE", SLVS_C_SYMMETRIC_LINE); + emscripten::constant("C_AT_MIDPOINT", SLVS_C_AT_MIDPOINT); + emscripten::constant("C_HORIZONTAL", SLVS_C_HORIZONTAL); + emscripten::constant("C_VERTICAL", SLVS_C_VERTICAL); + emscripten::constant("C_DIAMETER", SLVS_C_DIAMETER); + emscripten::constant("C_PT_ON_CIRCLE", SLVS_C_PT_ON_CIRCLE); + emscripten::constant("C_SAME_ORIENTATION", SLVS_C_SAME_ORIENTATION); + emscripten::constant("C_ANGLE", SLVS_C_ANGLE); + emscripten::constant("C_PARALLEL", SLVS_C_PARALLEL); + emscripten::constant("C_PERPENDICULAR", SLVS_C_PERPENDICULAR); + emscripten::constant("C_ARC_LINE_TANGENT", SLVS_C_ARC_LINE_TANGENT); + emscripten::constant("C_CUBIC_LINE_TANGENT", SLVS_C_CUBIC_LINE_TANGENT); + emscripten::constant("C_EQUAL_RADIUS", SLVS_C_EQUAL_RADIUS); + emscripten::constant("C_PROJ_PT_DISTANCE", SLVS_C_PROJ_PT_DISTANCE); + emscripten::constant("C_WHERE_DRAGGED", SLVS_C_WHERE_DRAGGED); + emscripten::constant("C_CURVE_CURVE_TANGENT", SLVS_C_CURVE_CURVE_TANGENT); + emscripten::constant("C_LENGTH_DIFFERENCE", SLVS_C_LENGTH_DIFFERENCE); + emscripten::constant("C_ARC_ARC_LEN_RATIO", SLVS_C_ARC_ARC_LEN_RATIO); + emscripten::constant("C_ARC_LINE_LEN_RATIO", SLVS_C_ARC_LINE_LEN_RATIO); + emscripten::constant("C_ARC_ARC_DIFFERENCE", SLVS_C_ARC_ARC_DIFFERENCE); + emscripten::constant("C_ARC_LINE_DIFFERENCE", SLVS_C_ARC_LINE_DIFFERENCE); + + emscripten::constant("E_POINT_IN_3D", SLVS_E_POINT_IN_3D); + emscripten::constant("E_POINT_IN_2D", SLVS_E_POINT_IN_2D); + emscripten::constant("E_NORMAL_IN_3D", SLVS_E_NORMAL_IN_3D); + emscripten::constant("E_NORMAL_IN_2D", SLVS_E_NORMAL_IN_2D); + emscripten::constant("E_DISTANCE", SLVS_E_DISTANCE); + emscripten::constant("E_WORKPLANE", SLVS_E_WORKPLANE); + emscripten::constant("E_LINE_SEGMENT", SLVS_E_LINE_SEGMENT); + emscripten::constant("E_CUBIC", SLVS_E_CUBIC); + emscripten::constant("E_CIRCLE", SLVS_E_CIRCLE); + emscripten::constant("E_ARC_OF_CIRCLE", SLVS_E_ARC_OF_CIRCLE); + + emscripten::constant("E_NONE", SLVS_E_NONE); + emscripten::constant("E_FREE_IN_3D", SLVS_E_FREE_IN_3D); + + emscripten::constant("RESULT_OKAY", SLVS_RESULT_OKAY); + emscripten::constant("RESULT_INCONSISTENT", SLVS_RESULT_INCONSISTENT); + emscripten::constant("RESULT_DIDNT_CONVERGE", SLVS_RESULT_DIDNT_CONVERGE); + emscripten::constant("RESULT_TOO_MANY_UNKNOWNS", SLVS_RESULT_TOO_MANY_UNKNOWNS); + emscripten::constant("RESULT_REDUNDANT_OKAY", SLVS_RESULT_REDUNDANT_OKAY); + + emscripten::value_array>("array_uint32_4") + .element(emscripten::index<0>()) + .element(emscripten::index<1>()) + .element(emscripten::index<3>()) + .element(emscripten::index<4>()); + + emscripten::value_object("Slvs_Entity") + .field("h", &Slvs_Entity::h) + .field("group", &Slvs_Entity::group) + .field("type", &Slvs_Entity::type) + .field("wrkpl", &Slvs_Entity::wrkpl) + .field("point", &Slvs_Entity::point) + .field("normal", &Slvs_Entity::normal) + .field("distance", &Slvs_Entity::distance) + .field("param", &Slvs_Entity::param); + + emscripten::value_object("Slvs_Constraint") + .field("h", &Slvs_Constraint::h) + .field("group", &Slvs_Constraint::group) + .field("type", &Slvs_Constraint::type) + .field("wrkpl", &Slvs_Constraint::wrkpl) + .field("valA", &Slvs_Constraint::valA) + .field("ptA", &Slvs_Constraint::ptA) + .field("ptB", &Slvs_Constraint::ptB) + .field("entityA", &Slvs_Constraint::entityA) + .field("entityB", &Slvs_Constraint::entityB) + .field("entityC", &Slvs_Constraint::entityC) + .field("entityD", &Slvs_Constraint::entityD) + .field("other", &Slvs_Constraint::other) + .field("other2", &Slvs_Constraint::other2); + + emscripten::value_object("Slvs_SolveResult") + .field("result", &JsSolveResult::result) + .field("dof", &JsSolveResult::dof) + .field("nbad", &JsSolveResult::nbad) + .field("bad", &JsSolveResult::bad); + + emscripten::class_("Quaternion") + .constructor<>() + .function("plus", &Quaternion::Plus) + .function("minus", &Quaternion::Minus) + .function("scaledBy", &Quaternion::ScaledBy) + .function("magnitude", &Quaternion::Magnitude) + .function("withMagnitude", &Quaternion::WithMagnitude) + .function("toThe", &Quaternion::ToThe) + .function("inverse", &Quaternion::Inverse) + .function("times", &Quaternion::Times) + .function("mirror", &Quaternion::Mirror) + .function("rotationU", &Quaternion::RotationU) + .function("rotationV", &Quaternion::RotationV) + .function("rotationN", &Quaternion::RotationN) + .property("w", &Quaternion::w) + .property("vx", &Quaternion::vx) + .property("vy", &Quaternion::vy) + .property("vz", &Quaternion::vz) + .class_function("from", emscripten::select_overload(&Quaternion::From)); + + emscripten::class_("Vector") + .constructor<>() + .property("x", &Vector::x) + .property("y", &Vector::y) + .property("z", &Vector::z); + + emscripten::function("isFreeIn3D", &Slvs_IsFreeIn3D); + emscripten::function("is3D", &Slvs_Is3D); + emscripten::function("isNone", &Slvs_IsNone); + emscripten::function("isPoint2D", &Slvs_IsPoint2D); + emscripten::function("isPoint3D", &Slvs_IsPoint3D); + emscripten::function("isNormal2D", &Slvs_IsNormal2D); + emscripten::function("isNormal3D", &Slvs_IsNormal3D); + emscripten::function("isLine", &Slvs_IsLine); + emscripten::function("isLine2D", &Slvs_IsLine2D); + emscripten::function("isLine3D", &Slvs_IsLine3D); + emscripten::function("isCubic", &Slvs_IsCubic); + emscripten::function("isArc", &Slvs_IsArc); + emscripten::function("isWorkplane", &Slvs_IsWorkplane); + emscripten::function("isDistance", &Slvs_IsDistance); + emscripten::function("isPoint", &Slvs_IsPoint); + + emscripten::function("addPoint2D", &Slvs_AddPoint2D); + emscripten::function("addPoint3D", &Slvs_AddPoint3D); + emscripten::function("addNormal2D", &Slvs_AddNormal2D); + emscripten::function("addNormal3D", &Slvs_AddNormal3D); + emscripten::function("addDistance", &Slvs_AddDistance); + emscripten::function("addLine2D", &Slvs_AddLine2D); + emscripten::function("addLine3D", &Slvs_AddLine3D); + emscripten::function("addCubic", &Slvs_AddCubic); + emscripten::function("addArc", &Slvs_AddArc); + emscripten::function("addCircle", &Slvs_AddCircle); + emscripten::function("addWorkplane", &Slvs_AddWorkplane); + emscripten::function("addBase2D", &Slvs_AddBase2D); + + emscripten::function("addConstraint", &Slvs_AddConstraint); + emscripten::function("coincident", &Slvs_Coincident); + emscripten::function("distance", &Slvs_Distance); + emscripten::function("equal", &Slvs_Equal); + emscripten::function("equalAngle", &Slvs_EqualAngle); + emscripten::function("equalPointToLine", &Slvs_EqualPointToLine); + emscripten::function("ratio", &Slvs_Ratio); + emscripten::function("symmetric", &Slvs_Symmetric); + emscripten::function("symmetricH", &Slvs_SymmetricH); + emscripten::function("symmetricV", &Slvs_SymmetricV); + emscripten::function("midpoint", &Slvs_Midpoint); + emscripten::function("horizontal", &Slvs_Horizontal); + emscripten::function("vertical", &Slvs_Vertical); + emscripten::function("diameter", &Slvs_Diameter); + emscripten::function("sameOrientation", &Slvs_SameOrientation); + emscripten::function("angle", &Slvs_Angle); + emscripten::function("perpendicular", &Slvs_Perpendicular); + emscripten::function("parallel", &Slvs_Parallel); + emscripten::function("tangent", &Slvs_Tangent); + emscripten::function("distanceProj", &Slvs_DistanceProj); + emscripten::function("lengthDiff", &Slvs_LengthDiff); + emscripten::function("dragged", &Slvs_Dragged); + + emscripten::function("getParamValue", &Slvs_GetParamValue); + emscripten::function("setParamValue", &Slvs_SetParamValue); + emscripten::function("markDragged", &Slvs_MarkDragged); + emscripten::function("solveSketch", &solveSketch); + emscripten::function("clearSketch", &Slvs_ClearSketch); +} diff --git a/src/slvs/lib.cpp b/src/slvs/lib.cpp new file mode 100644 index 000000000..56c7c2423 --- /dev/null +++ b/src/slvs/lib.cpp @@ -0,0 +1,1124 @@ +//----------------------------------------------------------------------------- +// A library wrapper around SolveSpace, to permit someone to use its constraint +// solver without coupling their program too much to SolveSpace's internals. +// +// Copyright 2008-2013 Jonathan Westhues. +//----------------------------------------------------------------------------- +#include +#include "solvespace.h" +#include +#include + +namespace SolveSpace { + +Sketch SK = {}; + +void Platform::FatalError(const std::string &message) { + fprintf(stderr, "%s", message.c_str()); + abort(); +} + +void Group::GenerateEquations(IdList *) { + // Nothing to do for now. +} + +} // namespace SolveSpace + +using namespace SolveSpace; + +static System SYS; +static ParamSet dragged; + +extern "C" { + +static ConstraintBase::Type Slvs_CTypeToConstraintBaseType(int type) { + switch(type) { +case SLVS_C_POINTS_COINCIDENT: return ConstraintBase::Type::POINTS_COINCIDENT; +case SLVS_C_PT_PT_DISTANCE: return ConstraintBase::Type::PT_PT_DISTANCE; +case SLVS_C_PT_PLANE_DISTANCE: return ConstraintBase::Type::PT_PLANE_DISTANCE; +case SLVS_C_PT_LINE_DISTANCE: return ConstraintBase::Type::PT_LINE_DISTANCE; +case SLVS_C_PT_FACE_DISTANCE: return ConstraintBase::Type::PT_FACE_DISTANCE; +case SLVS_C_PT_IN_PLANE: return ConstraintBase::Type::PT_IN_PLANE; +case SLVS_C_PT_ON_LINE: return ConstraintBase::Type::PT_ON_LINE; +case SLVS_C_PT_ON_FACE: return ConstraintBase::Type::PT_ON_FACE; +case SLVS_C_EQUAL_LENGTH_LINES: return ConstraintBase::Type::EQUAL_LENGTH_LINES; +case SLVS_C_LENGTH_RATIO: return ConstraintBase::Type::LENGTH_RATIO; +case SLVS_C_ARC_ARC_LEN_RATIO: return ConstraintBase::Type::ARC_ARC_LEN_RATIO; +case SLVS_C_ARC_LINE_LEN_RATIO: return ConstraintBase::Type::ARC_LINE_LEN_RATIO; +case SLVS_C_EQ_LEN_PT_LINE_D: return ConstraintBase::Type::EQ_LEN_PT_LINE_D; +case SLVS_C_EQ_PT_LN_DISTANCES: return ConstraintBase::Type::EQ_PT_LN_DISTANCES; +case SLVS_C_EQUAL_ANGLE: return ConstraintBase::Type::EQUAL_ANGLE; +case SLVS_C_EQUAL_LINE_ARC_LEN: return ConstraintBase::Type::EQUAL_LINE_ARC_LEN; +case SLVS_C_LENGTH_DIFFERENCE: return ConstraintBase::Type::LENGTH_DIFFERENCE; +case SLVS_C_ARC_ARC_DIFFERENCE: return ConstraintBase::Type::ARC_ARC_DIFFERENCE; +case SLVS_C_ARC_LINE_DIFFERENCE: return ConstraintBase::Type::ARC_LINE_DIFFERENCE; +case SLVS_C_SYMMETRIC: return ConstraintBase::Type::SYMMETRIC; +case SLVS_C_SYMMETRIC_HORIZ: return ConstraintBase::Type::SYMMETRIC_HORIZ; +case SLVS_C_SYMMETRIC_VERT: return ConstraintBase::Type::SYMMETRIC_VERT; +case SLVS_C_SYMMETRIC_LINE: return ConstraintBase::Type::SYMMETRIC_LINE; +case SLVS_C_AT_MIDPOINT: return ConstraintBase::Type::AT_MIDPOINT; +case SLVS_C_HORIZONTAL: return ConstraintBase::Type::HORIZONTAL; +case SLVS_C_VERTICAL: return ConstraintBase::Type::VERTICAL; +case SLVS_C_DIAMETER: return ConstraintBase::Type::DIAMETER; +case SLVS_C_PT_ON_CIRCLE: return ConstraintBase::Type::PT_ON_CIRCLE; +case SLVS_C_SAME_ORIENTATION: return ConstraintBase::Type::SAME_ORIENTATION; +case SLVS_C_ANGLE: return ConstraintBase::Type::ANGLE; +case SLVS_C_PARALLEL: return ConstraintBase::Type::PARALLEL; +case SLVS_C_PERPENDICULAR: return ConstraintBase::Type::PERPENDICULAR; +case SLVS_C_ARC_LINE_TANGENT: return ConstraintBase::Type::ARC_LINE_TANGENT; +case SLVS_C_CUBIC_LINE_TANGENT: return ConstraintBase::Type::CUBIC_LINE_TANGENT; +case SLVS_C_EQUAL_RADIUS: return ConstraintBase::Type::EQUAL_RADIUS; +case SLVS_C_PROJ_PT_DISTANCE: return ConstraintBase::Type::PROJ_PT_DISTANCE; +case SLVS_C_WHERE_DRAGGED: return ConstraintBase::Type::WHERE_DRAGGED; +case SLVS_C_CURVE_CURVE_TANGENT: return ConstraintBase::Type::CURVE_CURVE_TANGENT; +default: Platform::FatalError("bad constraint type " + std::to_string(type)); + } +} + +static EntityBase::Type Slvs_CTypeToEntityBaseType(int type) { + switch(type) { +case SLVS_E_POINT_IN_3D: return EntityBase::Type::POINT_IN_3D; +case SLVS_E_POINT_IN_2D: return EntityBase::Type::POINT_IN_2D; +case SLVS_E_NORMAL_IN_3D: return EntityBase::Type::NORMAL_IN_3D; +case SLVS_E_NORMAL_IN_2D: return EntityBase::Type::NORMAL_IN_2D; +case SLVS_E_DISTANCE: return EntityBase::Type::DISTANCE; +case SLVS_E_WORKPLANE: return EntityBase::Type::WORKPLANE; +case SLVS_E_LINE_SEGMENT: return EntityBase::Type::LINE_SEGMENT; +case SLVS_E_CUBIC: return EntityBase::Type::CUBIC; +case SLVS_E_CIRCLE: return EntityBase::Type::CIRCLE; +case SLVS_E_ARC_OF_CIRCLE: return EntityBase::Type::ARC_OF_CIRCLE; +default: Platform::FatalError("bad entity type " + std::to_string(type)); + } +} + +static bool Slvs_CanInitiallySatisfy(const ConstraintBase &c) { + switch(c.type) { + case ConstraintBase::Type::CUBIC_LINE_TANGENT: + case ConstraintBase::Type::PARALLEL: + // Can't initially satisfy if not projected onto a workplane + return c.workplane != EntityBase::FREE_IN_3D; + + case ConstraintBase::Type::PT_PT_DISTANCE: + case ConstraintBase::Type::PROJ_PT_DISTANCE: + case ConstraintBase::Type::PT_LINE_DISTANCE: + case ConstraintBase::Type::PT_PLANE_DISTANCE: + case ConstraintBase::Type::PT_FACE_DISTANCE: + case ConstraintBase::Type::EQUAL_LENGTH_LINES: + case ConstraintBase::Type::EQ_LEN_PT_LINE_D: + case ConstraintBase::Type::EQ_PT_LN_DISTANCES: + case ConstraintBase::Type::LENGTH_RATIO: + case ConstraintBase::Type::ARC_ARC_LEN_RATIO: + case ConstraintBase::Type::ARC_LINE_LEN_RATIO: + case ConstraintBase::Type::LENGTH_DIFFERENCE: + case ConstraintBase::Type::ARC_ARC_DIFFERENCE: + case ConstraintBase::Type::ARC_LINE_DIFFERENCE: + case ConstraintBase::Type::DIAMETER: + case ConstraintBase::Type::EQUAL_RADIUS: + case ConstraintBase::Type::EQUAL_LINE_ARC_LEN: + case ConstraintBase::Type::PT_IN_PLANE: + case ConstraintBase::Type::PT_ON_FACE: + case ConstraintBase::Type::PT_ON_CIRCLE: + case ConstraintBase::Type::HORIZONTAL: + case ConstraintBase::Type::VERTICAL: + case ConstraintBase::Type::PERPENDICULAR: + case ConstraintBase::Type::ANGLE: + case ConstraintBase::Type::EQUAL_ANGLE: + case ConstraintBase::Type::ARC_LINE_TANGENT: + case ConstraintBase::Type::CURVE_CURVE_TANGENT: + return true; + + case ConstraintBase::Type::AT_MIDPOINT: + // Can initially satisfy if between a line segment and a workplane + return c.ptA == EntityBase::NO_ENTITY; + + case ConstraintBase::Type::POINTS_COINCIDENT: + case ConstraintBase::Type::PT_ON_LINE: + case ConstraintBase::Type::SYMMETRIC: + case ConstraintBase::Type::SYMMETRIC_HORIZ: + case ConstraintBase::Type::SYMMETRIC_VERT: + case ConstraintBase::Type::SYMMETRIC_LINE: + case ConstraintBase::Type::SAME_ORIENTATION: + case ConstraintBase::Type::WHERE_DRAGGED: + case ConstraintBase::Type::COMMENT: + // Can't initially satisfy these constraints + return false; + } + ssassert(false, "Unexpected constraint type"); +} + +bool Slvs_IsFreeIn3D(Slvs_Entity e) { + return e.h == SLVS_FREE_IN_3D; +} + +bool Slvs_Is3D(Slvs_Entity e) { + return e.wrkpl == SLVS_FREE_IN_3D; +} + +bool Slvs_IsNone(Slvs_Entity e) { + return e.h == 0; +} + +bool Slvs_IsPoint2D(Slvs_Entity e) { + return e.type == SLVS_E_POINT_IN_2D; +} + +bool Slvs_IsPoint3D(Slvs_Entity e) { + return e.type == SLVS_E_POINT_IN_3D; +} + +bool Slvs_IsNormal2D(Slvs_Entity e) { + return e.type == SLVS_E_NORMAL_IN_2D; +} + +bool Slvs_IsNormal3D(Slvs_Entity e) { + return e.type == SLVS_E_NORMAL_IN_3D; +} + +bool Slvs_IsLine(Slvs_Entity e) { + return e.type == SLVS_E_LINE_SEGMENT; +} + +bool Slvs_IsLine2D(Slvs_Entity e) { + return e.type == SLVS_E_LINE_SEGMENT && !Slvs_Is3D(e); +} + +bool Slvs_IsLine3D(Slvs_Entity e) { + return e.type == SLVS_E_LINE_SEGMENT && Slvs_Is3D(e); +} + +bool Slvs_IsCubic(Slvs_Entity e) { + return e.type == SLVS_E_CUBIC; +} + +bool Slvs_IsArc(Slvs_Entity e) { + return e.type == SLVS_E_ARC_OF_CIRCLE; +} + +bool Slvs_IsWorkplane(Slvs_Entity e) { + return e.type == SLVS_E_WORKPLANE; +} + +bool Slvs_IsDistance(Slvs_Entity e) { + return e.type == SLVS_E_DISTANCE; +} + +bool Slvs_IsPoint(Slvs_Entity e) { + switch(e.type) { + case SLVS_E_POINT_IN_3D: + case SLVS_E_POINT_IN_2D: + return true; + default: + return false; + } +} + +bool Slvs_IsCircle(Slvs_Entity e) { + return e.type == SLVS_E_CIRCLE || e.type == SLVS_E_ARC_OF_CIRCLE; +} + +Slvs_hParam Slvs_AddParam(double val) { + Param pa = {}; + pa.val = val; + SK.param.AddAndAssignId(&pa); + return pa.h.v; +} + +// entities +Slvs_Entity Slvs_AddPoint2D(uint32_t grouph, double u, double v, Slvs_Entity workplane) { + Slvs_hParam uph = Slvs_AddParam(u); + Slvs_hParam vph = Slvs_AddParam(v); + EntityBase e = {}; + e.type = EntityBase::Type::POINT_IN_2D; + e.group.v = grouph; + e.workplane.v = workplane.h; + e.param[0].v = uph; + e.param[1].v = vph; + SK.entity.AddAndAssignId(&e); + + Slvs_Entity ce = Slvs_Entity {}; + ce.h = e.h.v; + ce.type = SLVS_E_POINT_IN_2D; + ce.group = grouph; + ce.wrkpl = workplane.h; + ce.param[0] = uph; + ce.param[1] = vph; + return ce; +} + +Slvs_Entity Slvs_AddPoint3D(uint32_t grouph, double x, double y, double z) { + Slvs_hParam xph = Slvs_AddParam(x); + Slvs_hParam yph = Slvs_AddParam(y); + Slvs_hParam zph = Slvs_AddParam(z); + EntityBase e = {}; + e.type = EntityBase::Type::POINT_IN_3D; + e.group.v = grouph; + e.workplane.v = EntityBase::FREE_IN_3D.v; + e.param[0].v = xph; + e.param[1].v = yph; + e.param[2].v = zph; + SK.entity.AddAndAssignId(&e); + + Slvs_Entity ce = Slvs_Entity {}; + ce.h = e.h.v; + ce.type = SLVS_E_POINT_IN_3D; + ce.group = grouph; + ce.wrkpl = SLVS_FREE_IN_3D; + ce.param[0] = xph; + ce.param[1] = yph; + ce.param[2] = zph; + return ce; +} + +Slvs_Entity Slvs_AddNormal2D(uint32_t grouph, Slvs_Entity workplane) { + if(!Slvs_IsWorkplane(workplane)) { + Platform::FatalError("workplane argument is not a workplane"); + } + EntityBase e = {}; + e.type = EntityBase::Type::NORMAL_IN_2D; + e.group.v = grouph; + e.workplane.v = workplane.h; + SK.entity.AddAndAssignId(&e); + + Slvs_Entity ce = Slvs_Entity {}; + ce.h = e.h.v; + ce.type = SLVS_E_NORMAL_IN_2D; + ce.group = grouph; + ce.wrkpl = workplane.h; + return ce; +} + +Slvs_Entity Slvs_AddNormal3D(uint32_t grouph, double qw, double qx, double qy, double qz) { + Slvs_hParam wph = Slvs_AddParam(qw); + Slvs_hParam xph = Slvs_AddParam(qx); + Slvs_hParam yph = Slvs_AddParam(qy); + Slvs_hParam zph = Slvs_AddParam(qz); + EntityBase e = {}; + e.type = EntityBase::Type::NORMAL_IN_3D; + e.group.v = grouph; + e.workplane.v = EntityBase::FREE_IN_3D.v; + e.param[0].v = wph; + e.param[1].v = xph; + e.param[2].v = yph; + e.param[3].v = zph; + SK.entity.AddAndAssignId(&e); + + Slvs_Entity ce = Slvs_Entity {}; + ce.h = e.h.v; + ce.type = SLVS_E_NORMAL_IN_3D; + ce.group = grouph; + ce.wrkpl = SLVS_FREE_IN_3D; + ce.param[0] = wph; + ce.param[1] = xph; + ce.param[2] = yph; + ce.param[3] = zph; + return ce; +} + +Slvs_Entity Slvs_AddDistance(uint32_t grouph, double value, Slvs_Entity workplane) { + if(!Slvs_IsWorkplane(workplane)) { + Platform::FatalError("workplane argument is not a workplane"); + } + Slvs_hParam valueph = Slvs_AddParam(value); + EntityBase e = {}; + e.type = EntityBase::Type::DISTANCE; + e.group.v = grouph; + e.workplane.v = workplane.h; + e.param[0].v = valueph; + SK.entity.AddAndAssignId(&e); + + Slvs_Entity ce = Slvs_Entity {}; + ce.h = e.h.v; + ce.type = SLVS_E_DISTANCE; + ce.group = grouph; + ce.wrkpl = workplane.h; + ce.param[0] = valueph; + return ce; +} + +Slvs_Entity Slvs_AddLine2D(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity workplane) { + if(!Slvs_IsWorkplane(workplane)) { + Platform::FatalError("workplane argument is not a workplane"); + } else if(!Slvs_IsPoint2D(ptA)) { + Platform::FatalError("ptA argument is not a 2d point"); + } else if(!Slvs_IsPoint2D(ptB)) { + Platform::FatalError("ptB argument is not a 2d point"); + } + EntityBase e = {}; + e.type = EntityBase::Type::LINE_SEGMENT; + e.group.v = grouph; + e.workplane.v = workplane.h; + e.point[0].v = ptA.h; + e.point[1].v = ptB.h; + SK.entity.AddAndAssignId(&e); + + Slvs_Entity ce = Slvs_Entity {}; + ce.h = e.h.v; + ce.type = SLVS_E_LINE_SEGMENT; + ce.group = grouph; + ce.wrkpl = workplane.h; + ce.point[0] = ptA.h; + ce.point[1] = ptB.h; + return ce; +} + +Slvs_Entity Slvs_AddLine3D(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB) { + if(!Slvs_IsPoint3D(ptA)) { + Platform::FatalError("ptA argument is not a 3d point"); + } else if(!Slvs_IsPoint3D(ptB)) { + Platform::FatalError("ptB argument is not a 3d point"); + } + EntityBase e = {}; + e.type = EntityBase::Type::LINE_SEGMENT; + e.group.v = grouph; + e.workplane.v = EntityBase::FREE_IN_3D.v; + e.point[0].v = ptA.h; + e.point[1].v = ptB.h; + SK.entity.AddAndAssignId(&e); + + Slvs_Entity ce = Slvs_Entity {}; + ce.h = e.h.v; + ce.type = SLVS_E_LINE_SEGMENT; + ce.group = grouph; + ce.wrkpl = SLVS_FREE_IN_3D; + ce.point[0] = ptA.h; + ce.point[1] = ptB.h; + return ce; +} + +Slvs_Entity Slvs_AddCubic(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity ptC, Slvs_Entity ptD, Slvs_Entity workplane) { + if(!Slvs_IsWorkplane(workplane)) { + Platform::FatalError("workplane argument is not a workplane"); + } else if(!Slvs_IsPoint2D(ptA)) { + Platform::FatalError("ptA argument is not a 2d point"); + } else if(!Slvs_IsPoint2D(ptB)) { + Platform::FatalError("ptB argument is not a 2d point"); + } else if(!Slvs_IsPoint2D(ptC)) { + Platform::FatalError("ptC argument is not a 2d point"); + } else if(!Slvs_IsPoint2D(ptD)) { + Platform::FatalError("ptD argument is not a 2d point"); + } + EntityBase e = {}; + e.type = EntityBase::Type::CUBIC; + e.group.v = grouph; + e.workplane.v = workplane.h; + e.point[0].v = ptA.h; + e.point[1].v = ptB.h; + e.point[2].v = ptC.h; + e.point[3].v = ptD.h; + SK.entity.AddAndAssignId(&e); + + Slvs_Entity ce = Slvs_Entity {}; + ce.h = e.h.v; + ce.type = SLVS_E_CUBIC; + ce.group = grouph; + ce.wrkpl = workplane.h; + ce.point[0] = ptA.h; + ce.point[1] = ptB.h; + ce.point[2] = ptC.h; + ce.point[3] = ptD.h; + return ce; +} + + +Slvs_Entity Slvs_AddArc(uint32_t grouph, Slvs_Entity normal, Slvs_Entity center, Slvs_Entity start, Slvs_Entity end, + Slvs_Entity workplane) { + if(!Slvs_IsWorkplane(workplane)) { + Platform::FatalError("workplane argument is not a workplane"); + } else if(!Slvs_IsNormal3D(normal)) { + Platform::FatalError("normal argument is not a 3d normal"); + } else if(!Slvs_IsPoint2D(center)) { + Platform::FatalError("center argument is not a 2d point"); + } else if(!Slvs_IsPoint2D(start)) { + Platform::FatalError("start argument is not a 2d point"); + } else if(!Slvs_IsPoint2D(end)) { + Platform::FatalError("end argument is not a 2d point"); + } + EntityBase e = {}; + e.type = EntityBase::Type::ARC_OF_CIRCLE; + e.group.v = grouph; + e.workplane.v = workplane.h; + e.normal.v = normal.h; + e.point[0].v = center.h; + e.point[1].v = start.h; + e.point[2].v = end.h; + SK.entity.AddAndAssignId(&e); + + Slvs_Entity ce = Slvs_Entity {}; + ce.h = e.h.v; + ce.type = SLVS_E_ARC_OF_CIRCLE; + ce.group = grouph; + ce.wrkpl = workplane.h; + ce.normal = normal.h; + ce.point[0] = center.h; + ce.point[1] = start.h; + ce.point[2] = end.h; + return ce; +} + +Slvs_Entity Slvs_AddCircle(uint32_t grouph, Slvs_Entity normal, Slvs_Entity center, Slvs_Entity radius, + Slvs_Entity workplane) { + if(!Slvs_IsWorkplane(workplane)) { + Platform::FatalError("workplane argument is not a workplane"); + } else if(!Slvs_IsNormal3D(normal)) { + Platform::FatalError("normal argument is not a 3d normal"); + } else if(!Slvs_IsPoint2D(center)) { + Platform::FatalError("center argument is not a 2d point"); + } else if(!Slvs_IsDistance(radius)) { + Platform::FatalError("radius argument is not a distance"); + } + EntityBase e = {}; + e.type = EntityBase::Type::CIRCLE; + e.group.v = grouph; + e.workplane.v = workplane.h; + e.normal.v = normal.h; + e.point[0].v = center.h; + e.distance.v = radius.h; + SK.entity.AddAndAssignId(&e); + + Slvs_Entity ce = Slvs_Entity {}; + ce.h = e.h.v; + ce.type = SLVS_E_CIRCLE; + ce.group = grouph; + ce.wrkpl = workplane.h; + ce.normal = normal.h; + ce.point[0] = center.h; + ce.distance = radius.h; + return ce; +} + +Slvs_Entity Slvs_AddWorkplane(uint32_t grouph, Slvs_Entity origin, Slvs_Entity nm) { + EntityBase e = {}; + e.type = EntityBase::Type::WORKPLANE; + e.group.v = grouph; + e.workplane.v = SLVS_FREE_IN_3D; + e.point[0].v = origin.h; + e.normal.v = nm.h; + SK.entity.AddAndAssignId(&e); + + Slvs_Entity ce = Slvs_Entity {}; + ce.h = e.h.v; + ce.type = SLVS_E_WORKPLANE; + ce.group = grouph; + ce.wrkpl = SLVS_FREE_IN_3D; + ce.point[0] = origin.h; + ce.normal = nm.h; + return ce; +} + +Slvs_Entity Slvs_AddBase2D(uint32_t grouph) { + Vector u = Vector::From(1, 0, 0); + Vector v = Vector::From(0, 1, 0); + Quaternion q = Quaternion::From(u, v); + Slvs_Entity nm = Slvs_AddNormal3D(grouph, q.w, q.vx, q.vy, q.vz); + return Slvs_AddWorkplane(grouph, Slvs_AddPoint3D(grouph, 0, 0, 0), nm); +} + +// constraints + +Slvs_Constraint Slvs_AddConstraint(uint32_t grouph, + int type, Slvs_Entity workplane, double val, Slvs_Entity ptA, + Slvs_Entity ptB = SLVS_E_NONE, Slvs_Entity entityA = SLVS_E_NONE, + Slvs_Entity entityB = SLVS_E_NONE, Slvs_Entity entityC = SLVS_E_NONE, + Slvs_Entity entityD = SLVS_E_NONE, int other = 0, int other2 = 0) { + ConstraintBase c = {}; + c.type = Slvs_CTypeToConstraintBaseType(type); + c.group.v = grouph; + c.workplane.v = workplane.h; + c.valA = val; + c.ptA.v = ptA.h; + c.ptB.v = ptB.h; + c.entityA.v = entityA.h; + c.entityB.v = entityB.h; + c.entityC.v = entityC.h; + c.entityD.v = entityD.h; + c.other = other ? true : false; + c.other2 = other2 ? true : false; + SK.constraint.AddAndAssignId(&c); + + Slvs_Constraint cc = Slvs_Constraint {}; + cc.h = c.h.v; + cc.type = type; + cc.group = grouph; + cc.wrkpl = workplane.h; + cc.valA = val; + cc.ptA = ptA.h; + cc.ptB = ptB.h; + cc.entityA = entityA.h; + cc.entityB = entityB.h; + cc.entityC = entityC.h; + cc.entityD = entityD.h; + cc.other = other ? true : false; + cc.other2 = other2 ? true : false; + return cc; +} + +Slvs_Constraint Slvs_Coincident(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity workplane = SLVS_E_FREE_IN_3D) { + if(Slvs_IsPoint(entityA) && Slvs_IsPoint(entityB)) { + return Slvs_AddConstraint(grouph, SLVS_C_POINTS_COINCIDENT, workplane, 0., entityA, entityB); + } else if(Slvs_IsPoint(entityA) && Slvs_IsWorkplane(entityB)) { + return Slvs_AddConstraint(grouph, SLVS_C_PT_IN_PLANE, SLVS_E_FREE_IN_3D, 0., entityA, SLVS_E_NONE, entityB); + } else if(Slvs_IsPoint(entityA) && Slvs_IsLine(entityB)) { + return Slvs_AddConstraint(grouph, SLVS_C_PT_ON_LINE, workplane, 0., entityA, SLVS_E_NONE, entityB); + } else if(Slvs_IsPoint(entityA) && (Slvs_IsCircle(entityB) || Slvs_IsArc(entityB))) { + return Slvs_AddConstraint(grouph, SLVS_C_PT_ON_CIRCLE, workplane, 0., entityA, SLVS_E_NONE, entityB); + } + Platform::FatalError("Invalid arguments for coincident constraint"); +} + +Slvs_Constraint Slvs_Distance(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, double value, Slvs_Entity workplane) { + if(Slvs_IsPoint(entityA) && Slvs_IsPoint(entityB)) { + return Slvs_AddConstraint(grouph, SLVS_C_PT_PT_DISTANCE, workplane, value, entityA, entityB); + } else if(Slvs_IsPoint(entityA) && Slvs_IsWorkplane(entityB) && Slvs_Is3D(workplane)) { + return Slvs_AddConstraint(grouph, SLVS_C_PT_PLANE_DISTANCE, entityB, value, entityA, SLVS_E_NONE, entityB); + } else if(Slvs_IsPoint(entityA) && Slvs_IsLine(entityB)) { + return Slvs_AddConstraint(grouph, SLVS_C_PT_LINE_DISTANCE, workplane, value, entityA, SLVS_E_NONE, entityB); + } + Platform::FatalError("Invalid arguments for distance constraint"); +} + +Slvs_Constraint Slvs_Equal(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity workplane = SLVS_E_FREE_IN_3D) { + if(Slvs_IsLine(entityA) && Slvs_IsLine(entityB)) { + return Slvs_AddConstraint(grouph, SLVS_C_EQUAL_LENGTH_LINES, workplane, 0., SLVS_E_NONE, SLVS_E_NONE, entityA, entityB); + } else if(Slvs_IsLine(entityA) && (Slvs_IsArc(entityB) || Slvs_IsCircle(entityB))) { + return Slvs_AddConstraint(grouph, SLVS_C_EQUAL_LINE_ARC_LEN, workplane, 0., SLVS_E_NONE, SLVS_E_NONE, entityA, entityB); + } else if((Slvs_IsArc(entityA) || Slvs_IsCircle(entityA)) && (Slvs_IsArc(entityB) || Slvs_IsCircle(entityB))) { + return Slvs_AddConstraint(grouph, SLVS_C_EQUAL_RADIUS, workplane, 0., SLVS_E_NONE, SLVS_E_NONE, entityA, entityB); + } + Platform::FatalError("Invalid arguments for equal constraint"); +} + +Slvs_Constraint Slvs_EqualAngle(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity entityC, Slvs_Entity entityD, Slvs_Entity workplane = SLVS_E_FREE_IN_3D) { + if(Slvs_IsLine2D(entityA) && Slvs_IsLine2D(entityB) && Slvs_IsLine2D(entityC) && Slvs_IsLine2D(entityD) && (Slvs_IsWorkplane(workplane) || Slvs_IsFreeIn3D(workplane))) { + return Slvs_AddConstraint(grouph, SLVS_C_EQUAL_ANGLE, workplane, 0., SLVS_E_NONE, SLVS_E_NONE, entityA, entityB, entityC, entityD); + } + Platform::FatalError("Invalid arguments for equal angle constraint"); +} + +Slvs_Constraint Slvs_EqualPointToLine(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity entityC, Slvs_Entity entityD, Slvs_Entity workplane = SLVS_E_FREE_IN_3D) { + if(Slvs_IsPoint2D(entityA) && Slvs_IsLine2D(entityB) && Slvs_IsPoint2D(entityC) && Slvs_IsLine2D(entityD) && (Slvs_IsWorkplane(workplane) || Slvs_IsFreeIn3D(workplane))) { + return Slvs_AddConstraint(grouph, SLVS_C_EQ_PT_LN_DISTANCES, workplane, 0., entityA, entityB, entityC, entityD); + } + Platform::FatalError("Invalid arguments for equal point to line constraint"); +} + +Slvs_Constraint Slvs_Ratio(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, double value, Slvs_Entity workplane = SLVS_E_FREE_IN_3D) { + if(Slvs_IsLine2D(entityA) && Slvs_IsLine2D(entityB) && (Slvs_IsWorkplane(workplane) || Slvs_IsFreeIn3D(workplane))) { + return Slvs_AddConstraint(grouph, SLVS_C_LENGTH_RATIO, workplane, value, SLVS_E_NONE, SLVS_E_NONE, entityA, entityB); + } + Platform::FatalError("Invalid arguments for ratio constraint"); +} + +Slvs_Constraint Slvs_Symmetric(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity entityC = SLVS_E_NONE, Slvs_Entity workplane = SLVS_E_FREE_IN_3D) { + if(Slvs_IsPoint3D(entityA) && Slvs_IsPoint3D(entityB) && Slvs_IsWorkplane(entityC) && Slvs_IsFreeIn3D(workplane)) { + return Slvs_AddConstraint(grouph, SLVS_C_SYMMETRIC, workplane, 0., entityA, entityB, entityC); + } else if(Slvs_IsPoint2D(entityA) && Slvs_IsPoint2D(entityB) && Slvs_IsWorkplane(entityC) && Slvs_IsFreeIn3D(workplane)) { + return Slvs_AddConstraint(grouph, SLVS_C_SYMMETRIC, entityC, 0., entityA, entityB, entityC); + } else if(Slvs_IsPoint2D(entityA) && Slvs_IsPoint2D(entityB) && Slvs_IsLine(entityC)) { + if(Slvs_IsFreeIn3D(workplane)) { + Platform::FatalError("3d workplane given for a 2d constraint"); + } + return Slvs_AddConstraint(grouph, SLVS_C_SYMMETRIC_LINE, workplane, 0., entityA, entityB, entityC); + } + Platform::FatalError("Invalid arguments for symmetric constraint"); +} + +Slvs_Constraint Slvs_SymmetricH(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity workplane) { + if(Slvs_IsFreeIn3D(workplane)) { + Platform::FatalError("3d workplane given for a 2d constraint"); + } else if(Slvs_IsPoint2D(ptA) && Slvs_IsPoint2D(ptB)) { + return Slvs_AddConstraint(grouph, SLVS_C_SYMMETRIC_HORIZ, workplane, 0., ptA, ptB); + } + Platform::FatalError("Invalid arguments for symmetric horizontal constraint"); +} + +Slvs_Constraint Slvs_SymmetricV(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity workplane) { + if(Slvs_IsFreeIn3D(workplane)) { + Platform::FatalError("3d workplane given for a 2d constraint"); + } else if(Slvs_IsPoint2D(ptA) && Slvs_IsPoint2D(ptB)) { + return Slvs_AddConstraint(grouph, SLVS_C_SYMMETRIC_VERT, workplane, 0., ptA, ptB); + } + Platform::FatalError("Invalid arguments for symmetric vertical constraint"); +} + +Slvs_Constraint Slvs_Midpoint(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity workplane = SLVS_E_FREE_IN_3D) { + if(Slvs_IsPoint(ptA) && Slvs_IsLine(ptB) && (Slvs_IsWorkplane(workplane) || Slvs_IsFreeIn3D(workplane))) { + return Slvs_AddConstraint(grouph, SLVS_C_AT_MIDPOINT, workplane, 0., ptA, SLVS_E_NONE, ptB); + } + Platform::FatalError("Invalid arguments for midpoint constraint"); +} + +Slvs_Constraint Slvs_Horizontal(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity workplane, Slvs_Entity entityB = SLVS_E_NONE) { + if(Slvs_IsFreeIn3D(workplane)) { + Platform::FatalError("Horizontal constraint is not supported in 3D"); + } else if(Slvs_IsLine2D(entityA)) { + return Slvs_AddConstraint(grouph, SLVS_C_HORIZONTAL, workplane, 0., SLVS_E_NONE, SLVS_E_NONE, entityA); + } else if(Slvs_IsPoint2D(entityA) && Slvs_IsPoint2D(entityB)) { + return Slvs_AddConstraint(grouph, SLVS_C_HORIZONTAL, workplane, 0., entityA, entityB); + } + Platform::FatalError("Invalid arguments for horizontal constraint"); +} + +Slvs_Constraint Slvs_Vertical(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity workplane, Slvs_Entity entityB = SLVS_E_NONE) { + if(Slvs_IsFreeIn3D(workplane)) { + Platform::FatalError("Vertical constraint is not supported in 3D"); + } else if(Slvs_IsLine2D(entityA)) { + return Slvs_AddConstraint(grouph, SLVS_C_VERTICAL, workplane, 0., SLVS_E_NONE, SLVS_E_NONE, entityA); + } else if(Slvs_IsPoint2D(entityA) && Slvs_IsPoint2D(entityB)) { + return Slvs_AddConstraint(grouph, SLVS_C_VERTICAL, workplane, 0., entityA, entityB); + } + Platform::FatalError("Invalid arguments for horizontal constraint"); +} + +Slvs_Constraint Slvs_Diameter(uint32_t grouph, Slvs_Entity entityA, double value) { + if(Slvs_IsArc(entityA) || Slvs_IsCircle(entityA)) { + return Slvs_AddConstraint(grouph, SLVS_C_DIAMETER, SLVS_E_FREE_IN_3D, value, SLVS_E_NONE, SLVS_E_NONE, entityA); + } + Platform::FatalError("Invalid arguments for diameter constraint"); +} + +Slvs_Constraint Slvs_SameOrientation(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB) { + if(Slvs_IsNormal3D(entityA) && Slvs_IsNormal3D(entityB)) { + return Slvs_AddConstraint(grouph, SLVS_C_SAME_ORIENTATION, SLVS_E_FREE_IN_3D, 0., SLVS_E_NONE, SLVS_E_NONE, entityA, entityB); + } + Platform::FatalError("Invalid arguments for same orientation constraint"); +} + +Slvs_Constraint Slvs_Angle(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, double value, Slvs_Entity workplane = SLVS_E_FREE_IN_3D, int inverse = 0) { + if(Slvs_IsLine2D(entityA) && Slvs_IsLine2D(entityB) && (Slvs_IsWorkplane(workplane) || Slvs_IsFreeIn3D(workplane))) { + return Slvs_AddConstraint(grouph, SLVS_C_ANGLE, workplane, value, SLVS_E_NONE, SLVS_E_NONE, entityA, entityB, SLVS_E_NONE, SLVS_E_NONE, inverse); + } + Platform::FatalError("Invalid arguments for angle constraint"); +} + +Slvs_Constraint Slvs_Perpendicular(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity workplane = SLVS_E_FREE_IN_3D, int inverse = 0) { + if(Slvs_IsLine2D(entityA) && Slvs_IsLine2D(entityB) && (Slvs_IsWorkplane(workplane) || Slvs_IsFreeIn3D(workplane))) { + return Slvs_AddConstraint(grouph, SLVS_C_PERPENDICULAR, workplane, 0., SLVS_E_NONE, SLVS_E_NONE, entityA, entityB, SLVS_E_NONE, SLVS_E_NONE, inverse); + } + Platform::FatalError("Invalid arguments for perpendicular constraint"); +} + +Slvs_Constraint Slvs_Parallel(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity workplane = SLVS_E_FREE_IN_3D) { + if(Slvs_IsLine2D(entityA) && Slvs_IsLine2D(entityB) && (Slvs_IsWorkplane(workplane) || Slvs_IsFreeIn3D(workplane))) { + return Slvs_AddConstraint(grouph, SLVS_C_PARALLEL, workplane, 0., SLVS_E_NONE, SLVS_E_NONE, entityA, entityB); + } + Platform::FatalError("Invalid arguments for parallel constraint"); +} + +Slvs_Constraint Slvs_Tangent(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity workplane = SLVS_E_FREE_IN_3D) { + if(Slvs_IsArc(entityA) && Slvs_IsLine2D(entityB)) { + if(Slvs_IsFreeIn3D(workplane)) { + Platform::FatalError("3d workplane given for a 2d constraint"); + } + Vector a1 = SK.entity.FindById(hEntity { entityA.point[1] })->PointGetNum(), + a2 = SK.entity.FindById(hEntity { entityA.point[2] })->PointGetNum(); + Vector l0 = SK.entity.FindById(hEntity { entityB.point[0] })->PointGetNum(), + l1 = SK.entity.FindById(hEntity { entityB.point[1] })->PointGetNum(); + int other; + if(l0.Equals(a1) || l1.Equals(a1)) { + other = 0; + } else if(l0.Equals(a2) || l1.Equals(a2)) { + other = 1; + } else { + Platform::FatalError("The tangent arc and line segment must share an " + "endpoint. Constrain them with Constrain -> " + "On Point before constraining tangent."); + } + return Slvs_AddConstraint(grouph, SLVS_C_ARC_LINE_TANGENT, workplane, 0., SLVS_E_NONE, SLVS_E_NONE, entityA, entityB, SLVS_E_NONE, SLVS_E_NONE, other); + } else if(Slvs_IsCubic(entityA) && Slvs_IsLine2D(entityB) && Slvs_IsFreeIn3D(workplane)) { + EntityBase* skEntityA = SK.entity.FindById(hEntity { entityA.h }); + Vector as = skEntityA->CubicGetStartNum(), af = skEntityA->CubicGetFinishNum(); + Vector l0 = SK.entity.FindById(hEntity { entityB.point[0] })->PointGetNum(), + l1 = SK.entity.FindById(hEntity { entityB.point[1] })->PointGetNum(); + int other; + if(l0.Equals(as) || l1.Equals(as)) { + other = 0; + } else if(l0.Equals(af) || l1.Equals(af)) { + other = 1; + } else { + Platform::FatalError("The tangent cubic and line segment must share an " + "endpoint. Constrain them with Constrain -> " + "On Point before constraining tangent."); + } + return Slvs_AddConstraint(grouph, SLVS_C_CUBIC_LINE_TANGENT, workplane, 0., SLVS_E_NONE, SLVS_E_NONE, entityA, entityB, SLVS_E_NONE, SLVS_E_NONE, other); + } else if((Slvs_IsArc(entityA) || Slvs_IsCubic(entityA)) && (Slvs_IsArc(entityB) || Slvs_IsCubic(entityB))) { + if(Slvs_IsFreeIn3D(workplane)) { + Platform::FatalError("3d workplane given for a 2d constraint"); + } + EntityBase* skEntityA = SK.entity.FindById(hEntity { entityA.h }); + EntityBase* skEntityB = SK.entity.FindById(hEntity { entityB.h }); + Vector as = skEntityA->EndpointStart(), af = skEntityA->EndpointFinish(), + bs = skEntityB->EndpointStart(), bf = skEntityB->EndpointFinish(); + int other; + int other2; + if(as.Equals(bs)) { + other = 0; + other2 = 0; + } else if(as.Equals(bf)) { + other = 0; + other2 = 1; + } else if(af.Equals(bs)) { + other = 1; + other2 = 0; + } else if(af.Equals(bf)) { + other = 1; + other2 = 1; + } else { + Platform::FatalError("The curves must share an endpoint. Constrain them " + "with Constrain -> On Point before constraining " + "tangent."); + } + return Slvs_AddConstraint(grouph, SLVS_C_CURVE_CURVE_TANGENT, workplane, 0., SLVS_E_NONE, SLVS_E_NONE, entityA, entityB, SLVS_E_NONE, SLVS_E_NONE, other, other2); + } + Platform::FatalError("Invalid arguments for tangent constraint"); +} + +Slvs_Constraint Slvs_DistanceProj(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity ptB, double value) { + if(Slvs_IsPoint(ptA) && Slvs_IsPoint(ptB)) { + return Slvs_AddConstraint(grouph, SLVS_C_PROJ_PT_DISTANCE, SLVS_E_FREE_IN_3D, value, ptA, ptB); + } + Platform::FatalError("Invalid arguments for projected distance constraint"); +} + +Slvs_Constraint Slvs_LengthDiff(uint32_t grouph, Slvs_Entity entityA, Slvs_Entity entityB, double value, Slvs_Entity workplane = SLVS_E_FREE_IN_3D) { + if(Slvs_IsLine(entityA) && Slvs_IsLine(entityB) && (Slvs_IsWorkplane(workplane) || Slvs_IsFreeIn3D(workplane))) { + return Slvs_AddConstraint(grouph, SLVS_C_LENGTH_DIFFERENCE, workplane, value, SLVS_E_NONE, SLVS_E_NONE, entityA, entityB); + } + Platform::FatalError("Invalid arguments for length difference constraint"); +} + +Slvs_Constraint Slvs_Dragged(uint32_t grouph, Slvs_Entity ptA, Slvs_Entity workplane = SLVS_E_FREE_IN_3D) { + if(Slvs_IsPoint(ptA) && (Slvs_IsWorkplane(workplane) || Slvs_IsFreeIn3D(workplane))) { + return Slvs_AddConstraint(grouph, SLVS_C_WHERE_DRAGGED, workplane, 0., ptA); + } + Platform::FatalError("Invalid arguments for dragged constraint"); +} + +void Slvs_QuaternionU(double qw, double qx, double qy, double qz, + double *x, double *y, double *z) +{ + Quaternion q = Quaternion::From(qw, qx, qy, qz); + Vector v = q.RotationU(); + *x = v.x; + *y = v.y; + *z = v.z; +} + +void Slvs_QuaternionV(double qw, double qx, double qy, double qz, + double *x, double *y, double *z) +{ + Quaternion q = Quaternion::From(qw, qx, qy, qz); + Vector v = q.RotationV(); + *x = v.x; + *y = v.y; + *z = v.z; +} + +void Slvs_QuaternionN(double qw, double qx, double qy, double qz, + double *x, double *y, double *z) +{ + Quaternion q = Quaternion::From(qw, qx, qy, qz); + Vector v = q.RotationN(); + *x = v.x; + *y = v.y; + *z = v.z; +} + +void Slvs_MakeQuaternion(double ux, double uy, double uz, + double vx, double vy, double vz, + double *qw, double *qx, double *qy, double *qz) +{ + Vector u = Vector::From(ux, uy, uz), + v = Vector::From(vx, vy, vz); + Quaternion q = Quaternion::From(u, v); + *qw = q.w; + *qx = q.vx; + *qy = q.vy; + *qz = q.vz; +} + +void Slvs_ClearSketch() +{ + dragged.clear(); + SYS.Clear(); + SK.param.Clear(); + SK.entity.Clear(); + SK.constraint.Clear(); +} + +void Slvs_MarkDragged(Slvs_Entity ptA) { + if(Slvs_IsPoint(ptA)) { + const size_t params = Slvs_IsPoint3D(ptA) ? 3 : 2; + for(size_t i = 0; i < params; ++i) { + hParam p = hParam { ptA.param[i] }; + dragged.insert(p); + } + } else { + SolveSpace::Platform::FatalError("Invalid entity for marking dragged"); + } +} + +Slvs_SolveResult Slvs_SolveSketch(uint32_t shg, Slvs_hConstraint **bad = nullptr) +{ + SYS.Clear(); + + Group g = {}; + g.h.v = shg; + + // add params from entities on sketch + for(EntityBase &ent : SK.entity) { + EntityBase *e = &ent; + // skip entities from other groups + if (e->group.v != shg) { + continue; + } + for (hParam &parh : e->param) { + if (parh.v != 0) { + // get params for this entity and add it to the system + Param *p = SK.GetParam(parh); + p->known = false; + SYS.param.Add(p); + } + } + } + + // add params from constraints + for(ConstraintBase &con : SK.constraint) { + ConstraintBase *c = &con; + if(c->group.v != shg) + continue; + // If we're solving a sketch twice without calling `Slvs_ClearSketch()` in between, + // we already have a constraint param in the sketch, and regeneration would simply + // create another one and orphan the existing one. While this doesn't create any + // correctness issues, it does waste memory, so identify this case and regenerate + // only if we actually need to. + if(c->valP.v) { + SYS.param.Add(SK.GetParam(c->valP)); + continue; + } + // If `valP` is 0, this is either a constraint which doesn't have a param, or one + // which we haven't seen before, so try to regenerate. + // This generates at most a single additional param + c->Generate(&SK.param); + if(c->valP.v) { + SYS.param.Add(SK.GetParam(c->valP)); + + if(Slvs_CanInitiallySatisfy(*c)) { + c->ModifyToSatisfy(); + } + } + } + + // mark dragged params + for(hParam p : dragged) { + SYS.dragged.insert(p); + } + + // for(hParam &par : SYS.dragged) { + // std::cout << "DraggedParam( h:" << par.v << " )\n"; + // } + + // for(Param &par : SYS.param) { + // std::cout << "SysParam( " << par.ToString() << " )\n"; + // } + + // for(EntityBase &ent : SK.entity) { + // std::cout << "SketchEntityBase( " << ent.ToString() << " )\n"; + // } + + // for(ConstraintBase &con : SK.constraint) { + // std::cout << "SketchConstraintBase( " << con.ToString() << " )\n"; + // } + + List badList; + bool andFindBad = bad != nullptr; + + int dof = 0; + SolveResult status = SYS.Solve(&g, &dof, &badList, andFindBad, false, false); + Slvs_SolveResult sr = {}; + sr.dof = dof; + sr.nbad = badList.n; + if(bad) { + if(sr.nbad <= 0) { + *bad = nullptr; + } else { + *bad = static_cast(malloc(sizeof(Slvs_hConstraint) * sr.nbad)); + for(int i = 0; i < sr.nbad; ++i) { + (*bad)[i] = badList[i].v; + } + } + } + sr.result = 0; + switch(status) { + case SolveResult::OKAY: { + sr.result = SLVS_RESULT_OKAY; + return sr; + } + case SolveResult::DIDNT_CONVERGE: { + sr.result = SLVS_RESULT_DIDNT_CONVERGE; + return sr; + } + case SolveResult::REDUNDANT_DIDNT_CONVERGE: { + sr.result = SLVS_RESULT_INCONSISTENT; + return sr; + } + case SolveResult::REDUNDANT_OKAY: { + sr.result = SLVS_RESULT_REDUNDANT_OKAY; + return sr; + } + case SolveResult::TOO_MANY_UNKNOWNS: { + sr.result = SLVS_RESULT_TOO_MANY_UNKNOWNS; + return sr; + } + } + return sr; +} + +double Slvs_GetParamValue(uint32_t ph) +{ + Param* p = SK.param.FindById(hParam { ph }); + return p->val; +} + +void Slvs_SetParamValue(uint32_t ph, double value) +{ + Param* p = SK.param.FindById(hParam { ph }); + p->val = value; +} + +void Slvs_Solve(Slvs_System *ssys, uint32_t shg) +{ + SYS.Clear(); + SK.param.Clear(); + SK.entity.Clear(); + SK.constraint.Clear(); + int i; + for(i = 0; i < ssys->params; i++) { + Slvs_Param *sp = &(ssys->param[i]); + Param p = {}; + + p.h.v = sp->h; + p.val = sp->val; + SK.param.Add(&p); + if(sp->group == shg) { + SYS.param.Add(&p); + } + } + + for(i = 0; i < ssys->entities; i++) { + Slvs_Entity *se = &(ssys->entity[i]); + EntityBase e = {}; + e.type = Slvs_CTypeToEntityBaseType(se->type); + e.h.v = se->h; + e.group.v = se->group; + e.workplane.v = se->wrkpl; + e.point[0].v = se->point[0]; + e.point[1].v = se->point[1]; + e.point[2].v = se->point[2]; + e.point[3].v = se->point[3]; + e.normal.v = se->normal; + e.distance.v = se->distance; + e.param[0].v = se->param[0]; + e.param[1].v = se->param[1]; + e.param[2].v = se->param[2]; + e.param[3].v = se->param[3]; + + SK.entity.Add(&e); + } + ParamList params = {}; + for(i = 0; i < ssys->constraints; i++) { + Slvs_Constraint *sc = &(ssys->constraint[i]); + ConstraintBase c = {}; + c.type = Slvs_CTypeToConstraintBaseType(sc->type); + c.h.v = sc->h; + c.group.v = sc->group; + c.workplane.v = sc->wrkpl; + c.valA = sc->valA; + c.ptA.v = sc->ptA; + c.ptB.v = sc->ptB; + c.entityA.v = sc->entityA; + c.entityB.v = sc->entityB; + c.entityC.v = sc->entityC; + c.entityD.v = sc->entityD; + c.other = (sc->other) ? true : false; + c.other2 = (sc->other2) ? true : false; + + c.Generate(¶ms); + if(!params.IsEmpty()) { + for(Param &p : params) { + p.h = SK.param.AddAndAssignId(&p); + c.valP = p.h; + SYS.param.Add(&p); + } + params.Clear(); + + if(Slvs_CanInitiallySatisfy(c)) { + c.ModifyToSatisfy(); + } + } + + SK.constraint.Add(&c); + } + + for(i = 0; i < ssys->ndragged; i++) { + if(ssys->dragged[i]) { + hParam hp = { ssys->dragged[i] }; + SYS.dragged.insert(hp); + } + } + + Group g = {}; + g.h.v = shg; + + List bad = {}; + + // Now we're finally ready to solve! + bool andFindBad = ssys->calculateFaileds ? true : false; + SolveResult how = SYS.Solve(&g, &(ssys->dof), &bad, andFindBad, /*andFindFree=*/false); + + switch(how) { + case SolveResult::OKAY: + ssys->result = SLVS_RESULT_OKAY; + break; + + case SolveResult::DIDNT_CONVERGE: + ssys->result = SLVS_RESULT_DIDNT_CONVERGE; + break; + + case SolveResult::REDUNDANT_DIDNT_CONVERGE: + ssys->result = SLVS_RESULT_INCONSISTENT; + break; + + case SolveResult::REDUNDANT_OKAY: + ssys->result = SLVS_RESULT_REDUNDANT_OKAY; + break; + + case SolveResult::TOO_MANY_UNKNOWNS: + ssys->result = SLVS_RESULT_TOO_MANY_UNKNOWNS; + break; + } + + // Write the new parameter values back to our caller. + for(i = 0; i < ssys->params; i++) { + Slvs_Param *sp = &(ssys->param[i]); + hParam hp = { sp->h }; + sp->val = SK.GetParam(hp)->val; + } + + if(ssys->failed) { + // Copy over any the list of problematic constraints. + for(i = 0; i < ssys->faileds && i < bad.n; i++) { + ssys->failed[i] = bad[i].v; + } + ssys->faileds = bad.n; + } + + bad.Clear(); + SYS.Clear(); + SK.param.Clear(); + SK.entity.Clear(); + SK.constraint.Clear(); + + Platform::FreeAllTemporary(); +} + +} /* extern "C" */ diff --git a/src/slvs/lib.pyx b/src/slvs/lib.pyx new file mode 100644 index 000000000..84e1b0249 --- /dev/null +++ b/src/slvs/lib.pyx @@ -0,0 +1,391 @@ +#cython: language_level=3 +from enum import IntEnum, auto +from libc.stdint cimport uint32_t +from libc.stdlib cimport free + +cdef extern from "slvs.h" nogil: + ctypedef uint32_t Slvs_hEntity + ctypedef uint32_t Slvs_hGroup + ctypedef uint32_t Slvs_hConstraint + ctypedef uint32_t Slvs_hParam + + ctypedef struct Slvs_Entity: + Slvs_hEntity h + Slvs_hGroup group + int type + Slvs_hEntity wrkpl + Slvs_hEntity point[4] + Slvs_hEntity normal + Slvs_hEntity distance + Slvs_hParam param[4] + + ctypedef struct Slvs_Constraint: + Slvs_hConstraint h + Slvs_hGroup group + int type + Slvs_hEntity wrkpl + double valA + Slvs_hEntity ptA + Slvs_hEntity ptB + Slvs_hEntity entityA + Slvs_hEntity entityB + Slvs_hEntity entityC + Slvs_hEntity entityD + int other + int other2 + + ctypedef struct Slvs_SolveResult: + int result + int dof + int nbad + + void Slvs_QuaternionU(double qw, double qx, double qy, double qz, + double *x, double *y, double *z) + void Slvs_QuaternionV(double qw, double qx, double qy, double qz, + double *x, double *y, double *z) + void Slvs_QuaternionN(double qw, double qx, double qy, double qz, + double *x, double *y, double *z) + + void Slvs_MakeQuaternion(double ux, double uy, double uz, + double vx, double vy, double vz, + double *qw, double *qx, double *qy, double *qz) + + Slvs_Entity Slvs_AddPoint2D(Slvs_hGroup grouph, double u, double v, Slvs_Entity workplane) + Slvs_Entity Slvs_AddPoint3D(Slvs_hGroup grouph, double x, double y, double z) + Slvs_Entity Slvs_AddNormal2D(Slvs_hGroup grouph, Slvs_Entity workplane) + Slvs_Entity Slvs_AddNormal3D(Slvs_hGroup grouph, double qw, double qx, double qy, double qz) + Slvs_Entity Slvs_AddDistance(Slvs_hGroup grouph, double value, Slvs_Entity workplane) + Slvs_Entity Slvs_AddLine2D(Slvs_hGroup grouph, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity workplane) + Slvs_Entity Slvs_AddLine3D(Slvs_hGroup grouph, Slvs_Entity ptA, Slvs_Entity ptB) + Slvs_Entity Slvs_AddCubic(Slvs_hGroup grouph, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity ptC, Slvs_Entity ptD, Slvs_Entity workplane) + Slvs_Entity Slvs_AddArc(Slvs_hGroup grouph, Slvs_Entity normal, Slvs_Entity center, Slvs_Entity start, Slvs_Entity end, Slvs_Entity workplane) + Slvs_Entity Slvs_AddCircle(Slvs_hGroup grouph, Slvs_Entity normal, Slvs_Entity center, Slvs_Entity radius, Slvs_Entity workplane) + Slvs_Entity Slvs_AddWorkplane(Slvs_hGroup grouph, Slvs_Entity origin, Slvs_Entity nm) + Slvs_Entity Slvs_AddBase2D(Slvs_hGroup grouph) + + Slvs_Constraint Slvs_AddConstraint(Slvs_hGroup grouph, int type, Slvs_Entity workplane, double val, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity entityC, Slvs_Entity entityD, int other, int other2) + Slvs_Constraint Slvs_Coincident(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity workplane) + Slvs_Constraint Slvs_Distance(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB, double value, Slvs_Entity workplane) + Slvs_Constraint Slvs_Equal(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity workplane) + Slvs_Constraint Slvs_EqualAngle(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity entityC, Slvs_Entity entityD, Slvs_Entity workplane) + Slvs_Constraint Slvs_EqualPointToLine(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity entityC, Slvs_Entity entityD, Slvs_Entity workplane) + Slvs_Constraint Slvs_Ratio(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB, double value, Slvs_Entity workplane) + Slvs_Constraint Slvs_Symmetric(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity entityC, Slvs_Entity workplane) + Slvs_Constraint Slvs_SymmetricH(Slvs_hGroup grouph, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity workplane) + Slvs_Constraint Slvs_SymmetricV(Slvs_hGroup grouph, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity workplane) + Slvs_Constraint Slvs_Midpoint(Slvs_hGroup grouph, Slvs_Entity ptA, Slvs_Entity ptB, Slvs_Entity workplane) + Slvs_Constraint Slvs_Horizontal(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity workplane, Slvs_Entity entityB) + Slvs_Constraint Slvs_Vertical(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity workplane, Slvs_Entity entityB) + Slvs_Constraint Slvs_Diameter(Slvs_hGroup grouph, Slvs_Entity entityA, double value) + Slvs_Constraint Slvs_SameOrientation(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB) + Slvs_Constraint Slvs_Angle(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB, double value, Slvs_Entity workplane, int inverse) + Slvs_Constraint Slvs_Perpendicular(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity workplane, int inverse) + Slvs_Constraint Slvs_Parallel(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity workplane) + Slvs_Constraint Slvs_Tangent(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB, Slvs_Entity workplane) + Slvs_Constraint Slvs_DistanceProj(Slvs_hGroup grouph, Slvs_Entity ptA, Slvs_Entity ptB, double value) + Slvs_Constraint Slvs_LengthDiff(Slvs_hGroup grouph, Slvs_Entity entityA, Slvs_Entity entityB, double value, Slvs_Entity workplane) + Slvs_Constraint Slvs_Dragged(Slvs_hGroup grouph, Slvs_Entity ptA, Slvs_Entity workplane) + + void Slvs_MarkDragged(Slvs_Entity ptA) + Slvs_SolveResult Slvs_SolveSketch(Slvs_hGroup hg, Slvs_hConstraint **bad) nogil + double Slvs_GetParamValue(int ph) + double Slvs_SetParamValue(int ph, double value) + void Slvs_ClearSketch() + + cdef Slvs_Entity _E_NONE "SLVS_E_NONE" + cdef Slvs_Entity _E_FREE_IN_3D "SLVS_E_FREE_IN_3D" + + cdef int _SLVS_C_POINTS_COINCIDENT "SLVS_C_POINTS_COINCIDENT" + cdef int _SLVS_C_PT_PT_DISTANCE "SLVS_C_PT_PT_DISTANCE" + cdef int _SLVS_C_PT_PLANE_DISTANCE "SLVS_C_PT_PLANE_DISTANCE" + cdef int _SLVS_C_PT_LINE_DISTANCE "SLVS_C_PT_LINE_DISTANCE" + cdef int _SLVS_C_PT_FACE_DISTANCE "SLVS_C_PT_FACE_DISTANCE" + cdef int _SLVS_C_PT_IN_PLANE "SLVS_C_PT_IN_PLANE" + cdef int _SLVS_C_PT_ON_LINE "SLVS_C_PT_ON_LINE" + cdef int _SLVS_C_PT_ON_FACE "SLVS_C_PT_ON_FACE" + cdef int _SLVS_C_EQUAL_LENGTH_LINES "SLVS_C_EQUAL_LENGTH_LINES" + cdef int _SLVS_C_LENGTH_RATIO "SLVS_C_LENGTH_RATIO" + cdef int _SLVS_C_EQ_LEN_PT_LINE_D "SLVS_C_EQ_LEN_PT_LINE_D" + cdef int _SLVS_C_EQ_PT_LN_DISTANCES "SLVS_C_EQ_PT_LN_DISTANCES" + cdef int _SLVS_C_EQUAL_ANGLE "SLVS_C_EQUAL_ANGLE" + cdef int _SLVS_C_EQUAL_LINE_ARC_LEN "SLVS_C_EQUAL_LINE_ARC_LEN" + cdef int _SLVS_C_SYMMETRIC "SLVS_C_SYMMETRIC" + cdef int _SLVS_C_SYMMETRIC_HORIZ "SLVS_C_SYMMETRIC_HORIZ" + cdef int _SLVS_C_SYMMETRIC_VERT "SLVS_C_SYMMETRIC_VERT" + cdef int _SLVS_C_SYMMETRIC_LINE "SLVS_C_SYMMETRIC_LINE" + cdef int _SLVS_C_AT_MIDPOINT "SLVS_C_AT_MIDPOINT" + cdef int _SLVS_C_HORIZONTAL "SLVS_C_HORIZONTAL" + cdef int _SLVS_C_VERTICAL "SLVS_C_VERTICAL" + cdef int _SLVS_C_DIAMETER "SLVS_C_DIAMETER" + cdef int _SLVS_C_PT_ON_CIRCLE "SLVS_C_PT_ON_CIRCLE" + cdef int _SLVS_C_SAME_ORIENTATION "SLVS_C_SAME_ORIENTATION" + cdef int _SLVS_C_ANGLE "SLVS_C_ANGLE" + cdef int _SLVS_C_PARALLEL "SLVS_C_PARALLEL" + cdef int _SLVS_C_PERPENDICULAR "SLVS_C_PERPENDICULAR" + cdef int _SLVS_C_ARC_LINE_TANGENT "SLVS_C_ARC_LINE_TANGENT" + cdef int _SLVS_C_CUBIC_LINE_TANGENT "SLVS_C_CUBIC_LINE_TANGENT" + cdef int _SLVS_C_EQUAL_RADIUS "SLVS_C_EQUAL_RADIUS" + cdef int _SLVS_C_PROJ_PT_DISTANCE "SLVS_C_PROJ_PT_DISTANCE" + cdef int _SLVS_C_WHERE_DRAGGED "SLVS_C_WHERE_DRAGGED" + cdef int _SLVS_C_CURVE_CURVE_TANGENT "SLVS_C_CURVE_CURVE_TANGENT" + cdef int _SLVS_C_LENGTH_DIFFERENCE "SLVS_C_LENGTH_DIFFERENCE" + cdef int _SLVS_C_ARC_ARC_LEN_RATIO "SLVS_C_ARC_ARC_LEN_RATIO" + cdef int _SLVS_C_ARC_LINE_LEN_RATIO "SLVS_C_ARC_LINE_LEN_RATIO" + cdef int _SLVS_C_ARC_ARC_DIFFERENCE "SLVS_C_ARC_ARC_DIFFERENCE" + cdef int _SLVS_C_ARC_LINE_DIFFERENCE "SLVS_C_ARC_LINE_DIFFERENCE" + + cdef int _SLVS_E_POINT_IN_3D "SLVS_E_POINT_IN_3D" + cdef int _SLVS_E_POINT_IN_2D "SLVS_E_POINT_IN_2D" + cdef int _SLVS_E_NORMAL_IN_3D "SLVS_E_NORMAL_IN_3D" + cdef int _SLVS_E_NORMAL_IN_2D "SLVS_E_NORMAL_IN_2D" + cdef int _SLVS_E_DISTANCE "SLVS_E_DISTANCE" + cdef int _SLVS_E_WORKPLANE "SLVS_E_WORKPLANE" + cdef int _SLVS_E_LINE_SEGMENT "SLVS_E_LINE_SEGMENT" + cdef int _SLVS_E_CUBIC "SLVS_E_CUBIC" + cdef int _SLVS_E_CIRCLE "SLVS_E_CIRCLE" + cdef int _SLVS_E_ARC_OF_CIRCLE "SLVS_E_ARC_OF_CIRCLE" + + cdef int _SLVS_RESULT_OKAY "SLVS_RESULT_OKAY" + cdef int _SLVS_RESULT_INCONSISTENT "SLVS_RESULT_INCONSISTENT" + cdef int _SLVS_RESULT_DIDNT_CONVERGE "SLVS_RESULT_DIDNT_CONVERGE" + cdef int _SLVS_RESULT_TOO_MANY_UNKNOWNS "SLVS_RESULT_TOO_MANY_UNKNOWNS" + cdef int _SLVS_RESULT_REDUNDANT_OKAY "SLVS_RESULT_REDUNDANT_OKAY" + +E_NONE = _E_NONE +E_FREE_IN_3D = _E_FREE_IN_3D + +# quaternion +cpdef tuple quaternion_u(double qw, double qx, double qy, double qz): + """Input quaternion, return unit vector of U axis. + Where `qw`, `qx`, `qy`, `qz` are corresponded to the W, X, Y, Z value of + quaternion. + """ + cdef double x, y, z + Slvs_QuaternionU(qw, qx, qy, qz, &x, &y, &z) + return x, y, z + + +cpdef tuple quaternion_v(double qw, double qx, double qy, double qz): + """Input quaternion, return unit vector of V axis. + Signature is same as [quaternion_u](#quaternion_u). + """ + cdef double x, y, z + Slvs_QuaternionV(qw, qx, qy, qz, &x, &y, &z) + return x, y, z + + +cpdef tuple quaternion_n(double qw, double qx, double qy, double qz): + """Input quaternion, return unit vector of normal. + Signature is same as [quaternion_u](#quaternion_u). + """ + cdef double x, y, z + Slvs_QuaternionN(qw, qx, qy, qz, &x, &y, &z) + return x, y, z + + +cpdef tuple make_quaternion(double ux, double uy, double uz, double vx, double vy, double vz): + """Input two unit vector, return quaternion. + Where `ux`, `uy`, `uz` are corresponded to the value of U vector; + `vx`, `vy`, `vz` are corresponded to the value of V vector. + """ + cdef double qw, qx, qy, qz + Slvs_MakeQuaternion(ux, uy, uz, vx, vy, vz, &qw, &qx, &qy, &qz) + return qw, qx, qy, qz + +# entities +def add_point_2d(grouph: int, u: float, v: float, workplane: Slvs_Entity) -> Slvs_Entity: + return Slvs_AddPoint2D(grouph, u, v, workplane) + +def add_point_3d(grouph: int, x: float, y: float, z: float) -> Slvs_Entity: + return Slvs_AddPoint3D(grouph, x, y, z) + +def add_normal_2d(grouph: int, workplane: Slvs_Entity) -> Slvs_Entity: + return Slvs_AddNormal2D(grouph, workplane) + +def add_normal_3d(grouph: int, qw: float, qx: float, qy: float, qz: float) -> Slvs_Entity: + return Slvs_AddNormal3D(grouph, qw, qx, qy, qz) + +def add_distance(grouph: int, value: float, workplane: Slvs_Entity) -> Slvs_Entity: + return Slvs_AddDistance(grouph, value, workplane) + +def add_line_2d(grouph: int, ptA: Slvs_Entity, ptB: Slvs_Entity, workplane: Slvs_Entity) -> Slvs_Entity: + return Slvs_AddLine2D(grouph, ptA, ptB, workplane) + +def add_line_3d(grouph: int, ptA: Slvs_Entity, ptB: Slvs_Entity) -> Slvs_Entity: + return Slvs_AddLine3D(grouph, ptA, ptB) + +def add_cubic(grouph: int, ptA: Slvs_Entity, ptB: Slvs_Entity, ptC: Slvs_Entity, ptD: Slvs_Entity, workplane: Slvs_Entity) -> Slvs_Entity: + return Slvs_AddCubic(grouph, ptA, ptB, ptC, ptD, workplane) + +def add_arc(grouph: int, normal: Slvs_Entity, center: Slvs_Entity, start: Slvs_Entity, end: Slvs_Entity, workplane: Slvs_Entity) -> Slvs_Entity: + return Slvs_AddArc(grouph, normal, center, start, end, workplane) + +def add_circle(grouph: int, normal: Slvs_Entity, center: Slvs_Entity, radius: Slvs_Entity, workplane: Slvs_Entity) -> Slvs_Entity: + return Slvs_AddCircle(grouph, normal, center, radius, workplane) + +def add_workplane(grouph: int, origin: Slvs_Entity, nm: Slvs_Entity) -> Slvs_Entity: + return Slvs_AddWorkplane(grouph, origin, nm) + +def add_base_2d(grouph: int) -> Slvs_Entity: + return Slvs_AddBase2D(grouph) + +# constraints +def add_constraint(grouph: int, c_type: int, workplane: Slvs_Entity, val: float, ptA: Slvs_Entity = E_NONE, + ptB: Slvs_Entity = E_NONE, entityA: Slvs_Entity = E_NONE, + entityB: Slvs_Entity = E_NONE, entityC: Slvs_Entity = E_NONE, + entityD: Slvs_Entity = E_NONE, other: int = 0, other2: int = 0) -> Slvs_Constraint: + return Slvs_AddConstraint(grouph, c_type, workplane, val, ptA, ptB, entityA, entityB, entityC, entityD, other, other2) + +def coincident(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity, workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_Coincident(grouph, entityA, entityB, workplane) + +def distance(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity, value: float, workplane: Slvs_Entity) -> Slvs_Constraint: + return Slvs_Distance(grouph, entityA, entityB, value, workplane) + +def equal(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity, workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_Equal(grouph, entityA, entityB, workplane) + +def equal_angle(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity, entityC: Slvs_Entity, + entityD: Slvs_Entity, + workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_EqualAngle(grouph, entityA, entityB, entityC, entityD, workplane) + +def equal_point_to_line(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity, + entityC: Slvs_Entity, entityD: Slvs_Entity, + workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_EqualPointToLine(grouph, entityA, entityB, entityC, entityD, workplane) + +def ratio(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity, value: float, workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_Ratio(grouph, entityA, entityB, value, workplane) + +def symmetric(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity, entityC: Slvs_Entity = E_NONE, workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_Symmetric(grouph, entityA, entityB, entityC, workplane) + +def symmetric_h(grouph: int, ptA: Slvs_Entity, ptB: Slvs_Entity, workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_SymmetricH(grouph, ptA, ptB, workplane) + +def symmetric_v(grouph: int, ptA: Slvs_Entity, ptB: Slvs_Entity, workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_SymmetricV(grouph, ptA, ptB, workplane) + +def midpoint(grouph: int, ptA: Slvs_Entity, ptB: Slvs_Entity, workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_Midpoint(grouph, ptA, ptB, workplane) + +def horizontal(grouph: int, entityA: Slvs_Entity, workplane: Slvs_Entity, entityB: Slvs_Entity = E_NONE) -> Slvs_Constraint: + return Slvs_Horizontal(grouph, entityA, workplane, entityB) + +def vertical(grouph: int, entityA: Slvs_Entity, workplane: Slvs_Entity, entityB: Slvs_Entity = E_NONE) -> Slvs_Constraint: + return Slvs_Vertical(grouph, entityA, workplane, entityB) + +def diameter(grouph: int, entityA: Slvs_Entity, value: float) -> Slvs_Constraint: + return Slvs_Diameter(grouph, entityA, value) + +def same_orientation(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity) -> Slvs_Constraint: + return Slvs_SameOrientation(grouph, entityA, entityB) + +def angle(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity, value: float, workplane: Slvs_Entity = E_FREE_IN_3D, inverse: bool = False) -> Slvs_Constraint: + return Slvs_Angle(grouph, entityA, entityB, value, workplane, inverse) + +def perpendicular(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity, workplane: Slvs_Entity = E_FREE_IN_3D, inverse: bool = False) -> Slvs_Constraint: + return Slvs_Perpendicular(grouph, entityA, entityB, workplane, inverse) + +def parallel(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity, workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_Parallel(grouph, entityA, entityB, workplane) + +def tangent(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity, workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_Tangent(grouph, entityA, entityB, workplane) + +def distance_proj(grouph: int, ptA: Slvs_Entity, ptB: Slvs_Entity, value: float) -> Slvs_Constraint: + return Slvs_DistanceProj(grouph, ptA, ptB, value) + +def length_diff(grouph: int, entityA: Slvs_Entity, entityB: Slvs_Entity, value: float, workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_LengthDiff(grouph, entityA, entityB, value, workplane) + +def dragged(grouph: int, ptA: Slvs_Entity, workplane: Slvs_Entity = E_FREE_IN_3D) -> Slvs_Constraint: + return Slvs_Dragged(grouph, ptA, workplane) + +# solver +class ResultFlag(IntEnum): + """Symbol of the result flags.""" + OKAY = _SLVS_RESULT_OKAY + INCONSISTENT = _SLVS_RESULT_INCONSISTENT + DIDNT_CONVERGE = _SLVS_RESULT_DIDNT_CONVERGE + TOO_MANY_UNKNOWNS = _SLVS_RESULT_TOO_MANY_UNKNOWNS + REDUNDANT_OKAY = _SLVS_RESULT_REDUNDANT_OKAY + +class ConstraintType(IntEnum): + """Symbol of the constraint types.""" + POINTS_COINCIDENT = _SLVS_C_POINTS_COINCIDENT + PT_PT_DISTANCE = _SLVS_C_PT_PT_DISTANCE + PT_PLANE_DISTANCE = _SLVS_C_PT_PLANE_DISTANCE + PT_LINE_DISTANCE = _SLVS_C_PT_LINE_DISTANCE + PT_FACE_DISTANCE = _SLVS_C_PT_FACE_DISTANCE + PT_IN_PLANE = _SLVS_C_PT_IN_PLANE + PT_ON_LINE = _SLVS_C_PT_ON_LINE + PT_ON_FACE = _SLVS_C_PT_ON_FACE + EQUAL_LENGTH_LINES = _SLVS_C_EQUAL_LENGTH_LINES + LENGTH_RATIO = _SLVS_C_LENGTH_RATIO + EQ_LEN_PT_LINE_D = _SLVS_C_EQ_LEN_PT_LINE_D + EQ_PT_LN_DISTANCES = _SLVS_C_EQ_PT_LN_DISTANCES + EQUAL_ANGLE = _SLVS_C_EQUAL_ANGLE + EQUAL_LINE_ARC_LEN = _SLVS_C_EQUAL_LINE_ARC_LEN + SYMMETRIC = _SLVS_C_SYMMETRIC + SYMMETRIC_HORIZ = _SLVS_C_SYMMETRIC_HORIZ + SYMMETRIC_VERT = _SLVS_C_SYMMETRIC_VERT + SYMMETRIC_LINE = _SLVS_C_SYMMETRIC_LINE + AT_MIDPOINT = _SLVS_C_AT_MIDPOINT + HORIZONTAL = _SLVS_C_HORIZONTAL + VERTICAL = _SLVS_C_VERTICAL + DIAMETER = _SLVS_C_DIAMETER + PT_ON_CIRCLE = _SLVS_C_PT_ON_CIRCLE + SAME_ORIENTATION = _SLVS_C_SAME_ORIENTATION + ANGLE = _SLVS_C_ANGLE + PARALLEL = _SLVS_C_PARALLEL + PERPENDICULAR = _SLVS_C_PERPENDICULAR + ARC_LINE_TANGENT = _SLVS_C_ARC_LINE_TANGENT + CUBIC_LINE_TANGENT = _SLVS_C_CUBIC_LINE_TANGENT + EQUAL_RADIUS = _SLVS_C_EQUAL_RADIUS + PROJ_PT_DISTANCE = _SLVS_C_PROJ_PT_DISTANCE + WHERE_DRAGGED = _SLVS_C_WHERE_DRAGGED + CURVE_CURVE_TANGENT = _SLVS_C_CURVE_CURVE_TANGENT + LENGTH_DIFFERENCE = _SLVS_C_LENGTH_DIFFERENCE + ARC_ARC_LEN_RATIO = _SLVS_C_ARC_ARC_LEN_RATIO + ARC_LINE_LEN_RATIO = _SLVS_C_ARC_LINE_LEN_RATIO + ARC_ARC_DIFFERENCE = _SLVS_C_ARC_ARC_DIFFERENCE + ARC_LINE_DIFFERENCE = _SLVS_C_ARC_LINE_DIFFERENCE + +class EntityType(IntEnum): + POINT_IN_3D = _SLVS_E_POINT_IN_3D + POINT_IN_2D = _SLVS_E_POINT_IN_2D + NORMAL_IN_3D = _SLVS_E_NORMAL_IN_3D + NORMAL_IN_2D = _SLVS_E_NORMAL_IN_2D + DISTANCE = _SLVS_E_DISTANCE + WORKPLANE = _SLVS_E_WORKPLANE + LINE_SEGMENT = _SLVS_E_LINE_SEGMENT + CUBIC = _SLVS_E_CUBIC + CIRCLE = _SLVS_E_CIRCLE + ARC_OF_CIRCLE = _SLVS_E_ARC_OF_CIRCLE + +def mark_dragged(ptA: Slvs_Entity): + Slvs_MarkDragged(ptA) + +def solve_sketch(grouph: int, calculateFaileds: bool): + cdef Slvs_hConstraint *badp = NULL + if not calculateFaileds: + return Slvs_SolveSketch(grouph, NULL) + else: + result = Slvs_SolveSketch(grouph, &badp) + bad = [] + if badp != NULL: + for i in range(0, result.nbad): + bad.append(badp[i]) + free(badp) + return result, bad + +def get_param_value(ph: int): + return Slvs_GetParamValue(ph) + +def set_param_value(ph: int, value: float): + Slvs_SetParamValue(ph, value) + +def clear_sketch(): + Slvs_ClearSketch() diff --git a/src/solvespace.cpp b/src/solvespace.cpp index 4e458cf48..2fdf7b6a3 100644 --- a/src/solvespace.cpp +++ b/src/solvespace.cpp @@ -7,8 +7,10 @@ #include "solvespace.h" #include "config.h" -SolveSpaceUI SolveSpace::SS = {}; -Sketch SolveSpace::SK = {}; +namespace SolveSpace { + +SolveSpaceUI SS = {}; +Sketch SK = {}; void SolveSpaceUI::Init() { #if !defined(HEADLESS) @@ -19,6 +21,7 @@ void SolveSpaceUI::Init() { Platform::SettingsRef settings = Platform::GetSettings(); SS.tangentArcRadius = 10.0; + SS.explodeDistance = 1.0; // Then, load the registry settings. // Default list of colors for the model material @@ -53,6 +56,8 @@ void SolveSpaceUI::Init() { exportMaxSegments = settings->ThawInt("ExportMaxSegments", 64); // Timeout value for finding redundant constrains (ms) timeoutRedundantConstr = settings->ThawInt("TimeoutRedundantConstraints", 1000); + // Animation speed calculation base time (ms) + animationSpeed = settings->ThawInt("AnimationSpeed", 800); // View units viewUnits = (Unit)settings->ThawInt("ViewUnits", (uint32_t)Unit::MM); // Number of digits after the decimal point @@ -68,12 +73,18 @@ void SolveSpaceUI::Init() { exportScale = settings->ThawFloat("ExportScale", 1.0); // Export offset (cutter radius comp) exportOffset = settings->ThawFloat("ExportOffset", 0.0); + // Dimensions on arcs default to diameter vs radius + arcDimDefaultDiameter = settings->ThawBool("ArcDimDefaultDiameter", false); + // Show full file path in the menu bar + showFullFilePath = settings->ThawBool("ShowFullFilePath", true); // Rewrite exported colors close to white into black (assuming white bg) fixExportColors = settings->ThawBool("FixExportColors", true); // Export background color exportBackgroundColor = settings->ThawBool("ExportBackgroundColor", false); // Draw back faces of triangles (when mesh is leaky/self-intersecting) drawBackFaces = settings->ThawBool("DrawBackFaces", true); + // Use camera mouse navigation + cameraNav = settings->ThawBool("CameraNav", false); // Use turntable mouse navigation turntableNav = settings->ThawBool("TurntableNav", false); // Immediately edit dimension @@ -104,6 +115,7 @@ void SolveSpaceUI::Init() { exportCanvas.dy = settings->ThawFloat("ExportCanvas_Dy", 5.0); // Extra parameters when exporting G code gCode.depth = settings->ThawFloat("GCode_Depth", 10.0); + gCode.safeHeight = settings->ThawFloat("GCode_SafeHeight", 5.0); gCode.passes = settings->ThawInt("GCode_Passes", 1); gCode.feed = settings->ThawFloat("GCode_Feed", 10.0); gCode.plungeFeed = settings->ThawFloat("GCode_PlungeFeed", 10.0); @@ -123,12 +135,8 @@ void SolveSpaceUI::Init() { SetLocale(locale); } - generateAllTimer = Platform::CreateTimer(); - generateAllTimer->onTimeout = std::bind(&SolveSpaceUI::GenerateAll, &SS, Generate::DIRTY, - /*andFindFree=*/false, /*genForBBox=*/false); - - showTWTimer = Platform::CreateTimer(); - showTWTimer->onTimeout = std::bind(&TextWindow::Show, &TW); + refreshTimer = Platform::CreateTimer(); + refreshTimer->onTimeout = std::bind(&SolveSpaceUI::Refresh, &SS); autosaveTimer = Platform::CreateTimer(); autosaveTimer->onTimeout = std::bind(&SolveSpaceUI::Autosave, &SS); @@ -235,6 +243,8 @@ void SolveSpaceUI::Exit() { settings->FreezeInt("ExportMaxSegments", (uint32_t)exportMaxSegments); // Timeout for finding which constraints to fix Jacobian settings->FreezeInt("TimeoutRedundantConstraints", (uint32_t)timeoutRedundantConstr); + // Animation speed + settings->FreezeInt("AnimationSpeed", (uint32_t)animationSpeed); // View units settings->FreezeInt("ViewUnits", (uint32_t)viewUnits); // Number of digits after the decimal point @@ -250,6 +260,10 @@ void SolveSpaceUI::Exit() { settings->FreezeFloat("ExportScale", exportScale); // Export offset (cutter radius comp) settings->FreezeFloat("ExportOffset", exportOffset); + // Rewrite the default arc dimension setting + settings->FreezeBool("ArcDimDefaultDiameter", arcDimDefaultDiameter); + // Show full file path in the menu bar + settings->FreezeBool("ShowFullFilePath", showFullFilePath); // Rewrite exported colors close to white into black (assuming white bg) settings->FreezeBool("FixExportColors", fixExportColors); // Export background color @@ -260,6 +274,8 @@ void SolveSpaceUI::Exit() { settings->FreezeBool("ShowContourAreas", showContourAreas); // Check that contours are closed and not self-intersecting settings->FreezeBool("CheckClosedContour", checkClosedContour); + // Use camera mouse navigation + settings->FreezeBool("CameraNav", cameraNav); // Use turntable mouse navigation settings->FreezeBool("TurntableNav", turntableNav); // Immediately edit dimensions @@ -300,12 +316,28 @@ void SolveSpaceUI::Exit() { Platform::ExitGui(); } +void SolveSpaceUI::Refresh() { + // generateAll must happen bfore updating displays + if(scheduledGenerateAll) { + // Clear the flag so that if the call to GenerateAll is blocked by a Message or Error, + // subsequent refreshes do not try to Generate again. + scheduledGenerateAll = false; + GenerateAll(Generate::DIRTY, /*andFindFree=*/false, /*genForBBox=*/false); + } + if(scheduledShowTW) { + scheduledShowTW = false; + TW.Show(); + } +} + void SolveSpaceUI::ScheduleGenerateAll() { - generateAllTimer->RunAfterProcessingEvents(); + scheduledGenerateAll = true; + refreshTimer->RunAfterProcessingEvents(); } void SolveSpaceUI::ScheduleShowTW() { - showTWTimer->RunAfterProcessingEvents(); + scheduledShowTW = true; + refreshTimer->RunAfterProcessingEvents(); } void SolveSpaceUI::ScheduleAutosave() { @@ -315,6 +347,7 @@ void SolveSpaceUI::ScheduleAutosave() { double SolveSpaceUI::MmPerUnit() { switch(viewUnits) { case Unit::INCHES: return 25.4; + case Unit::FEET_INCHES: return 25.4; // The 'unit' is still inches case Unit::METERS: return 1000.0; case Unit::MM: return 1.0; } @@ -323,14 +356,47 @@ double SolveSpaceUI::MmPerUnit() { const char *SolveSpaceUI::UnitName() { switch(viewUnits) { case Unit::INCHES: return "in"; + case Unit::FEET_INCHES: return "in"; case Unit::METERS: return "m"; case Unit::MM: return "mm"; } return ""; } -std::string SolveSpaceUI::MmToString(double v) { +std::string SolveSpaceUI::MmToString(double v, bool editable) { v /= MmPerUnit(); + // The syntax 2' 6" for feet and inches is not something we can (currently) + // parse back from a string so if editable is true, we treat FEET_INCHES the + // same as INCHES and just return the unadorned decimal number of inches. + if(viewUnits == Unit::FEET_INCHES && !editable) { + // Now convert v from inches to 64'ths of an inch, to make rounding easier. + v = floor((v + (1.0 / 128.0)) * 64.0); + int feet = (int)(v / (12.0 * 64.0)); + v = v - (feet * 12.0 * 64.0); + // v is now the feet-less remainder in 1/64 inches + int inches = (int)(v / 64.0); + int numerator = (int)(v - ((double)inches * 64.0)); + int denominator = 64; + // Divide down to smallest denominator where the numerator is still a whole number + while ((numerator != 0) && ((numerator & 1) == 0)) { + numerator /= 2; + denominator /= 2; + } + std::ostringstream str; + if(feet != 0) { + str << feet << "'-"; + } + // For something like 0.5, show 1/2" rather than 0 1/2" + if(!(feet == 0 && inches == 0 && numerator != 0)) { + str << inches; + } + if(numerator != 0) { + str << " " << numerator << "/" << denominator; + } + str << "\""; + return str.str(); + } + int digits = UnitDigitsAfterDecimal(); double minimum = 0.5 * pow(10,-digits); while ((v < minimum) && (v > LENGTH_EPS)) { @@ -349,7 +415,7 @@ static const char *DimToString(int dim) { } static std::pair SelectSIPrefixMm(int ord, int dim) { // decide what units to use depending on the order of magnitude of the -// measure in meters and the dimmension (1,2,3 lenear, area, volume) +// measure in meters and the dimension (1,2,3 lenear, area, volume) switch(dim) { case 0: case 1: @@ -378,7 +444,7 @@ static std::pair SelectSIPrefixMm(int ord, int dim) { default: dbp ("dimensions over 3 not supported"); break; - } + } return {0, "m"}; } static std::pair SelectSIPrefixInch(int deg) { @@ -394,17 +460,22 @@ std::string SolveSpaceUI::MmToStringSI(double v, int dim) { dim = 1; } - v /= pow((viewUnits == Unit::INCHES) ? 25.4 : 1000, dim); + bool inches = (viewUnits == Unit::INCHES) || (viewUnits == Unit::FEET_INCHES); + v /= pow(inches ? 25.4 : 1000, dim); int vdeg = (int)(log10(fabs(v))); std::string unit; if(fabs(v) > 0.0) { int sdeg = 0; std::tie(sdeg, unit) = - (viewUnits == Unit::INCHES) + inches ? SelectSIPrefixInch(vdeg/dim) : SelectSIPrefixMm(vdeg, dim); v /= pow(10.0, sdeg * dim); } + if(viewUnits == Unit::FEET_INCHES && fabs(v) > pow(12.0, dim)) { + unit = "ft"; + v /= pow(12.0, dim); + } int pdeg = (int)ceil(log10(fabs(v) + 1e-10)); return ssprintf("%.*g%s%s%s", pdeg + UnitDigitsAfterDecimal(), v, compact ? "" : " ", unit.c_str(), DimToString(dim)); @@ -434,10 +505,11 @@ int SolveSpaceUI::GetMaxSegments() { return maxSegments; } int SolveSpaceUI::UnitDigitsAfterDecimal() { - return (viewUnits == Unit::INCHES) ? afterDecimalInch : afterDecimalMm; + return (viewUnits == Unit::INCHES || viewUnits == Unit::FEET_INCHES) ? + afterDecimalInch : afterDecimalMm; } void SolveSpaceUI::SetUnitDigitsAfterDecimal(int v) { - if(viewUnits == Unit::INCHES) { + if(viewUnits == Unit::INCHES || viewUnits == Unit::FEET_INCHES) { afterDecimalInch = v; } else { afterDecimalMm = v; @@ -508,12 +580,18 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) { if(saveAs || saveFile.IsEmpty()) { Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(GW.window); + // FIXME(emscripten): + dbp("Calling AddFilter()..."); dialog->AddFilter(C_("file-type", "SolveSpace models"), { SKETCH_EXT }); + dbp("Calling ThawChoices()..."); dialog->ThawChoices(settings, "Sketch"); if(!newSaveFile.IsEmpty()) { + dbp("Calling SetFilename()..."); dialog->SetFilename(newSaveFile); } + dbp("Calling RunModal()..."); if(dialog->RunModal()) { + dbp("Calling FreezeChoices()..."); dialog->FreezeChoices(settings, "Sketch"); newSaveFile = dialog->GetFilename(); } else { @@ -526,6 +604,9 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) { RemoveAutosave(); saveFile = newSaveFile; unsaved = false; + if (this->OnSaveFinished) { + this->OnSaveFinished(newSaveFile, saveAs, false); + } return true; } else { return false; @@ -537,7 +618,11 @@ void SolveSpaceUI::Autosave() ScheduleAutosave(); 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); + } } } @@ -555,7 +640,7 @@ bool SolveSpaceUI::OkayToStartNewFile() { using Platform::MessageDialog; dialog->SetType(MessageDialog::Type::QUESTION); dialog->SetTitle(C_("title", "Modified File")); - if(!SolveSpace::SS.saveFile.IsEmpty()) { + if(!SS.saveFile.IsEmpty()) { dialog->SetMessage(ssprintf(C_("dialog", "Do you want to save the changes you made to " "the sketch “%s”?"), saveFile.raw.c_str())); } else { @@ -589,7 +674,11 @@ void SolveSpaceUI::UpdateWindowTitles() { GW.window->SetTitle(C_("title", "(new sketch)")); } else { if(!GW.window->SetTitleForFilename(saveFile)) { - GW.window->SetTitle(saveFile.raw); + if(SS.showFullFilePath) { + GW.window->SetTitle(saveFile.raw); + } else { + GW.window->SetTitle(saveFile.raw.substr(saveFile.raw.find_last_of("/\\") + 1)); + } } } @@ -637,6 +726,9 @@ void SolveSpaceUI::MenuFile(Command id) { if(dialog->RunModal()) { dialog->FreezeChoices(settings, "ExportImage"); SS.ExportAsPngTo(dialog->GetFilename()); + if (SS.OnSaveFinished) { + SS.OnSaveFinished(dialog->GetFilename(), false, false); + } } break; } @@ -651,10 +743,8 @@ void SolveSpaceUI::MenuFile(Command id) { // If the user is exporting something where it would be // inappropriate to include the constraints, then warn. - if(SS.GW.showConstraints && - (dialog->GetFilename().HasExtension("txt") || - fabs(SS.exportOffset) > LENGTH_EPS)) - { + if(SS.GW.showConstraints != GraphicsWindow::ShowConstraintMode::SCM_NOSHOW && + (dialog->GetFilename().HasExtension("txt") || fabs(SS.exportOffset) > LENGTH_EPS)) { Message(_("Constraints are currently shown, and will be exported " "in the toolpath. This is probably not what you want; " "hide them by clicking the link at the top of the " @@ -662,6 +752,9 @@ void SolveSpaceUI::MenuFile(Command id) { } SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe=*/false); + if (SS.OnSaveFinished) { + SS.OnSaveFinished(dialog->GetFilename(), false, false); + } break; } @@ -674,6 +767,9 @@ void SolveSpaceUI::MenuFile(Command id) { dialog->FreezeChoices(settings, "ExportWireframe"); SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe*/true); + if (SS.OnSaveFinished) { + SS.OnSaveFinished(dialog->GetFilename(), false, false); + } break; } @@ -686,6 +782,9 @@ void SolveSpaceUI::MenuFile(Command id) { dialog->FreezeChoices(settings, "ExportSection"); SS.ExportSectionTo(dialog->GetFilename()); + if (SS.OnSaveFinished) { + SS.OnSaveFinished(dialog->GetFilename(), false, false); + } break; } @@ -698,6 +797,10 @@ void SolveSpaceUI::MenuFile(Command id) { dialog->FreezeChoices(settings, "ExportMesh"); SS.ExportMeshTo(dialog->GetFilename()); + if (SS.OnSaveFinished) { + SS.OnSaveFinished(dialog->GetFilename(), false, false); + } + break; } @@ -711,6 +814,9 @@ void SolveSpaceUI::MenuFile(Command id) { StepFileWriter sfw = {}; sfw.ExportSurfacesTo(dialog->GetFilename()); + if (SS.OnSaveFinished) { + SS.OnSaveFinished(dialog->GetFilename(), false, false); + } break; } @@ -764,7 +870,11 @@ void SolveSpaceUI::MenuAnalyze(Command id) { SS.TW.stepDim.isDistance = (c->type != Constraint::Type::ANGLE) && (c->type != Constraint::Type::LENGTH_RATIO) && - (c->type != Constraint::Type::LENGTH_DIFFERENCE); + (c->type != Constraint::Type::ARC_ARC_LEN_RATIO) && + (c->type != Constraint::Type::ARC_LINE_LEN_RATIO) && + (c->type != Constraint::Type::LENGTH_DIFFERENCE) && + (c->type != Constraint::Type::ARC_ARC_DIFFERENCE) && + (c->type != Constraint::Type::ARC_LINE_DIFFERENCE) ; SS.TW.shown.constraint = c->h; SS.TW.shown.screen = TextWindow::Screen::STEP_DIMENSION; @@ -1007,7 +1117,11 @@ void SolveSpaceUI::MenuHelp(Command id) { "law. For details, visit http://gnu.org/licenses/\n" "\n" "© 2008-%d Jonathan Westhues and other authors.\n"), -PACKAGE_VERSION, 2021); +PACKAGE_VERSION, 2025); + break; + + case Command::GITHUB: + Platform::OpenInBrowser(GIT_HASH_URL); break; default: ssassert(false, "Unexpected menu ID"); @@ -1026,12 +1140,14 @@ void SolveSpaceUI::Clear() { GW.showGridMenuItem = NULL; GW.dimSolidModelMenuItem = NULL; GW.perspectiveProjMenuItem = NULL; + GW.explodeMenuItem = NULL; GW.showToolbarMenuItem = NULL; GW.showTextWndMenuItem = NULL; GW.fullScreenMenuItem = NULL; GW.unitsMmMenuItem = NULL; GW.unitsMetersMenuItem = NULL; GW.unitsInchesMenuItem = NULL; + GW.unitsFeetInchesMenuItem = NULL; GW.inWorkplaneMenuItem = NULL; GW.in3dMenuItem = NULL; GW.undoMenuItem = NULL; @@ -1116,3 +1232,5 @@ Group *Sketch::GetRunningMeshGroupFor(hGroup h) { } return NULL; } + +} // namespace SolveSpace diff --git a/src/solvespace.h b/src/solvespace.h index e64a1ab86..572c48d71 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -7,74 +7,25 @@ #ifndef SOLVESPACE_H #define SOLVESPACE_H -#include -#include -#include -#include -#include -#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include - -// We declare these in advance instead of simply using FT_Library -// (defined as typedef FT_LibraryRec_* FT_Library) because including -// freetype.h invokes indescribable horrors and we would like to avoid -// doing that every time we include solvespace.h. -struct FT_LibraryRec_; -struct FT_FaceRec_; - -typedef struct _cairo cairo_t; -typedef struct _cairo_surface cairo_surface_t; - -// The few floating-point equality comparisons in SolveSpace have been -// carefully considered, so we disable the -Wfloat-equal warning for them -#ifdef __clang__ -# define EXACT(expr) \ - (_Pragma("clang diagnostic push") \ - _Pragma("clang diagnostic ignored \"-Wfloat-equal\"") \ - (expr) \ - _Pragma("clang diagnostic pop")) -#else -# define EXACT(expr) (expr) -#endif -// Debugging functions -#if defined(__GNUC__) -#define ssassert(condition, message) \ - do { \ - if(__builtin_expect((condition), true) == false) { \ - SolveSpace::AssertFailure(__FILE__, __LINE__, __func__, #condition, message); \ - __builtin_unreachable(); \ - } \ - } while(0) -#else -#define ssassert(condition, message) \ - do { \ - if((condition) == false) { \ - SolveSpace::AssertFailure(__FILE__, __LINE__, __func__, #condition, message); \ - abort(); \ - } \ - } while(0) -#endif +#define EIGEN_NO_DEBUG +#undef Success +#include + +#include "dsc.h" +#include "polygon.h" +#include "srf/surface.h" +#include "render/render.h" +#include "expr.h" +#include "sketch.h" +#include "ttf.h" +#include "ui.h" -#define dbp SolveSpace::Platform::DebugPrint -#define DBPTRI(tri) \ - dbp("tri: (%.3f %.3f %.3f) (%.3f %.3f %.3f) (%.3f %.3f %.3f)", \ - CO((tri).a), CO((tri).b), CO((tri).c)) +#include "platform/platform.h" namespace SolveSpace { @@ -83,80 +34,14 @@ using std::max; using std::swap; using std::fabs; -[[noreturn]] -void AssertFailure(const char *file, unsigned line, const char *function, - const char *condition, const char *message); - -#if defined(__GNUC__) -__attribute__((__format__ (__printf__, 1, 2))) -#endif -std::string ssprintf(const char *fmt, ...); - -inline bool IsReasonable(double x) { - return std::isnan(x) || x > 1e11 || x < -1e11; -} - -inline int WRAP(int v, int n) { - // Clamp it to the range [0, n) - while(v >= n) v -= n; - while(v < 0) v += n; - return v; -} -inline double WRAP_NOT_0(double v, double n) { - // Clamp it to the range (0, n] - while(v > n) v -= n; - while(v <= 0) v += n; - return v; -} -inline double WRAP_SYMMETRIC(double v, double n) { - // Clamp it to the range (-n/2, n/2] - while(v > n/2) v -= n; - while(v <= -n/2) v += n; - return v; -} - -#define CO(v) (v).x, (v).y, (v).z - -static constexpr double ANGLE_COS_EPS = 1e-6; -static constexpr double LENGTH_EPS = 1e-6; -static constexpr double VERY_POSITIVE = 1e10; -static constexpr double VERY_NEGATIVE = -1e10; - -#include "platform/platform.h" -#include "platform/gui.h" -#include "resource.h" - -using Platform::AllocTemporary; -using Platform::FreeAllTemporary; - -class Expr; -class ExprVector; -class ExprQuaternion; -class RgbaColor; -enum class Command : uint32_t; - enum class Unit : uint32_t { MM = 0, INCHES, - METERS + METERS, + FEET_INCHES }; -template -using handle_map = std::map; - -class Group; -class SSurface; -#include "dsc.h" -#include "polygon.h" -#include "srf/surface.h" -#include "render/render.h" - -class Entity; -class hEntity; -class Param; -class hParam; -typedef IdList EntityList; -typedef IdList ParamList; +class Pixmap; enum class SolveResult : uint32_t { OKAY = 0, @@ -166,16 +51,16 @@ enum class SolveResult : uint32_t { TOO_MANY_UNKNOWNS = 20 }; - -#include "sketch.h" -#include "ui.h" -#include "expr.h" - - // Utility functions that are provided in the platform-independent code. -class utf8_iterator : std::iterator { +class utf8_iterator { const char *p, *n; public: + using iterator_category = std::forward_iterator_tag; + using value_type = char32_t; + using difference_type = std::ptrdiff_t; + using pointer = char32_t*; + using reference = char32_t&; + utf8_iterator(const char *p) : p(p), n(NULL) {} bool operator==(const utf8_iterator &i) const { return p==i.p; } bool operator!=(const utf8_iterator &i) const { return p!=i.p; } @@ -193,23 +78,9 @@ class ReadUTF8 { utf8_iterator end() const { return utf8_iterator(&str[0] + str.length()); } }; - -#define arraylen(x) (sizeof((x))/sizeof((x)[0])) -#define PI (3.1415926535897931) -void MakeMatrix(double *mat, double a11, double a12, double a13, double a14, - double a21, double a22, double a23, double a24, - double a31, double a32, double a33, double a34, - double a41, double a42, double a43, double a44); -void MultMatrix(double *mata, double *matb, double *matr); - -int64_t GetMilliseconds(); -void Message(const char *fmt, ...); -void MessageAndRun(std::function onDismiss, const char *fmt, ...); -void Error(const char *fmt, ...); - class System { public: - enum { MAX_UNKNOWNS = 1024 }; + enum { MAX_UNKNOWNS = 2048 }; EntityList entity; ParamList param; @@ -217,7 +88,7 @@ class System { // A list of parameters that are being dragged; these are the ones that // we should put as close as possible to their initial positions. - List dragged; + ParamSet dragged; enum { // In general, the tag indicates the subsys that a variable/equation @@ -231,37 +102,33 @@ class System { // The system Jacobian matrix struct { // The corresponding equation for each row - hEquation eq[MAX_UNKNOWNS]; + std::vector eq; // The corresponding parameter for each column - hParam param[MAX_UNKNOWNS]; + std::vector param; // We're solving AX = B int m, n; struct { - Expr *sym[MAX_UNKNOWNS][MAX_UNKNOWNS]; - double num[MAX_UNKNOWNS][MAX_UNKNOWNS]; - } A; + // This only observes the Expr - does not own them! + Eigen::SparseMatrix sym; + Eigen::SparseMatrix num; + } A; - double scale[MAX_UNKNOWNS]; - - // Some helpers for the least squares solve - double AAt[MAX_UNKNOWNS][MAX_UNKNOWNS]; - double Z[MAX_UNKNOWNS]; - - double X[MAX_UNKNOWNS]; + Eigen::VectorXd X; struct { - Expr *sym[MAX_UNKNOWNS]; - double num[MAX_UNKNOWNS]; - } B; + // This only observes the Expr - does not own them! + std::vector sym; + Eigen::VectorXd num; + } B; } mat; - static const double RANK_MAG_TOLERANCE, CONVERGE_TOLERANCE; + static const double CONVERGE_TOLERANCE; int CalculateRank(); - bool TestRank(int *rank = NULL); - static bool SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS], - double B[], int N); + bool TestRank(int *dof = NULL, int *rank = NULL); + static bool SolveLinearSystem(const Eigen::SparseMatrix &A, + const Eigen::VectorXd &B, Eigen::VectorXd *X); bool SolveLeastSquares(); bool WriteJacobian(int tag); @@ -270,17 +137,15 @@ class System { void WriteEquationsExceptFor(hConstraint hc, Group *g); void FindWhichToRemoveToFixJacobian(Group *g, List *bad, bool forceDofCheck); - void SolveBySubstitution(); + SubstitutionMap SolveBySubstitution(); bool IsDragged(hParam p); - bool NewtonSolve(int tag); + bool NewtonSolve(); void MarkParamsFree(bool findFree); - int CalculateDof(); - SolveResult Solve(Group *g, int *rank = NULL, int *dof = NULL, - List *bad = NULL, + SolveResult Solve(Group *g, int *dof = NULL, List *bad = NULL, bool andFindBad = false, bool andFindFree = false, bool forceDofCheck = false); @@ -291,10 +156,19 @@ class System { void Clear(); }; -#include "ttf.h" - class StepFileWriter { public: + bool HasCartesianPointAnAlias(int number, Vector v, int vertex, + bool *vertex_has_alias = nullptr); + int InsertPoint(int number); + int InsertVertex(int number); + bool HasBSplineCurveAnAlias(int number, std::vector points); + int InsertCurve(int number); + bool HasEdgeCurveAnAlias(int number, int prevFinish, int thisFinish, int curveId, + bool *flip = nullptr); + int InsertEdgeCurve(int number); + bool HasOrientedEdgeAnAlias(int number, int edgeCurveId, bool flip); + int InsertOrientedEdge(int number); void ExportSurfacesTo(const Platform::Path &filename); void WriteHeader(); void WriteProductHeader(); @@ -308,6 +182,50 @@ class StepFileWriter { List advancedFaces; FILE *f; int id; + + // Structs to keep track of duplicated entities. + // Basic alias. + typedef struct { + int reference; + std::vector aliases; + } alias_t; + + // Cartesian points. + typedef struct { + alias_t alias; + alias_t vertexAlias; + Vector v; + } pointAliases_t; + + // Curves. + typedef struct { + alias_t alias; + std::vector memberPoints; + RgbaColor color; + } curveAliases_t; + + // Edges. + typedef struct { + alias_t alias; + int prevFinish; + int thisFinish; + int curveId; + RgbaColor color; + } edgeCurveAliases_t; + + // Edges. + typedef struct { + alias_t alias; + int edgeCurveId; + bool flip; + } orientedEdgeAliases_t; + + std::vector pointAliases; + std::vector edgeCurveAliases; + std::vector curveAliases; + std::vector orientedEdgeAliases; + bool exportParts = true; + RgbaColor currentColor; }; class VectorFileWriter { @@ -488,7 +406,7 @@ class Sketch { // These are generated from the above. IdList entity; - IdList param; + ParamList param; inline CONSTRAINT *GetConstraint(hConstraint h) { return constraint.FindById(h); } @@ -518,7 +436,7 @@ class SolveSpaceUI { List
groupOrder; IdList request; IdList constraint; - IdList param; + ParamList param; IdList style; hGroup activeGroup; @@ -564,15 +482,19 @@ class SolveSpaceUI { double exportChordTol; int exportMaxSegments; int timeoutRedundantConstr; //milliseconds + int animationSpeed; //milliseconds double cameraTangent; double gridSpacing; double exportScale; double exportOffset; + bool arcDimDefaultDiameter; + bool showFullFilePath; bool fixExportColors; bool exportBackgroundColor; bool drawBackFaces; bool showContourAreas; bool checkClosedContour; + bool cameraNav; bool turntableNav; bool immediatelyEditDimension; bool automaticLineConstraints; @@ -597,6 +519,7 @@ class SolveSpaceUI { } exportCanvas; struct { double depth; + double safeHeight; int passes; double feed; double plungeFeed; @@ -608,8 +531,10 @@ class SolveSpaceUI { int afterDecimalDegree; bool useSIPrefixes; int autosaveInterval; // in minutes + bool explode; + double explodeDistance; - std::string MmToString(double v); + std::string MmToString(double v, bool editable=false); std::string MmToStringSI(double v, int dim = 0); std::string DegreeToString(double v); double ExprToMm(Expr *e); @@ -675,6 +600,7 @@ class SolveSpaceUI { void NewFile(); bool SaveToFile(const Platform::Path &filename); bool LoadAutosaveFor(const Platform::Path &filename); + std::function OnSaveFinished; bool LoadFromFile(const Platform::Path &filename, bool canCancel = false); void UpgradeLegacyData(); bool LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le, @@ -750,8 +676,7 @@ class SolveSpaceUI { bool EntityExists(hEntity he); bool GroupsInOrder(hGroup before, hGroup after); bool PruneGroups(hGroup hg); - bool PruneRequests(hGroup hg); - bool PruneConstraints(hGroup hg); + bool PruneRequestsAndConstraints(hGroup hg); static void ShowNakedEdges(bool reportOnlyWhenNotOkay); enum class Generate : uint32_t { @@ -785,9 +710,11 @@ class SolveSpaceUI { // the sketch! bool allConsistent; - Platform::TimerRef showTWTimer; - Platform::TimerRef generateAllTimer; + bool scheduledGenerateAll; + bool scheduledShowTW; + Platform::TimerRef refreshTimer; Platform::TimerRef autosaveTimer; + void Refresh(); void ScheduleShowTW(); void ScheduleGenerateAll(); void ScheduleAutosave(); @@ -812,14 +739,11 @@ class SolveSpaceUI { void ImportDxf(const Platform::Path &file); void ImportDwg(const Platform::Path &file); bool LinkIDF(const Platform::Path &filename, EntityList *le, SMesh *m, SShell *sh); +bool LinkStl(const Platform::Path &filename, EntityList *le, SMesh *m, SShell *sh); extern SolveSpaceUI SS; extern Sketch SK; -} - -#ifndef __OBJC__ -using namespace SolveSpace; -#endif +} // namespace SolveSpace #endif diff --git a/src/srf/boolean.cpp b/src/srf/boolean.cpp index 1edf46ed5..4778b185e 100644 --- a/src/srf/boolean.cpp +++ b/src/srf/boolean.cpp @@ -6,6 +6,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + static int I; void SShell::MakeFromUnionOf(SShell *a, SShell *b) { @@ -29,7 +31,7 @@ void SCurve::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const { } } -// We will be inserting other curve verticies into our curves to split them. +// We will be inserting other curve vertices into our curves to split them. // This is helpful when curved surfaces become tangent along a trim and the // usual tests for curve-surface intersection don't split the curve at a vertex. // This is faster than the previous version that split at surface corners and @@ -39,7 +41,7 @@ static void FindVertsOnCurve(List *l, const SCurve *curve, SShell *sh) { Vector amax, amin; curve->GetAxisAlignedBounding(&amax, &amin); - for(auto sc : sh->curve) { + for(const auto &sc : sh->curve) { if(!sc.isExact) continue; Vector cmax, cmin; @@ -157,7 +159,7 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, // Now add any vertex that is on this segment const Vector lineStart = prev.p; const Vector lineDirection = (p->p).Minus(prev.p); - for(auto vtx : vertpts) { + for(const auto &vtx : vertpts) { double t = (vtx.p.Minus(lineStart)).DivProjected(lineDirection); if((0.0 < t) && (t < 1.0)) { il.Add(&vtx); @@ -270,32 +272,35 @@ void SSurface::TrimFromEdgeList(SEdgeList *el, bool asUv) { static bool KeepRegion(SSurface::CombineAs type, bool opA, SShell::Class shell, SShell::Class orig) { - bool inShell = (shell == SShell::Class::INSIDE), - outSide = (shell == SShell::Class::OUTSIDE), - inSame = (shell == SShell::Class::COINC_SAME), - inOrig = (orig == SShell::Class::INSIDE); + bool inShell = (shell == SShell::Class::SURF_INSIDE), + outSide = (shell == SShell::Class::SURF_OUTSIDE), + coincSame = (shell == SShell::Class::SURF_COINC_SAME), + coincOpp = (shell == SShell::Class::SURF_COINC_OPP), + inOrig = (orig == SShell::Class::SURF_INSIDE); + // This one line is not really part of this functions logic if(!inOrig) return false; + switch(type) { case SSurface::CombineAs::UNION: if(opA) { return outSide; } else { - return outSide || inSame; + return outSide || coincSame; } case SSurface::CombineAs::DIFFERENCE: if(opA) { - return outSide; + return outSide || coincOpp; } else { - return inShell || inSame; + return inShell; } case SSurface::CombineAs::INTERSECTION: if(opA) { return inShell; } else { - return inShell || inSame; + return inShell || coincSame; } default: ssassert(false, "Unexpected combine type"); @@ -318,29 +323,29 @@ static void TagByClassifiedEdge(SBspUv::Class bspclass, SShell::Class *indir, SS { switch(bspclass) { case SBspUv::Class::INSIDE: - *indir = SShell::Class::INSIDE; - *outdir = SShell::Class::INSIDE; + *indir = SShell::Class::SURF_INSIDE; + *outdir = SShell::Class::SURF_INSIDE; break; case SBspUv::Class::OUTSIDE: - *indir = SShell::Class::OUTSIDE; - *outdir = SShell::Class::OUTSIDE; + *indir = SShell::Class::SURF_OUTSIDE; + *outdir = SShell::Class::SURF_OUTSIDE; break; case SBspUv::Class::EDGE_PARALLEL: - *indir = SShell::Class::INSIDE; - *outdir = SShell::Class::OUTSIDE; + *indir = SShell::Class::SURF_INSIDE; + *outdir = SShell::Class::SURF_OUTSIDE; break; case SBspUv::Class::EDGE_ANTIPARALLEL: - *indir = SShell::Class::OUTSIDE; - *outdir = SShell::Class::INSIDE; + *indir = SShell::Class::SURF_OUTSIDE; + *outdir = SShell::Class::SURF_INSIDE; break; default: dbp("TagByClassifiedEdge: fail!"); - *indir = SShell::Class::OUTSIDE; - *outdir = SShell::Class::OUTSIDE; + *indir = SShell::Class::SURF_OUTSIDE; + *outdir = SShell::Class::SURF_OUTSIDE; break; } } @@ -438,13 +443,12 @@ void SSurface::EdgeNormalsWithinSurface(Point2d auv, Point2d buv, double t; sc->exact.ClosestPointTo(*pt, &t, /*mustConverge=*/false); *pt = sc->exact.PointAt(t); - ClosestPointTo(*pt, &muv); } else if(!sc->isExact) { SSurface *trimmedA = sc->GetSurfaceA(sha, shb), *trimmedB = sc->GetSurfaceB(sha, shb); *pt = trimmedA->ClosestPointOnThisAndSurface(trimmedB, *pt); - ClosestPointTo(*pt, &muv); } + ClosestPointTo(*pt, &muv); *surfn = NormalAt(muv.x, muv.y); @@ -472,6 +476,9 @@ void SSurface::EdgeNormalsWithinSurface(Point2d auv, Point2d buv, pout = PointAt(muv.Plus(enuv)); *enin = pin.Minus(*pt), *enout = pout.Minus(*pt); +// ideally this should work (fail screwdriver file) +// *enin = enxyz.ScaledBy(-1.0); +// *enout = enxyz; } //----------------------------------------------------------------------------- @@ -521,20 +528,19 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, SEdgeList inter = {}; SSurface *ss; - SCurve *sc; - for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) { - if(sc->source != SCurve::Source::INTERSECTION) continue; + for(SCurve &sc : into->curve) { + if(sc.source != SCurve::Source::INTERSECTION) continue; if(opA) { - if(sc->surfA != h) continue; - ss = shb->surface.FindById(sc->surfB); + if(sc.surfA != h) continue; + ss = shb->surface.FindById(sc.surfB); } else { - if(sc->surfB != h) continue; - ss = sha->surface.FindById(sc->surfA); + if(sc.surfB != h) continue; + ss = sha->surface.FindById(sc.surfA); } int i; - for(i = 1; i < sc->pts.n; i++) { - Vector a = sc->pts[i-1].p, - b = sc->pts[i].p; + for(i = 1; i < sc.pts.n; i++) { + Vector a = sc.pts[i-1].p, + b = sc.pts[i].p; Point2d auv, buv; ss->ClosestPointTo(a, &(auv.x), &(auv.y)); @@ -560,9 +566,9 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, bkwds = !bkwds; } if(bkwds) { - inter.AddEdge(tb, ta, sc->h.v, 1); + inter.AddEdge(tb, ta, sc.h.v, 1); } else { - inter.AddEdge(ta, tb, sc->h.v, 0); + inter.AddEdge(ta, tb, sc.h.v, 0); } } } @@ -613,8 +619,8 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, SShell::Class indir_shell, outdir_shell, indir_orig, outdir_orig; - indir_orig = SShell::Class::INSIDE; - outdir_orig = SShell::Class::OUTSIDE; + indir_orig = SShell::Class::SURF_INSIDE; + outdir_orig = SShell::Class::SURF_OUTSIDE; agnst->ClassifyEdge(&indir_shell, &outdir_shell, ret.PointAt(auv), ret.PointAt(buv), pt, @@ -711,20 +717,18 @@ void SShell::MakeIntersectionCurvesAgainst(SShell *agnst, SShell *into) { for(int i = 0; i< surface.n; i++) { SSurface *sa = &surface[i]; - SSurface *sb; - for(sb = agnst->surface.First(); sb; sb = agnst->surface.NextAfter(sb)){ + for(SSurface &sb : agnst->surface){ // Intersect every surface from our shell against every surface // from agnst; this will add zero or more curves to the curve // list for into. - sa->IntersectAgainst(sb, this, agnst, into); + sa->IntersectAgainst(&sb, this, agnst, into); } } } void SShell::CleanupAfterBoolean() { - SSurface *ss; - for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { - ss->edges.Clear(); + for(SSurface &ss : surface) { + ss.edges.Clear(); } } @@ -734,10 +738,9 @@ void SShell::CleanupAfterBoolean() { // by their new IDs. //----------------------------------------------------------------------------- void SShell::RewriteSurfaceHandlesForCurves(SShell *a, SShell *b) { - SCurve *sc; - for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) { - sc->surfA = sc->GetSurfaceA(a, b)->newH, - sc->surfB = sc->GetSurfaceB(a, b)->newH; + for(SCurve &sc : curve) { + sc.surfA = sc.GetSurfaceA(a, b)->newH, + sc.surfB = sc.GetSurfaceB(a, b)->newH; } } @@ -759,32 +762,32 @@ void SShell::MakeFromAssemblyOf(SShell *a, SShell *b) { // First, copy over all the curves. Note which shell (a or b) each curve // came from, but assign it a new ID. curve.ReserveMore(a->curve.n + b->curve.n); - SCurve *c, cn; + SCurve cn; for(i = 0; i < 2; i++) { ab = (i == 0) ? a : b; - for(c = ab->curve.First(); c; c = ab->curve.NextAfter(c)) { - cn = SCurve::FromTransformationOf(c, t, q, 1.0); + for(SCurve &c : ab->curve) { + cn = SCurve::FromTransformationOf(&c, t, q, 1.0); cn.source = (i == 0) ? SCurve::Source::A : SCurve::Source::B; // surfA and surfB are wrong now, and we can't fix them until // we've assigned IDs to the surfaces. So we'll get that later. - c->newH = curve.AddAndAssignId(&cn); + c.newH = curve.AddAndAssignId(&cn); } } // Likewise copy over all the surfaces. surface.ReserveMore(a->surface.n + b->surface.n); - SSurface *s, sn; + SSurface sn; for(i = 0; i < 2; i++) { ab = (i == 0) ? a : b; - for(s = ab->surface.First(); s; s = ab->surface.NextAfter(s)) { - sn = SSurface::FromTransformationOf(s, t, q, 1.0, /*includingTrims=*/true); + for(SSurface &s : ab->surface) { + sn = SSurface::FromTransformationOf(&s, t, q, 1.0, /*includingTrims=*/true); // All the trim curve IDs get rewritten; we know the new handles // to the curves since we recorded them in the previous step. STrimBy *stb; for(stb = sn.trim.First(); stb; stb = sn.trim.NextAfter(stb)) { stb->curve = ab->curve.FindById(stb->curve)->newH; } - s->newH = surface.AddAndAssignId(&sn); + s.newH = surface.AddAndAssignId(&sn); } } @@ -800,7 +803,7 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type) { b->MakeClassifyingBsps(NULL); // Copy over all the original curves, splitting them so that a - // piecwise linear segment never crosses a surface from the other + // piecewise linear segment never crosses a surface from the other // shell. a->CopyCurvesSplitAgainst(/*opA=*/true, b, this); b->CopyCurvesSplitAgainst(/*opA=*/false, a, this); @@ -809,12 +812,11 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type) { // the surfaces in B (which is all of the intersection curves). a->MakeIntersectionCurvesAgainst(b, this); - SCurve *sc; - for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) { - SSurface *srfA = sc->GetSurfaceA(a, b), - *srfB = sc->GetSurfaceB(a, b); + for(SCurve &sc : curve) { + SSurface *srfA = sc.GetSurfaceA(a, b), + *srfB = sc.GetSurfaceB(a, b); - sc->RemoveShortSegments(srfA, srfB); + sc.RemoveShortSegments(srfA, srfB); } // And clean up the piecewise linear things we made as a calculation aid @@ -866,7 +868,7 @@ void SSurface::MakeClassifyingBsp(SShell *shell, SShell *useCurvesFrom) { } SBspUv *SBspUv::Alloc() { - return (SBspUv *)AllocTemporary(sizeof(SBspUv)); + return (SBspUv *)Platform::AllocTemporary(sizeof(SBspUv)); } SBspUv *SBspUv::From(SEdgeList *el, SSurface *srf) { @@ -1044,3 +1046,4 @@ double SBspUv::MinimumDistanceToEdge(Point2d p, SSurface *srf) const { return min(d, min(dn, dp)); } +} // namespace SolveSpace diff --git a/src/srf/curve.cpp b/src/srf/curve.cpp index 55496b670..ef3e19a04 100644 --- a/src/srf/curve.cpp +++ b/src/srf/curve.cpp @@ -4,7 +4,9 @@ // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- -#include "../solvespace.h" +#include "solvespace.h" + +namespace SolveSpace { SBezier SBezier::From(Vector4 p0, Vector4 p1) { SBezier ret = {}; @@ -390,7 +392,10 @@ SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl, { SBezierLoop loop = {}; - if(sbl->l.n < 1) return loop; + if(sbl->l.n < 1) { + *allClosed = false; + return loop; + } sbl->l.ClearTags(); SBezier *first = &(sbl->l[0]); @@ -817,7 +822,7 @@ void SCurve::RemoveShortSegments(SSurface *srfA, SSurface *srfB) { continue; } - // if the curve is exact and points are >0.05 appart wrt t, point is there + // if the curve is exact and points are >0.05 apart wrt t, point is there // deliberately regardless of chord tolerance (ex: small circles) tprev = t = tnext = 0; if (isExact) { @@ -878,3 +883,4 @@ STrimBy STrimBy::EntireCurve(SShell *shell, hSCurve hsc, bool backwards) { return stb; } +} // namespace SolveSpace diff --git a/src/srf/merge.cpp b/src/srf/merge.cpp index a91a307fa..03ef339d8 100644 --- a/src/srf/merge.cpp +++ b/src/srf/merge.cpp @@ -4,7 +4,9 @@ // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- -#include "../solvespace.h" +#include "solvespace.h" + +namespace SolveSpace { void SShell::MergeCoincidentSurfaces() { surface.ClearTags(); @@ -58,10 +60,9 @@ void SShell::MergeCoincidentSurfaces() { // All the references to this surface get replaced with the // new srf - SCurve *sc; - for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) { - if(sc->surfA == sj->h) sc->surfA = si->h; - if(sc->surfB == sj->h) sc->surfB = si->h; + for(SCurve &sc : curve) { + if(sc.surfA == sj->h) sc.surfA = si->h; + if(sc.surfB == sj->h) sc.surfB = si->h; } } @@ -126,3 +127,4 @@ void SShell::MergeCoincidentSurfaces() { surface.RemoveTagged(); } +} // namespace SolveSpace diff --git a/src/srf/ratpoly.cpp b/src/srf/ratpoly.cpp index 46180a55e..20b8d198b 100644 --- a/src/srf/ratpoly.cpp +++ b/src/srf/ratpoly.cpp @@ -5,7 +5,9 @@ // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- -#include "../solvespace.h" +#include "solvespace.h" + +namespace SolveSpace { // Converge it to better than LENGTH_EPS; we want two points, each // independently projected into uv and back, to end up equal with the @@ -699,3 +701,4 @@ void SSurface::PointOnCurve(const SBezier *curve, double *up, double *vp) dbp("didn't converge (surface and curve intersecting)"); } +} // namespace SolveSpace diff --git a/src/srf/raycast.cpp b/src/srf/raycast.cpp index 587792821..4c2f43500 100644 --- a/src/srf/raycast.cpp +++ b/src/srf/raycast.cpp @@ -9,6 +9,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + // Dot product tolerance for perpendicular; this is on the direction cosine, // so it's about 0.001 degrees. const double SShell::DOTP_TOL = 1e-5; @@ -381,9 +383,8 @@ void SShell::AllPointsIntersecting(Vector a, Vector b, List *il, bool asSegment, bool trimmed, bool inclTangent) { - SSurface *ss; - for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { - ss->AllPointsIntersecting(a, b, il, + for(SSurface &ss : surface) { + ss.AllPointsIntersecting(a, b, il, asSegment, trimmed, inclTangent); } } @@ -399,14 +400,14 @@ SShell::Class SShell::ClassifyRegion(Vector edge_n, Vector inter_surf_n, // are coincident. Test the edge's surface normal // to see if it's with same or opposite normals. if(inter_surf_n.Dot(edge_surf_n) > 0) { - return Class::COINC_SAME; + return Class::SURF_COINC_SAME; } else { - return Class::COINC_OPP; + return Class::SURF_COINC_OPP; } } else if(dot > 0) { - return Class::OUTSIDE; + return Class::SURF_OUTSIDE; } else { - return Class::INSIDE; + return Class::SURF_INSIDE; } } @@ -434,11 +435,10 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, // First, check for edge-on-edge int edge_inters = 0; Vector inter_surf_n[2], inter_edge_n[2]; - SSurface *srf; - for(srf = surface.First(); srf; srf = surface.NextAfter(srf)) { - if(srf->LineEntirelyOutsideBbox(ea, eb, /*asSegment=*/true)) continue; + for(SSurface &srf : surface) { + if(srf.LineEntirelyOutsideBbox(ea, eb, /*asSegment=*/true)) continue; - SEdgeList *sel = &(srf->edges); + SEdgeList *sel = &(srf.edges); SEdge *se; for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { if((ea.Equals(se->a) && eb.Equals(se->b)) || @@ -448,9 +448,9 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, if(edge_inters < 2) { // Edge-on-edge case Point2d pm; - srf->ClosestPointTo(p, &pm, /*mustConverge=*/false); + srf.ClosestPointTo(p, &pm, /*mustConverge=*/false); // A vector normal to the surface, at the intersection point - inter_surf_n[edge_inters] = srf->NormalAt(pm); + inter_surf_n[edge_inters] = srf.NormalAt(pm); // A vector normal to the intersecting edge (but within the // intersecting surface) at the intersection point, pointing // out. @@ -476,7 +476,7 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, swap(inter_edge_n[0], inter_edge_n[1]); } - Class coinc = (surf_n.Dot(inter_surf_n[0])) > 0 ? Class::COINC_SAME : Class::COINC_OPP; + Class coinc = (surf_n.Dot(inter_surf_n[0])) > 0 ? Class::SURF_COINC_SAME : Class::SURF_COINC_OPP; if(fabs(dotp[0]) < DOTP_TOL && fabs(dotp[1]) < DOTP_TOL) { // This is actually an edge on face case, just that the face @@ -486,25 +486,25 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, } else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] > DOTP_TOL) { if(edge_n_out.Dot(inter_edge_n[0]) > 0) { *indir = coinc; - *outdir = Class::OUTSIDE; + *outdir = Class::SURF_OUTSIDE; } else { - *indir = Class::INSIDE; + *indir = Class::SURF_INSIDE; *outdir = coinc; } } else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] < -DOTP_TOL) { if(edge_n_out.Dot(inter_edge_n[0]) > 0) { *indir = coinc; - *outdir = Class::INSIDE; + *outdir = Class::SURF_INSIDE; } else { - *indir = Class::OUTSIDE; + *indir = Class::SURF_OUTSIDE; *outdir = coinc; } } else if(dotp[0] > DOTP_TOL && dotp[1] > DOTP_TOL) { - *indir = Class::INSIDE; - *outdir = Class::OUTSIDE; + *indir = Class::SURF_INSIDE; + *outdir = Class::SURF_OUTSIDE; } else if(dotp[0] < -DOTP_TOL && dotp[1] < -DOTP_TOL) { - *indir = Class::OUTSIDE; - *outdir = Class::INSIDE; + *indir = Class::SURF_OUTSIDE; + *outdir = Class::SURF_INSIDE; } else { // Edge is tangent to the shell at shell's edge, so can't be // a boundary of the surface. @@ -520,25 +520,25 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, // are on surface) and for numerical stability, so we don't pick up // the additional error from the line intersection. - for(srf = surface.First(); srf; srf = surface.NextAfter(srf)) { - if(srf->LineEntirelyOutsideBbox(ea, eb, /*asSegment=*/true)) continue; + for(SSurface &srf : surface) { + if(srf.LineEntirelyOutsideBbox(ea, eb, /*asSegment=*/true)) continue; Point2d puv; - srf->ClosestPointTo(p, &(puv.x), &(puv.y), /*mustConverge=*/false); - Vector pp = srf->PointAt(puv); + srf.ClosestPointTo(p, &(puv.x), &(puv.y), /*mustConverge=*/false); + Vector pp = srf.PointAt(puv); if((pp.Minus(p)).Magnitude() > LENGTH_EPS) continue; Point2d dummy = { 0, 0 }; - SBspUv::Class c = (srf->bsp) ? srf->bsp->ClassifyPoint(puv, dummy, srf) : SBspUv::Class::OUTSIDE; + SBspUv::Class c = (srf.bsp) ? srf.bsp->ClassifyPoint(puv, dummy, &srf) : SBspUv::Class::OUTSIDE; if(c == SBspUv::Class::OUTSIDE) continue; - // Edge-on-face (unless edge-on-edge above superceded) + // Edge-on-face (unless edge-on-edge above superseded) Point2d pin, pout; - srf->ClosestPointTo(p.Plus(edge_n_in), &pin, /*mustConverge=*/false); - srf->ClosestPointTo(p.Plus(edge_n_out), &pout, /*mustConverge=*/false); + srf.ClosestPointTo(p.Plus(edge_n_in), &pin, /*mustConverge=*/false); + srf.ClosestPointTo(p.Plus(edge_n_out), &pout, /*mustConverge=*/false); - Vector surf_n_in = srf->NormalAt(pin), - surf_n_out = srf->NormalAt(pout); + Vector surf_n_in = srf.NormalAt(pin), + surf_n_out = srf.NormalAt(pout); *indir = ClassifyRegion(edge_n_in, surf_n_in, surf_n); *outdir = ClassifyRegion(edge_n_out, surf_n_out, surf_n); @@ -559,8 +559,8 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, /*asSegment=*/false, /*trimmed=*/true, /*inclTangent=*/false); // no intersections means it's outside - *indir = Class::OUTSIDE; - *outdir = Class::OUTSIDE; + *indir = Class::SURF_OUTSIDE; + *outdir = Class::SURF_OUTSIDE; double dmin = VERY_POSITIVE; bool onEdge = false; edge_inters = 0; @@ -586,11 +586,11 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, // Edge does not lie on surface; either strictly inside // or strictly outside if((si->surfNormal).Dot(ray) > 0) { - *indir = Class::INSIDE; - *outdir = Class::INSIDE; + *indir = Class::SURF_INSIDE; + *outdir = Class::SURF_INSIDE; } else { - *indir = Class::OUTSIDE; - *outdir = Class::OUTSIDE; + *indir = Class::SURF_OUTSIDE; + *outdir = Class::SURF_OUTSIDE; } onEdge = si->onEdge; } @@ -613,3 +613,4 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, return true; } +} // namespace SolveSpace diff --git a/src/srf/shell.cpp b/src/srf/shell.cpp new file mode 100644 index 000000000..9d7fa4752 --- /dev/null +++ b/src/srf/shell.cpp @@ -0,0 +1,618 @@ +//----------------------------------------------------------------------------- +// 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" + +namespace SolveSpace { + +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 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> 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 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 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 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> hsl = {}; + + for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { + std::vector 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 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; iTriangulateInto(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(); +} + +} // namespace SolveSpace diff --git a/src/srf/surface.cpp b/src/srf/surface.cpp index 815aedad2..8ddd084d1 100644 --- a/src/srf/surface.cpp +++ b/src/srf/surface.cpp @@ -4,7 +4,9 @@ // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- -#include "../solvespace.h" +#include "solvespace.h" + +namespace SolveSpace { SSurface SSurface::FromExtrusionOf(SBezier *sb, Vector t0, Vector t1) { SSurface ret = {}; @@ -489,614 +491,4 @@ void SSurface::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 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> 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 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 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 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> hsl = {}; - - for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { - std::vector 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 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); - SSurface *s; - for(s = a->surface.First(); s; s = a->surface.NextAfter(s)) { - SSurface n; - n = SSurface::FromTransformationOf(s, t, q, scale, /*includingTrims=*/true); - surface.Add(&n); // keeping the old ID - } - - curve.ReserveMore(a->curve.n); - SCurve *c; - for(c = a->curve.First(); c; c = a->curve.NextAfter(c)) { - SCurve n; - n = SCurve::FromTransformationOf(c, t, q, scale); - curve.Add(&n); // keeping the old ID - } -} - -void SShell::MakeEdgesInto(SEdgeList *sel) { - SSurface *s; - for(s = surface.First(); s; s = surface.NextAfter(s)) { - s->MakeEdgesInto(this, sel, SSurface::MakeAs::XYZ); - } -} - -void SShell::MakeSectionEdgesInto(Vector n, double d, SEdgeList *sel, SBezierList *sbl) -{ - SSurface *s; - for(s = surface.First(); s; s = surface.NextAfter(s)) { - if(s->CoincidentWithPlane(n, d)) { - s->MakeSectionEdgesInto(this, sel, sbl); - } - } -} - -void SShell::TriangulateInto(SMesh *sm) { -#pragma omp parallel for - for(int i=0; iTriangulateInto(this, &m); - #pragma omp critical - sm->MakeFromCopyOf(&m); - m.Clear(); - } -} - -bool SShell::IsEmpty() const { - return surface.IsEmpty(); -} - -void SShell::Clear() { - SSurface *s; - for(s = surface.First(); s; s = surface.NextAfter(s)) { - s->Clear(); - } - surface.Clear(); - - SCurve *c; - for(c = curve.First(); c; c = curve.NextAfter(c)) { - c->Clear(); - } - curve.Clear(); -} +} // namespace SolveSpace diff --git a/src/srf/surface.h b/src/srf/surface.h index 549745525..d11716762 100644 --- a/src/srf/surface.h +++ b/src/srf/surface.h @@ -10,6 +10,15 @@ #ifndef SOLVESPACE_SURFACE_H #define SOLVESPACE_SURFACE_H +#include + +#include "dsc.h" +#include "handle.h" +#include "polygon.h" + +namespace SolveSpace { + +class Group; class SBezierList; class SSurface; class SCurvePt; @@ -407,10 +416,10 @@ class SShell { // outside, or coincident (with parallel or antiparallel normal) with a // shell. enum class Class : uint32_t { - INSIDE = 100, - OUTSIDE = 200, - COINC_SAME = 300, - COINC_OPP = 400 + SURF_INSIDE = 100, + SURF_OUTSIDE = 200, + SURF_COINC_SAME = 300, + SURF_COINC_OPP = 400 }; static const double DOTP_TOL; Class ClassifyRegion(Vector edge_n, Vector inter_surf_n, @@ -435,5 +444,6 @@ class SShell { void Clear(); }; -#endif +} // namespace SolveSpace +#endif diff --git a/src/srf/surfinter.cpp b/src/srf/surfinter.cpp index 0a827761b..c13c7d2f9 100644 --- a/src/srf/surfinter.cpp +++ b/src/srf/surfinter.cpp @@ -7,6 +7,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + extern int FLAG; void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, @@ -23,20 +25,20 @@ void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, // Now we have to piecewise linearize the curve. If there's already an // identical curve in the shell, then follow that pwl exactly, otherwise // calculate from scratch. - SCurve split, *existing = NULL, *se; + SCurve split, *existing = NULL; SBezier sbrev = *sb; sbrev.Reverse(); bool backwards = false; #pragma omp critical(into) { - for(se = into->curve.First(); se; se = into->curve.NextAfter(se)) { - if(se->isExact) { - if(sb->Equals(&(se->exact))) { - existing = se; + for(SCurve &se : into->curve) { + if(se.isExact) { + if(sb->Equals(&(se.exact))) { + existing = &se; break; } - if(sbrev.Equals(&(se->exact))) { - existing = se; + if(sbrev.Equals(&(se.exact))) { + existing = &se; backwards = true; break; } @@ -332,15 +334,14 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, shext = agnstA; } bool foundExact = false; - SCurve *sc; - for(sc = shext->curve.First(); sc; sc = shext->curve.NextAfter(sc)) { - if(sc->source == SCurve::Source::INTERSECTION) continue; - if(!sc->isExact) continue; - if((sc->surfA != sext->h) && (sc->surfB != sext->h)) continue; + for(SCurve &sc : shext->curve) { + if(sc.source == SCurve::Source::INTERSECTION) continue; + if(!sc.isExact) continue; + if((sc.surfA != sext->h) && (sc.surfB != sext->h)) continue; // we have a curve belonging to the curved surface and not the plane. // does it lie completely in the plane? - if(splane->ContainsPlaneCurve(sc)) { - SBezier bezier = sc->exact; + if(splane->ContainsPlaneCurve(&sc)) { + SBezier bezier = sc.exact; AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into); foundExact = true; } @@ -457,13 +458,11 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, } } + Vector dp = nb.Cross(na).WithMagnitude(1.0); + if(!fwd) dp = dp.ScaledBy(-1.0); int i; for(i = 0; i < 20; i++) { - Vector dp = nb.Cross(na); - if(!fwd) dp = dp.ScaledBy(-1); - dp = dp.WithMagnitude(step); - - np = start.Plus(dp); + np = start.Plus(dp.ScaledBy(step)); npc = ClosestPointOnThisAndSurface(b, np); tol = (npc.Minus(np)).Magnitude(); @@ -571,10 +570,9 @@ bool SSurface::ContainsPlaneCurve(SCurve *sc) const { void SShell::MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal, SEdgeList *el, SShell *useCurvesFrom) { - SSurface *ss; - for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { - if(proto->CoincidentWith(ss, sameNormal)) { - ss->MakeEdgesInto(this, el, SSurface::MakeAs::XYZ, useCurvesFrom); + for(SSurface &ss : surface) { + if(proto->CoincidentWith(&ss, sameNormal)) { + ss.MakeEdgesInto(this, el, SSurface::MakeAs::XYZ, useCurvesFrom); } } @@ -595,3 +593,4 @@ void SShell::MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal, } } +} // namespace SolveSpace diff --git a/src/srf/triangulate.cpp b/src/srf/triangulate.cpp index d02f96380..81a2a4534 100644 --- a/src/srf/triangulate.cpp +++ b/src/srf/triangulate.cpp @@ -6,7 +6,9 @@ // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- -#include "../solvespace.h" +#include "solvespace.h" + +namespace SolveSpace { void SPolygon::UvTriangulateInto(SMesh *m, SSurface *srf) { if(l.n <= 0) return; @@ -426,7 +428,7 @@ void SContour::UvTriangulateInto(SMesh *m, SSurface *srf) { if (i == l.n-1) { end = true; } - if (end) { // triangulate the fan and tag the verticies + if (end) { // triangulate the fan and tag the vertices if (j > 3) { Vector center = l[pstart+1].p.Plus(l[pstart+j-1].p).ScaledBy(0.5); for (int x=0; xMakeTriangulationGridInto(&lj, 0, 1, /*swapped=*/false, 0); // force 2nd order grid to have at least 4 segments in each direction - if ((li.n < 5) && (srf->degm>1)) { // 4 segments minimun + if ((li.n < 5) && (srf->degm>1)) { // 4 segments minimum li.Clear(); li.Add(&v[0]);li.Add(&v[1]);li.Add(&v[2]);li.Add(&v[3]);li.Add(&v[4]); } - if ((lj.n < 5) && (srf->degn>1)) { // 4 segments minimun + if ((lj.n < 5) && (srf->degn>1)) { // 4 segments minimum lj.Clear(); lj.Add(&v[0]);lj.Add(&v[1]);lj.Add(&v[2]);lj.Add(&v[3]);lj.Add(&v[4]); } @@ -681,7 +683,7 @@ void SPolygon::UvGridTriangulateInto(SMesh *mesh, SSurface *srf) { if (!bottom[j]) // add our own bottom edge holes.AddEdge(a, b); } else { - if (prev_flag) // add our left neighbots right edge + if (prev_flag) // add our left neighbors right edge holes.AddEdge(a, d); if (bottom[j]) // add our bottom neighbors top edge holes.AddEdge(b, a); @@ -732,3 +734,5 @@ void SPolygon::TriangulateInto(SMesh *m) const { p.Clear(); pm.Clear(); } + +} // namespace SolveSpace diff --git a/src/style.cpp b/src/style.cpp index b4ed841e4..950b04e9c 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -7,6 +7,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + const Style::Default Style::Defaults[] = { { { ACTIVE_GRP }, "ActiveGrp", RGBf(1.0, 1.0, 1.0), 1.5, 4, true, StipplePattern::CONTINUOUS }, { { CONSTRUCTION }, "Construction", RGBf(0.1, 0.7, 0.1), 1.5, 0, false, StipplePattern::CONTINUOUS }, @@ -114,7 +116,8 @@ void Style::FillDefaultStyle(Style *s, const Default *d, bool factory) { s->stippleType = (factory) ? d->stippleType : Style::StipplePatternFromString( - settings->ThawString(CnfStippleType(d->cnfPrefix), "")); + settings->ThawString(CnfStippleType(d->cnfPrefix), + StipplePatternName(d->stippleType))); s->stippleScale = (factory) ? 15.0 : settings->ThawFloat(CnfStippleScale(d->cnfPrefix), 15.0); @@ -397,7 +400,11 @@ StipplePattern Style::PatternType(hStyle hs) { std::string Style::StipplePatternName(hStyle hs) { Style *s = Get(hs); - switch(s->stippleType) { + return StipplePatternName(s->stippleType); +} + +std::string Style::StipplePatternName(StipplePattern stippleType) { + switch(stippleType) { case StipplePattern::CONTINUOUS: return "Continuous"; case StipplePattern::SHORT_DASH: return "ShortDash"; case StipplePattern::DASH: return "Dash"; @@ -409,10 +416,9 @@ std::string Style::StipplePatternName(hStyle hs) { case StipplePattern::ZIGZAG: return "ZigZag"; } - return "CONTINUOUS"; + return "Continuous"; } - double Style::StippleScale(hStyle hs) { Style *s = Get(hs); return s->stippleScale; @@ -466,14 +472,13 @@ void TextWindow::ShowListOfStyles() { Printf(true, "%Ft color style-name"); bool darkbg = false; - Style *s; - for(s = SK.style.First(); s; s = SK.style.NextAfter(s)) { + for(Style &s : SK.style) { Printf(false, "%Bp %Bz %Bp %Fl%Ll%f%D%s%E", darkbg ? 'd' : 'a', - &s->color, + &s.color, darkbg ? 'd' : 'a', - ScreenShowStyleInfo, s->h.v, - s->DescriptionString().c_str()); + ScreenShowStyleInfo, s.h.v, + s.DescriptionString().c_str()); darkbg = !darkbg; } @@ -560,7 +565,7 @@ void TextWindow::ScreenChangeStyleMetric(int link, uint32_t v) { if(units == Style::UnitsAs::PIXELS) { edit_value = ssprintf("%.2f", val); } else { - edit_value = SS.MmToString(val); + edit_value = SS.MmToString(val, true); } SS.TW.ShowEditControl(col, edit_value); SS.TW.edit.style = hs; @@ -939,3 +944,5 @@ void TextWindow::ShowStyleInfo() { void TextWindow::ScreenAssignSelectionToStyle(int link, uint32_t v) { Style::AssignSelectionToStyle(v); } + +} // namespace SolveSpace diff --git a/src/system.cpp b/src/system.cpp index 1119c3431..89d315938 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -8,277 +8,311 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -// This tolerance is used to determine whether two (linearized) constraints -// are linearly dependent. If this is too small, then we will attempt to -// solve truly inconsistent systems and fail. But if it's too large, then -// we will give up on legitimate systems like a skinny right angle triangle by -// its hypotenuse and long side. -const double System::RANK_MAG_TOLERANCE = 1e-4; +#include +#include + +namespace SolveSpace { // The solver will converge all unknowns to within this tolerance. This must // always be much less than LENGTH_EPS, and in practice should be much less. const double System::CONVERGE_TOLERANCE = (LENGTH_EPS/(1e2)); -bool System::WriteJacobian(int tag) { - - int j = 0; - for(auto &p : param) { - if(j >= MAX_UNKNOWNS) - return false; +constexpr size_t LikelyPartialCountPerEq = 10; - if(p.tag != tag) - continue; - mat.param[j] = p.h; - j++; +bool System::WriteJacobian(int tag) { + // Clear all + mat.param.clear(); + mat.eq.clear(); + mat.A.sym.setZero(); + mat.B.sym.clear(); + + for(Equation &e : eq) { + if(e.tag != tag) continue; + mat.eq.push_back(&e); } - mat.n = j; - - int i = 0; - - for(auto &e : eq) { - if(i >= MAX_UNKNOWNS) return false; - - if(e.tag != tag) - continue; - - mat.eq[i] = e.h; - Expr *f = e.e->DeepCopyWithParamsAsPointers(¶m, &(SK.param)); - f = f->FoldConstants(); - - // Hash table (61 bits) to accelerate generation of zero partials. - uint64_t scoreboard = f->ParamsUsed(); - for(j = 0; j < mat.n; j++) { - Expr *pd; - if(scoreboard & ((uint64_t)1 << (mat.param[j].v % 61)) && - f->DependsOn(mat.param[j])) - { - pd = f->PartialWrt(mat.param[j]); - pd = pd->FoldConstants(); - pd = pd->DeepCopyWithParamsAsPointers(¶m, &(SK.param)); - } else { - pd = Expr::From(0.0); - } - mat.A.sym[i][j] = pd; + if(mat.eq.size() >= MAX_UNKNOWNS) { + return false; + } + mat.m = mat.eq.size(); + + std::unordered_map paramToIndex; + for(Param &p : param) { + if(p.tag != tag) continue; + // Fill the param id to index map + paramToIndex[p.h.v] = mat.param.size(); + mat.param.push_back(p.h); + } + mat.n = mat.param.size(); + + // In some experimenting, this is almost always the right size. + // Value is usually between 0 and 20, comes from number of constraints? + mat.A.sym.resize(mat.m, mat.n); + mat.A.sym.reserve(Eigen::VectorXi::Constant(mat.n, LikelyPartialCountPerEq)); + + mat.B.sym.reserve(mat.eq.size()); + for(size_t i = 0; i < mat.eq.size(); i++) { + Equation *e = mat.eq[i]; + // Deep-copy and simplify (fold) the current equation. + Expr *f = e->e->DeepCopyWithParamsAsPointers(¶m, &(SK.param), /*foldConstants=*/true); + + ParamSet paramsUsed; + f->ParamsUsedList(¶msUsed); + + for(hParam p : paramsUsed) { + // Find the index of this parameter + auto it = paramToIndex.find(p.v); + if(it == paramToIndex.end()) continue; + // this is the parameter index + const int j = it->second; + // compute partial derivative of f + Expr *pd = f->PartialWrt(p); + pd = pd->FoldConstants(/*allocCopy=*/false); + if(pd->IsZeroConst()) + continue; + mat.A.sym.insert(i, j) = pd; } - mat.B.sym[i] = f; - i++; + mat.B.sym.push_back(f); } - mat.m = i; - return true; } void System::EvalJacobian() { - int i, j; - for(i = 0; i < mat.m; i++) { - for(j = 0; j < mat.n; j++) { - mat.A.num[i][j] = (mat.A.sym[i][j])->Eval(); + using namespace Eigen; + mat.A.num.setZero(); + mat.A.num.resize(mat.m, mat.n); + const int size = mat.A.sym.outerSize(); + + for(int k = 0; k < size; k++) { + for(SparseMatrix ::InnerIterator it(mat.A.sym, k); it; ++it) { + double value = it.value()->Eval(); + if(EXACT(value == 0.0)) continue; + mat.A.num.insert(it.row(), it.col()) = value; } } + mat.A.num.makeCompressed(); } bool System::IsDragged(hParam p) { - hParam *pp; - for(pp = dragged.First(); pp; pp = dragged.NextAfter(pp)) { - if(p == *pp) return true; - } - return false; + return dragged.find(p) != dragged.end(); } -void System::SolveBySubstitution() { +SubstitutionMap System::SolveBySubstitution() { + // Contains pointers to last substitutions in a substitution chain + std::vector subVec; + // Maps a parameter to the index of its last substitution in `subVec` + std::unordered_map> leaves; + // Tracks how many slots in `subVec` contain a specific last substitution + // (this can happen as slots that once contained a specific substitution + // are updated to point to another over the run of the substitution algorithm) + std::unordered_map, HandleHasher> slotTrack; + for(auto &teq : eq) { Expr *tex = teq.e; + // If we have `(a - b) = 0` where both a and b are parameters, then `a = b` and we can substitute if(tex->op == Expr::Op::MINUS && tex->a->op == Expr::Op::PARAM && tex->b->op == Expr::Op::PARAM) { - hParam a = tex->a->parh; - hParam b = tex->b->parh; - if(!(param.FindByIdNoOops(a) && param.FindByIdNoOops(b))) { + Param *sub = param.FindByIdNoOops(tex->a->parh); + Param *by = param.FindByIdNoOops(tex->b->parh); + if(!sub || !by) { // Don't substitute unless they're both solver params; // otherwise it's an equation that can be solved immediately, // or an error to flag later. continue; } - if(IsDragged(a)) { - // A is being dragged, so A should stay, and B should go - std::swap(a, b); + if(sub->h == by->h) { + teq.tag = EQ_SUBSTITUTED; + continue; + } + + // Take the last substitution of parameter a + size_t subIdx = 0; + auto it = leaves.find(sub->h); + if(it != leaves.end()) { + subIdx = it->second; + sub = subVec.at(it->second - 1); + } + + // Take the last substitution of parameter b + size_t byIdx = 0; + it = leaves.find(by->h); + if(it != leaves.end()) { + byIdx = it->second; + by = subVec.at(it->second - 1); } - for(auto &req : eq) { - req.e->Substitute(a, b); // A becomes B, B unchanged + // If the last substituton of `a` is a dragged param, keep it + // and substitute the other param + if(IsDragged(sub->h)) { + std::swap(sub, by); + std::swap(subIdx, byIdx); } - for(auto &rp : param) { - if(rp.substd == a) { - rp.substd = b; + + if(subIdx == 0) { + if(byIdx == 0) { + // Neither `sub` nor `by` are in the map, so add them and + // set the target index + subVec.push_back(by); + leaves[by->h] = leaves[sub->h] = subVec.size(); + } else { + // `sub` isn't in the map, but `by` is, so just add `sub` to + // the map with `by` as the target + leaves[sub->h] = byIdx; + } + } else { + // `sub` already exists in the map, so just update any slots + // that point to it as the last substitution to point to `by` + // instead + auto it = slotTrack.find(sub->h); + if(it == slotTrack.end()) { + // There's only this one slot, so just replace it with `by` + subVec[subIdx - 1] = by; + + // If `by` was already in the map, that means we now have + // an additional slot where it resides, so add it to the + // slot tracker + if(byIdx != 0) { + // If `by` is already in the slot tracker, we'll get + // back a vector with at least two elements; otherwise + // this access will add a new item to the slot tracker + // with an empty vector + auto &bySlots = slotTrack[by->h]; + if(bySlots.empty()) { + bySlots.push_back(byIdx); + } + bySlots.push_back(subIdx); + } + } else { + // We have more than one slot pointing to `sub`, so update + // all of the slots to point to `by` + for(size_t i : it->second) { + subVec[i - 1] = by; + } + + // No more slots are pointing to `sub`, so extract the slot list + // and erase `sub` from the tracker + auto subSlots = std::move(it->second); + slotTrack.erase(it); + + // Same as above: this access either gives us an existing vector + // with at least two elements, or creates an empty vector + auto &bySlots = slotTrack[by->h]; + if(bySlots.empty()) { + bySlots = std::move(subSlots); + if(byIdx != 0) { + bySlots.push_back(byIdx); + } + } else { + bySlots.insert(bySlots.end(), subSlots.begin(), subSlots.end()); + } } + + leaves[by->h] = subIdx; } - Param *ptr = param.FindById(a); - ptr->tag = VAR_SUBSTITUTED; - ptr->substd = b; + sub->tag = VAR_SUBSTITUTED; teq.tag = EQ_SUBSTITUTED; } } + + SubstitutionMap subMap; + for(auto &sub : leaves) { + Param *by = subVec[sub.second - 1]; + if(sub.first != by->h) { + subMap[sub.first] = by; + } + } + + // Substitute all the equations + for(auto &req : eq) { + req.e->Substitute(subMap); + } + + return subMap; } //----------------------------------------------------------------------------- -// Calculate the rank of the Jacobian matrix, by Gram-Schimdt orthogonalization -// in place. A row (~equation) is considered to be all zeros if its magnitude -// is less than the tolerance RANK_MAG_TOLERANCE. +// Calculate the rank of the Jacobian matrix //----------------------------------------------------------------------------- int System::CalculateRank() { - // Actually work with magnitudes squared, not the magnitudes - double rowMag[MAX_UNKNOWNS] = {}; - double tol = RANK_MAG_TOLERANCE*RANK_MAG_TOLERANCE; - - int i, iprev, j; - int rank = 0; - - for(i = 0; i < mat.m; i++) { - // Subtract off this row's component in the direction of any - // previous rows - for(iprev = 0; iprev < i; iprev++) { - if(rowMag[iprev] <= tol) continue; // ignore zero rows - - double dot = 0; - for(j = 0; j < mat.n; j++) { - dot += (mat.A.num[iprev][j]) * (mat.A.num[i][j]); - } - for(j = 0; j < mat.n; j++) { - mat.A.num[i][j] -= (dot/rowMag[iprev])*mat.A.num[iprev][j]; - } - } - // Our row is now normal to all previous rows; calculate the - // magnitude of what's left - double mag = 0; - for(j = 0; j < mat.n; j++) { - mag += (mat.A.num[i][j]) * (mat.A.num[i][j]); - } - if(mag > tol) { - rank++; - } - rowMag[i] = mag; - } - - return rank; + using namespace Eigen; + if(mat.n == 0 || mat.m == 0) return 0; + SparseQR , COLAMDOrdering> solver; + solver.compute(mat.A.num); + int result = solver.rank(); + return result; } -bool System::TestRank(int *rank) { +bool System::TestRank(int *dof, int *rank) { EvalJacobian(); int jacobianRank = CalculateRank(); - if(rank) *rank = jacobianRank; + // We are calculating dof based on real rank, not mat.m. + // Using this approach we can calculate real dof even when redundant is allowed. + if(dof != NULL) *dof = mat.n - jacobianRank; + if(rank) { + *rank = jacobianRank; + } return jacobianRank == mat.m; } -bool System::SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS], - double B[], int n) +bool System::SolveLinearSystem(const Eigen::SparseMatrix &A, + const Eigen::VectorXd &B, Eigen::VectorXd *X) { - // Gaussian elimination, with partial pivoting. It's an error if the - // matrix is singular, because that means two constraints are - // equivalent. - int i, j, ip, jp, imax = 0; - double max, temp; - - for(i = 0; i < n; i++) { - // We are trying eliminate the term in column i, for rows i+1 and - // greater. First, find a pivot (between rows i and N-1). - max = 0; - for(ip = i; ip < n; ip++) { - if(fabs(A[ip][i]) > max) { - imax = ip; - max = fabs(A[ip][i]); - } - } - // Don't give up on a singular matrix unless it's really bad; the - // assumption code is responsible for identifying that condition, - // so we're not responsible for reporting that error. - if(fabs(max) < 1e-20) continue; - - // Swap row imax with row i - for(jp = 0; jp < n; jp++) { - swap(A[i][jp], A[imax][jp]); - } - swap(B[i], B[imax]); - - // For rows i+1 and greater, eliminate the term in column i. - for(ip = i+1; ip < n; ip++) { - temp = A[ip][i]/A[i][i]; - - for(jp = i; jp < n; jp++) { - A[ip][jp] -= temp*(A[i][jp]); - } - B[ip] -= temp*B[i]; - } - } - - // We've put the matrix in upper triangular form, so at this point we - // can solve by back-substitution. - for(i = n - 1; i >= 0; i--) { - if(fabs(A[i][i]) < 1e-20) continue; - - temp = B[i]; - for(j = n - 1; j > i; j--) { - temp -= X[j]*A[i][j]; - } - X[i] = temp / A[i][i]; - } - - return true; + if(A.outerSize() == 0) return true; + using namespace Eigen; + SparseQR, COLAMDOrdering> solver; + //SimplicialLDLT> solver; + solver.compute(A); + *X = solver.solve(B); + return (solver.info() == Success); } bool System::SolveLeastSquares() { - int r, c, i; - + using namespace Eigen; // Scale the columns; this scale weights the parameters for the least // squares solve, so that we can encourage the solver to make bigger // changes in some parameters, and smaller in others. - for(c = 0; c < mat.n; c++) { + VectorXd scale = VectorXd::Ones(mat.n); + for(int c = 0; c < mat.n; c++) { if(IsDragged(mat.param[c])) { // It's least squares, so this parameter doesn't need to be all // that big to get a large effect. - mat.scale[c] = 1/20.0; - } else { - mat.scale[c] = 1; - } - for(r = 0; r < mat.m; r++) { - mat.A.num[r][c] *= mat.scale[c]; + scale[c] = 1 / 20.0; } } - // Write A*A' - for(r = 0; r < mat.m; r++) { - for(c = 0; c < mat.m; c++) { // yes, AAt is square - double sum = 0; - for(i = 0; i < mat.n; i++) { - sum += mat.A.num[r][i]*mat.A.num[c][i]; - } - mat.AAt[r][c] = sum; + const int size = mat.A.num.outerSize(); + for(int k = 0; k < size; k++) { + for(SparseMatrix::InnerIterator it(mat.A.num, k); it; ++it) { + it.valueRef() *= scale[it.col()]; } } - if(!SolveLinearSystem(mat.Z, mat.AAt, mat.B.num, mat.m)) return false; + SparseMatrix AAt = mat.A.num * mat.A.num.transpose(); + AAt.makeCompressed(); + VectorXd z(mat.n); - // And multiply that by A' to get our solution. - for(c = 0; c < mat.n; c++) { - double sum = 0; - for(i = 0; i < mat.m; i++) { - sum += mat.A.num[i][c]*mat.Z[i]; - } - mat.X[c] = sum * mat.scale[c]; + if(!SolveLinearSystem(AAt, mat.B.num, &z)) return false; + + mat.X = mat.A.num.transpose() * z; + + for(int c = 0; c < mat.n; c++) { + mat.X[c] *= scale[c]; } return true; } -bool System::NewtonSolve(int tag) { +bool System::NewtonSolve() { int iter = 0; bool converged = false; int i; // Evaluate the functions at our operating point. + mat.B.num = Eigen::VectorXd(mat.m); for(i = 0; i < mat.m; i++) { mat.B.num[i] = (mat.B.sym[i])->Eval(); } @@ -302,13 +336,15 @@ bool System::NewtonSolve(int tag) { // Re-evalute the functions, since the params have just changed. for(i = 0; i < mat.m; i++) { mat.B.num[i] = (mat.B.sym[i])->Eval(); + if(IsReasonable(mat.B.num[i])) { + // Very bad, and clearly not convergent + return false; + } } + // Check for convergence converged = true; for(i = 0; i < mat.m; i++) { - if(IsReasonable(mat.B.num[i])) { - return false; - } if(fabs(mat.B.num[i]) > CONVERGE_TOLERANCE) { converged = false; break; @@ -400,32 +436,33 @@ void System::FindWhichToRemoveToFixJacobian(Group *g, List *bad, bo } } -SolveResult System::Solve(Group *g, int *rank, int *dof, List *bad, +SolveResult System::Solve(Group *g, int *dof, List *bad, bool andFindBad, bool andFindFree, bool forceDofCheck) { WriteEquationsExceptFor(Constraint::NO_CONSTRAINT, g); - int i; bool rankOk; -/* - dbp("%d equations", eq.n); - for(i = 0; i < eq.n; i++) { - dbp(" %.3f = %s = 0", eq[i].e->Eval(), eq[i].e->Print()); - } - dbp("%d parameters", param.n); - for(i = 0; i < param.n; i++) { - dbp(" param %08x at %.3f", param[i].h.v, param[i].val); - } */ + // int x; + // printf("%d equations", eq.n); + // for(x = 0; x < eq.n; x++) { + // printf(" %.3f = %s = 0", eq[x].e->Eval(), eq[x].e->Print().c_str()); + // } + // printf("%d parameters", param.n); + // for(x = 0; x < param.n; x++) { + // printf(" param %08x at %.3f", param[x].h.v, param[x].val); + // } // All params and equations are assigned to group zero. param.ClearTags(); eq.ClearTags(); - // Solving by substitution eliminates duplicate e.g. H/V constraints, which can cause rank test - // to succeed even on overdefined systems, which will fail later. - if(!forceDofCheck) { - SolveBySubstitution(); + SubstitutionMap subMap; + + // Since we are suppressing dof calculation or allowing redundant, we + // can't / don't want to catch result of dof checking without substitution + if(g->suppressDofCalculation || g->allowRedundant || !forceDofCheck) { + subMap = SolveBySubstitution(); } // Before solving the big system, see if we can find any equations that @@ -447,7 +484,7 @@ SolveResult System::Solve(Group *g, int *rank, int *dof, List *bad, e.tag = alone; p->tag = alone; WriteJacobian(alone); - if(!NewtonSolve(alone)) { + if(!NewtonSolve()) { // We don't do the rank test, so let's arbitrarily return // the DIDNT_CONVERGE result here. rankOk = true; @@ -462,33 +499,29 @@ SolveResult System::Solve(Group *g, int *rank, int *dof, List *bad, if(!WriteJacobian(0)) { return SolveResult::TOO_MANY_UNKNOWNS; } - - rankOk = TestRank(rank); + // Clear dof value in order to have indication when dof is actually not calculated + if(dof != NULL) *dof = -1; + // We are suppressing or allowing redundant, so we no need to catch unsolveable + redundant + rankOk = (!g->suppressDofCalculation && !g->allowRedundant) ? TestRank(dof) : true; // And do the leftovers as one big system - if(!NewtonSolve(0)) { + if(!NewtonSolve()) { goto didnt_converge; } - rankOk = TestRank(rank); + // Here we are want to calculate dof even when redundant is allowed, so just handle suppressing + rankOk = (!g->suppressDofCalculation) ? TestRank(dof) : true; if(!rankOk) { if(andFindBad) FindWhichToRemoveToFixJacobian(g, bad, forceDofCheck); } else { - // This is not the full Jacobian, but any substitutions or single-eq - // solves removed one equation and one unknown, therefore no effect - // on the number of DOF. - if(dof) *dof = CalculateDof(); MarkParamsFree(andFindFree); } // System solved correctly, so write the new values back in to the // main parameter table. for(auto &p : param) { - double val; - if(p.tag == VAR_SUBSTITUTED) { - val = param.FindById(p.substd)->val; - } else { - val = p.val; - } + auto it = subMap.find(p.h); + double val = it == subMap.end() ? p.val : it->second->val; + Param *pp = SK.GetParam(p.h); pp->val = val; pp->known = true; @@ -499,12 +532,12 @@ SolveResult System::Solve(Group *g, int *rank, int *dof, List *bad, didnt_converge: SK.constraint.ClearTags(); // Not using range-for here because index is used in additional ways - for(i = 0; i < eq.n; i++) { + for(size_t i = 0; i < mat.eq.size(); i++) { if(fabs(mat.B.num[i]) > CONVERGE_TOLERANCE || IsReasonable(mat.B.num[i])) { // This constraint is unsatisfied. - if(!mat.eq[i].isFromConstraint()) continue; + if(!mat.eq[i]->h.isFromConstraint()) continue; - hConstraint hc = mat.eq[i].constraint(); + hConstraint hc = mat.eq[i]->h.constraint(); ConstraintBase *c = SK.constraint.FindByIdNoOops(hc); if(!c) continue; // Don't double-show constraints that generated multiple @@ -534,11 +567,14 @@ SolveResult System::SolveRank(Group *g, int *rank, int *dof, List * return SolveResult::TOO_MANY_UNKNOWNS; } - bool rankOk = TestRank(rank); + bool rankOk = TestRank(dof, rank); if(!rankOk) { - if(andFindBad) FindWhichToRemoveToFixJacobian(g, bad, /*forceDofCheck=*/true); + // When we are testing with redundant allowed, we don't want to have additional info + // about redundants since this test is working only for single redundant constraint + if(!g->suppressDofCalculation && !g->allowRedundant) { + if(andFindBad) FindWhichToRemoveToFixJacobian(g, bad, true); + } } else { - if(dof) *dof = CalculateDof(); MarkParamsFree(andFindFree); } return rankOk ? SolveResult::OKAY : SolveResult::REDUNDANT_OKAY; @@ -548,7 +584,9 @@ void System::Clear() { entity.Clear(); param.Clear(); eq.Clear(); - dragged.Clear(); + dragged.clear(); + mat.A.num.setZero(); + mat.A.sym.setZero(); } void System::MarkParamsFree(bool find) { @@ -573,7 +611,4 @@ void System::MarkParamsFree(bool find) { } } -int System::CalculateDof() { - return mat.n - mat.m; -} - +} // namespace SolveSpace diff --git a/src/textscreens.cpp b/src/textscreens.cpp index 07d5cdeea..41dcb1b7d 100644 --- a/src/textscreens.cpp +++ b/src/textscreens.cpp @@ -6,6 +6,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + //----------------------------------------------------------------------------- // A navigation bar that always appears at the top of the window, with a // link to bring us back home. @@ -57,11 +59,13 @@ void TextWindow::ScreenToggleGroupShown(int link, uint32_t v) { SS.GenerateAll(); } void TextWindow::ScreenShowGroupsSpecial(int link, uint32_t v) { - bool state = link == 's'; for(hGroup hg : SK.groupOrder) { Group *g = SK.GetGroup(hg); - - g->visible = state; + switch(link) { + case 's': g->visible = true; break; + case 'c': g->visible = g->solved.dof != 0; break; + case 'h': g->visible = false; break; + } } SS.GW.persistentDirty = true; } @@ -123,13 +127,18 @@ void TextWindow::ShowListOfGroups() { sprintf(sdof, "%-3d", dof); } } + std::string suffix; + if(g->forceToMesh || g->IsTriangleMeshAssembly()) { + suffix = " (∆)"; + } + bool ref = (g->h == Group::HGROUP_REFERENCES); Printf(false, "%Bp%Fd " "%Ft%s%Fb%D%f%Ll%s%E " "%Fb%s%D%f%Ll%s%E " "%Fp%D%f%s%Ll%s%E " - "%Fl%Ll%D%f%s", + "%Fp%Ll%D%f%s%E%s", // Alternate between light and dark backgrounds, for readability backgroundParity ? 'd' : 'a', // Link that activates the group @@ -146,13 +155,16 @@ void TextWindow::ShowListOfGroups() { ok ? ((warn && SS.checkClosedContour) ? "err" : sdof) : "", ok ? "" : "ERR", // Link to a screen that gives more details on the group - g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str()); + g->suppress ? 'g' : 'l', + g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str(), + suffix.c_str()); if(active) afterActive = true; backgroundParity = !backgroundParity; } - Printf(true, " %Fl%Ls%fshow all%E / %Fl%Lh%fhide all%E", + Printf(true, " %Fl%Ls%fshow all%E / %Fl%Lc%fonly unconstrained%E / %Fl%Lh%fhide all%E", + &(TextWindow::ScreenShowGroupsSpecial), &(TextWindow::ScreenShowGroupsSpecial), &(TextWindow::ScreenShowGroupsSpecial)); Printf(true, " %Fl%Ls%fline styles%E /" @@ -187,8 +199,7 @@ void TextWindow::ScreenHoverRequest(int link, uint32_t v) { SS.GW.hover.emphasized = true; } void TextWindow::ScreenHoverConstraint(int link, uint32_t v) { - if(!SS.GW.showConstraints) return; - + if( SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_NOSHOW ) return; hConstraint hc = { v }; SS.GW.hover.Clear(); SS.GW.hover.constraint = hc; @@ -220,8 +231,36 @@ void TextWindow::ScreenChangeGroupOption(int link, uint32_t v) { Group *g = SK.GetGroup(SS.TW.shown.group); switch(link) { - case 's': g->subtype = Group::Subtype::ONE_SIDED; break; - case 'S': g->subtype = Group::Subtype::TWO_SIDED; break; + case 's': + if(g->subtype == Group::Subtype::TWO_SIDED) + g->subtype = Group::Subtype::ONE_SIDED; + if(g->subtype == Group::Subtype::TWO_SKEWED) + g->subtype = Group::Subtype::ONE_SKEWED; + break; + case 'S': + if(g->subtype == Group::Subtype::ONE_SIDED) + g->subtype = Group::Subtype::TWO_SIDED; + if(g->subtype == Group::Subtype::ONE_SKEWED) + g->subtype = Group::Subtype::TWO_SKEWED; + break; + case 'w': + if(g->subtype == Group::Subtype::ONE_SIDED) { + g->subtype = Group::Subtype::ONE_SKEWED; + break; + } + if(g->subtype == Group::Subtype::TWO_SIDED) { + g->subtype = Group::Subtype::TWO_SKEWED; + break; + } + if(g->subtype == Group::Subtype::ONE_SKEWED) { + g->subtype = Group::Subtype::ONE_SIDED; + break; + } + if(g->subtype == Group::Subtype::TWO_SKEWED) { + g->subtype = Group::Subtype::TWO_SIDED; + break; + } + break; case 'k': g->skipFirst = true; break; case 'K': g->skipFirst = false; break; @@ -254,6 +293,8 @@ void TextWindow::ScreenChangeGroupOption(int link, uint32_t v) { case 'e': g->allowRedundant = !(g->allowRedundant); break; + case 'D': g->suppressDofCalculation = !(g->suppressDofCalculation); break; + case 'v': g->visible = !(g->visible); break; case 'd': g->allDimsReference = !(g->allDimsReference); break; @@ -299,6 +340,23 @@ void TextWindow::ScreenChangeGroupScale(int link, uint32_t v) { SS.TW.edit.meaning = Edit::GROUP_SCALE; SS.TW.edit.group.v = v; } +void TextWindow::ScreenChangeHelixPitch(int link, uint32_t v) { + Group *g = SK.GetGroup(SS.TW.shown.group); + double pitch = g->valB/SS.MmPerUnit(); + SS.TW.ShowEditControl(3, ssprintf("%.8f", pitch)); + SS.TW.edit.meaning = Edit::HELIX_PITCH; + SS.TW.edit.group.v = v; +} +void TextWindow::ScreenChangePitchOption(int link, uint32_t v) { + Group *g = SK.GetGroup(SS.TW.shown.group); + if(g->valB == 0.0) { + g->valB = SK.GetParam(g->h.param(7))->val * PI / + (SK.GetParam(g->h.param(3))->val); + } else { + g->valB = 0.0; + } + SS.GW.Invalidate(); +} void TextWindow::ScreenDeleteGroup(int link, uint32_t v) { SS.UndoRemember(); @@ -351,15 +409,34 @@ void TextWindow::ShowGroupInfo() { } Printf(true, " %Ft%s%E", s); - bool one = (g->subtype == Group::Subtype::ONE_SIDED); + bool one = ((g->subtype == Group::Subtype::ONE_SIDED) || + (g->subtype == Group::Subtype::ONE_SKEWED)); + bool two = ((g->subtype == Group::Subtype::TWO_SIDED) || + (g->subtype == Group::Subtype::TWO_SKEWED)); + bool skew = ((g->subtype == Group::Subtype::ONE_SKEWED) || + (g->subtype == Group::Subtype::TWO_SKEWED)); + + if (g->type == Group::Type::EXTRUDE) { + Printf(false, + "%Ba %f%Ls%Fd%s one-sided%E " + "%f%LS%Fd%s two-sided%E " + "%f%Lw%Fd%s skewed%E", + &TextWindow::ScreenChangeGroupOption, + one ? RADIO_TRUE : RADIO_FALSE, + &TextWindow::ScreenChangeGroupOption, + two ? RADIO_TRUE : RADIO_FALSE, + &TextWindow::ScreenChangeGroupOption, + skew ? CHECK_TRUE : CHECK_FALSE); + } else { Printf(false, "%Ba %f%Ls%Fd%s one-sided%E " "%f%LS%Fd%s two-sided%E", &TextWindow::ScreenChangeGroupOption, one ? RADIO_TRUE : RADIO_FALSE, &TextWindow::ScreenChangeGroupOption, - !one ? RADIO_TRUE : RADIO_FALSE); - + two ? RADIO_TRUE : RADIO_FALSE); + } + if(g->type == Group::Type::ROTATE || g->type == Group::Type::TRANSLATE) { if(g->subtype == Group::Subtype::ONE_SIDED) { bool skip = g->skipFirst; @@ -398,6 +475,26 @@ void TextWindow::ShowGroupInfo() { } Printf(false, ""); + if(g->type == Group::Type::HELIX) { + Printf(false, "%Ft pitch - length per turn%E"); + + if (fabs(g->valB) != 0.0) { + Printf(false, " %Ba %# %Fl%Ll%f%D[change]%E", + g->valB / SS.MmPerUnit(), + &TextWindow::ScreenChangeHelixPitch, g->h.v); + } else { + Printf(false, " %Ba %# %E", + SK.GetParam(g->h.param(7))->val * PI / + ( (SK.GetParam(g->h.param(3))->val) * SS.MmPerUnit() ), + &TextWindow::ScreenChangeHelixPitch, g->h.v); + } + Printf(false, " %Fd%f%LP%s fixed", + &TextWindow::ScreenChangePitchOption, + g->valB != 0 ? CHECK_TRUE : CHECK_FALSE); + + Printf(false, ""); // blank line + } + if(g->type == Group::Type::EXTRUDE || g->type == Group::Type::LATHE || g->type == Group::Type::REVOLVE || g->type == Group::Type::LINKED || g->type == Group::Type::HELIX) { @@ -451,7 +548,7 @@ void TextWindow::ShowGroupInfo() { &TextWindow::ScreenChangeGroupOption, g->visible ? CHECK_TRUE : CHECK_FALSE); - if(!g->IsForcedToMeshBySource()) { + if(!g->IsForcedToMeshBySource() && !g->IsTriangleMeshAssembly()) { Printf(false, " %f%Lf%Fd%s force NURBS surfaces to triangle mesh", &TextWindow::ScreenChangeGroupOption, g->forceToMesh ? CHECK_TRUE : CHECK_FALSE); @@ -467,6 +564,10 @@ void TextWindow::ShowGroupInfo() { &TextWindow::ScreenChangeGroupOption, g->allowRedundant ? CHECK_TRUE : CHECK_FALSE); + Printf(false, " %f%LD%Fd%s suppress dof calculation (improves solver performance)", + &TextWindow::ScreenChangeGroupOption, + g->suppressDofCalculation ? CHECK_TRUE : CHECK_FALSE); + Printf(false, " %f%Ld%Fd%s treat all dimensions as reference", &TextWindow::ScreenChangeGroupOption, g->allDimsReference ? CHECK_TRUE : CHECK_FALSE); @@ -579,7 +680,7 @@ void TextWindow::ShowGroupSolveInfo() { } if(g->solved.timeout) { - Printf(true, "%FxSome items in list have been ommitted%Fd"); + Printf(true, "%FxSome items in list have been omitted%Fd"); Printf(false, "%Fxbecause the operation timed out.%Fd"); } @@ -603,7 +704,7 @@ void TextWindow::ScreenStepDimFinish(int link, uint32_t v) { SS.TW.edit.meaning = Edit::STEP_DIM_FINISH; std::string edit_value; if(SS.TW.stepDim.isDistance) { - edit_value = SS.MmToString(SS.TW.stepDim.finish); + edit_value = SS.MmToString(SS.TW.stepDim.finish, true); } else { edit_value = ssprintf("%.3f", SS.TW.stepDim.finish); } @@ -690,7 +791,7 @@ void TextWindow::ScreenChangeTangentArc(int link, uint32_t v) { switch(link) { case 'r': { SS.TW.edit.meaning = Edit::TANGENT_ARC_RADIUS; - SS.TW.ShowEditControl(3, SS.MmToString(SS.tangentArcRadius)); + SS.TW.ShowEditControl(3, SS.MmToString(SS.tangentArcRadius, true)); break; } @@ -789,6 +890,15 @@ void TextWindow::EditControlDone(std::string s) { } break; + case Edit::HELIX_PITCH: // stored in valB + if(Expr *e = Expr::From(s, /*popUpError=*/true)) { + double ev = e->Eval(); + Group *g = SK.GetGroup(edit.group); + g->valB = ev * SS.MmPerUnit(); + SS.MarkGroupDirty(g->h); + } + break; + case Edit::GROUP_COLOR: { Vector rgb; if(sscanf(s.c_str(), "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) { @@ -871,3 +981,4 @@ void TextWindow::EditControlDone(std::string s) { } } +} // namespace SolveSpace diff --git a/src/textwin.cpp b/src/textwin.cpp index 3e3392261..537c2d13e 100644 --- a/src/textwin.cpp +++ b/src/textwin.cpp @@ -3,6 +3,8 @@ // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- +#include +#include #include "solvespace.h" namespace SolveSpace { @@ -57,7 +59,7 @@ class ShowHideButton : public Button { /*outlineColor=*/{}); } if(!*(variable)) { - int s = 0, f = 24; + int s = 0, f = 23; RgbaColor color = { 255, 0, 0, 150 }; uiCanvas->DrawLine(x+s, y-s, x+f, y-f, color, 2); uiCanvas->DrawLine(x+s, y-f, x+f, y-s, color, 2); @@ -83,69 +85,59 @@ class FacesButton : public ShowHideButton { } }; -class OccludedLinesButton : public Button { +#include +class TriStateButton : public Button { public: - std::shared_ptr visibleIcon; - std::shared_ptr stippledIcon; - std::shared_ptr invisibleIcon; + static const size_t tri = 3; - std::string Tooltip() override { - switch(SS.GW.drawOccludedAs) { - case GraphicsWindow::DrawOccludedAs::INVISIBLE: - return "Stipple occluded lines"; - - case GraphicsWindow::DrawOccludedAs::STIPPLED: - return "Draw occluded lines"; + TriStateButton(unsigned *variable, const std::array &states, + const std::array &tooltips, + const std::array &iconNames) + : variable(variable), states(states), tooltips(tooltips), iconNames(iconNames) { + } - case GraphicsWindow::DrawOccludedAs::VISIBLE: - return "Don't draw occluded lines"; + unsigned *const variable; + const std::array states; + const std::array tooltips; + const std::array iconNames; + std::shared_ptr icons[tri]; - default: ssassert(false, "Unexpected mode"); - } + std::string Tooltip() override { + for(size_t k = 0; k < tri; ++k) + if(*variable == states[k]) + return tooltips[k]; + ssassert(false, "Unexpected mode"); } void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) override { - if(visibleIcon == NULL) { - visibleIcon = LoadPng("icons/text-window/occluded-visible.png"); - } - if(stippledIcon == NULL) { - stippledIcon = LoadPng("icons/text-window/occluded-stippled.png"); - } - if(invisibleIcon == NULL) { - invisibleIcon = LoadPng("icons/text-window/occluded-invisible.png"); - } + for(size_t k = 0; k < tri; ++k) + if(icons[k] == nullptr) + icons[k] = LoadPng("icons/text-window/" + iconNames[k] + ".png"); std::shared_ptr icon; - switch(SS.GW.drawOccludedAs) { - case GraphicsWindow::DrawOccludedAs::INVISIBLE: icon = invisibleIcon; break; - case GraphicsWindow::DrawOccludedAs::STIPPLED: icon = stippledIcon; break; - case GraphicsWindow::DrawOccludedAs::VISIBLE: icon = visibleIcon; break; - } + for(size_t k = 0; k < tri; ++k) + if(*variable == states[k]) { + icon = icons[k]; + break; + } uiCanvas->DrawPixmap(icon, x, y - 24); if(asHovered) { uiCanvas->DrawRect(x - 2, x + 26, y + 2, y - 26, - /*fillColor=*/{ 255, 255, 0, 75 }, + /*fillColor=*/{255, 255, 0, 75}, /*outlineColor=*/{}); } } + int AdvanceWidth() override { return 32; } void Click() override { - switch(SS.GW.drawOccludedAs) { - case GraphicsWindow::DrawOccludedAs::INVISIBLE: - SS.GW.drawOccludedAs = GraphicsWindow::DrawOccludedAs::STIPPLED; - break; - - case GraphicsWindow::DrawOccludedAs::STIPPLED: - SS.GW.drawOccludedAs = GraphicsWindow::DrawOccludedAs::VISIBLE; + for(size_t k = 0; k < tri; ++k) + if(*variable == states[k]) { + *variable = states[(k + 1) % tri]; break; - - case GraphicsWindow::DrawOccludedAs::VISIBLE: - SS.GW.drawOccludedAs = GraphicsWindow::DrawOccludedAs::INVISIBLE; - break; - } + } SS.GenerateAll(); SS.GW.Invalidate(); @@ -163,8 +155,13 @@ static ShowHideButton pointsButton = { &(SS.GW.showPoints), "point", "points" }; static ShowHideButton constructionButton = { &(SS.GW.showConstruction), "construction", "construction entities" }; -static ShowHideButton constraintsButton = - { &(SS.GW.showConstraints), "constraint", "constraints and dimensions" }; +static TriStateButton constraintsButton = { + (unsigned *)(&(SS.GW.showConstraints)), + {(unsigned)GraphicsWindow::ShowConstraintMode::SCM_SHOW_ALL, + (unsigned)GraphicsWindow::ShowConstraintMode::SCM_SHOW_DIM, + (unsigned)GraphicsWindow::ShowConstraintMode::SCM_NOSHOW}, + {"Show only dimensions", "Hide constraints and dimensions", "Show constraints and dimensions"}, + {"constraint", "constraint-dimo", "constraint-wo"}}; static FacesButton facesButton; static ShowHideButton shadedButton = { &(SS.GW.showShaded), "shaded", "shaded view of solid model" }; @@ -174,7 +171,13 @@ static ShowHideButton outlinesButton = { &(SS.GW.showOutlines), "outlines", "outline of solid model" }; static ShowHideButton meshButton = { &(SS.GW.showMesh), "mesh", "triangle mesh of solid model" }; -static OccludedLinesButton occludedLinesButton; +static TriStateButton occludedLinesButton = { + (unsigned *)(&(SS.GW.drawOccludedAs)), + {(unsigned)GraphicsWindow::DrawOccludedAs::INVISIBLE, + (unsigned)GraphicsWindow::DrawOccludedAs::STIPPLED, + (unsigned)GraphicsWindow::DrawOccludedAs::VISIBLE}, + {"Stipple occluded lines", "Draw occluded lines", "Don't draw occluded lines"}, + {"occluded-invisible", "occluded-stippled", "occluded-visible"}}; static Button *buttons[] = { &workplanesButton, @@ -203,7 +206,7 @@ const TextWindow::Color TextWindow::fgColors[] = { { 'r', RGBi( 0, 0, 0) }, // Reverse : black { 'x', RGBi(255, 20, 20) }, // Error : red { 'i', RGBi( 0, 255, 255) }, // Info : cyan - { 'g', RGBi(160, 160, 160) }, + { 'g', RGBi(128, 128, 128) }, // Disabled : gray { 'b', RGBi(200, 200, 200) }, { 0, RGBi( 0, 0, 0) } }; @@ -235,6 +238,7 @@ void TextWindow::Init() { using namespace std::placeholders; window->onClose = []() { + SS.TW.HideEditControl(); SS.GW.showTextWindow = false; SS.GW.EnsureValidActives(); }; @@ -281,16 +285,17 @@ void TextWindow::Init() { void TextWindow::ClearSuper() { // Ugly hack, but not so ugly as the next line Platform::WindowRef oldWindow = std::move(window); - std::shared_ptr oldCanvas = canvas; + std::shared_ptr oldCanvas = std::move(canvas); // Cannot use *this = {} here because TextWindow instances // are 2.4MB long; this causes stack overflows in prologue // when built with MSVC, even with optimizations. - memset(this, 0, sizeof(*this)); + this->~TextWindow(); + new(this) TextWindow(); // Return old canvas window = std::move(oldWindow); - canvas = oldCanvas; + canvas = std::move(oldCanvas); HideEditControl(); @@ -348,8 +353,8 @@ void TextWindow::ClearScreen() { rows = 0; } -// This message was addded when someone had too many fonts for the text window -// Scrolling seemed to be broken, but was actaully at the MAX_ROWS. +// This message was added when someone had too many fonts for the text window +// Scrolling seemed to be broken, but was actually at the MAX_ROWS. static const char* endString = " **** End of Text Screen ****"; void TextWindow::Printf(bool halfLine, const char *fmt, ...) { @@ -624,7 +629,7 @@ void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow hoveredButton = NULL; } - double hoveredX, hoveredY; + double hoveredX = 0, hoveredY = 0; for(Button *button : buttons) { if(how == PAINT) { button->Draw(uiCanvas, x, y, (button == hoveredButton)); diff --git a/src/toolbar.cpp b/src/toolbar.cpp index bf6b59c59..372b9098c 100644 --- a/src/toolbar.cpp +++ b/src/toolbar.cpp @@ -7,6 +7,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + struct ToolIcon { std::string name; Command command; @@ -153,11 +155,18 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, UiCanvas *canvas, double width, height; window->GetContentSize(&width, &height); - int x = 17, y = (int)(height - 52); + int x = 17, y = (int)(height - 21); // 20 is the menu bar height - // When changing these values, also change the asReference drawing code in drawentity.cpp. + // When changing these values, also change the asReference drawing code in drawentity.cpp + // as well as the "window->SetMinContentSize(720, 636);" in graphicswin.cpp int fudge = 8; - int h = 32*18 + 3*16 + fudge; + int h = 32*18 + 3*16 + fudge; // Toolbar height = 18 icons * 32 pixels + 3 dividers * 16 pixels + fudge + + if(h < y) { + // If there is enough vertical space leave up to 32 pixels between the menu bar and the toolbar. + y -= ((y - h) < 32) ? y - h : 32; + } + int aleft = 0, aright = 66, atop = y+16+fudge/2, abot = y+16-h; bool withinToolbar = @@ -239,3 +248,5 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, UiCanvas *canvas, return withinToolbar; } + +} // namespace SolveSpace diff --git a/src/ttf.cpp b/src/ttf.cpp index ddb2aa175..2aa928b72 100644 --- a/src/ttf.cpp +++ b/src/ttf.cpp @@ -11,7 +11,7 @@ #include FT_ADVANCES_H /* Yecch. Irritatingly, you need to do this nonsense to get the error string table, - since nobody thought to put this exact function into FreeType itsself. */ + since nobody thought to put this exact function into FreeType itself. */ #undef __FTERRORS_H__ #define FT_ERRORDEF(e, v, s) { (e), (s) }, #define FT_ERROR_START_LIST @@ -41,6 +41,8 @@ extern "C" const char *ft_error_string(int err) { #include "solvespace.h" +namespace SolveSpace { + //----------------------------------------------------------------------------- // Get the list of available font filenames, and load the name for each of // them. Only that, though, not the glyphs too. @@ -81,9 +83,6 @@ void TtfFontList::LoadAll() { [](const TtfFont &a, const TtfFont &b) { return a.name == b.name; }); l.RemoveLast(&l[l.n] - it); - //! @todo identify fonts by their name and not filename, which may change - //! between OSes. - loaded = true; } @@ -108,11 +107,11 @@ TtfFont *TtfFontList::LoadFont(const std::string &font) } void TtfFontList::PlotString(const std::string &font, const std::string &str, - SBezierList *sbl, Vector origin, Vector u, Vector v) + SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v) { TtfFont *tf = LoadFont(font); if(!str.empty() && tf != NULL) { - tf->PlotString(str, sbl, origin, u, v); + tf->PlotString(str, sbl, kerning, origin, u, v); } else { // No text or no font; so draw a big X for an error marker. SBezier sb; @@ -123,11 +122,11 @@ void TtfFontList::PlotString(const std::string &font, const std::string &str, } } -double TtfFontList::AspectRatio(const std::string &font, const std::string &str) +double TtfFontList::AspectRatio(const std::string &font, const std::string &str, bool kerning) { TtfFont *tf = LoadFont(font); if(tf != NULL) { - return tf->AspectRatio(str); + return tf->AspectRatio(str, kerning); } return 0.0; @@ -331,7 +330,7 @@ static int CubicTo(const FT_Vector *c1, const FT_Vector *c2, const FT_Vector *p, } void TtfFont::PlotString(const std::string &str, - SBezierList *sbl, Vector origin, Vector u, Vector v) + SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v) { ssassert(fontFace != NULL, "Expected font face to be loaded"); @@ -344,6 +343,7 @@ void TtfFont::PlotString(const std::string &str, outlineFuncs.delta = 0; FT_Pos dx = 0; + uint32_t prevGid = 0; for(char32_t cid : ReadUTF8(str)) { uint32_t gid = FT_Get_Char_Index(fontFace, cid); if (gid == 0) { @@ -382,6 +382,13 @@ void TtfFont::PlotString(const std::string &str, */ FT_BBox cbox; FT_Outline_Get_CBox(&fontFace->glyph->outline, &cbox); + + // Apply Kerning, if any: + FT_Vector kernVector; + if(kerning && FT_Get_Kerning(fontFace, prevGid, gid, FT_KERNING_DEFAULT, &kernVector) == 0) { + dx += kernVector.x; + } + FT_Pos bx = dx - cbox.xMin; // Yes, this is what FreeType calls left-side bearing. // Then interchangeably uses that with "left-side bearing". Sigh. @@ -402,14 +409,16 @@ void TtfFont::PlotString(const std::string &str, // And we're done, so advance our position by the requested advance // width, plus the user-requested extra advance. dx += fontFace->glyph->advance.x; + prevGid = gid; } } -double TtfFont::AspectRatio(const std::string &str) { +double TtfFont::AspectRatio(const std::string &str, bool kerning) { ssassert(fontFace != NULL, "Expected font face to be loaded"); // We always request a unit size character, so the aspect ratio is the same as advance length. double dx = 0; + uint32_t prevGid = 0; for(char32_t chr : ReadUTF8(str)) { uint32_t gid = FT_Get_Char_Index(fontFace, chr); if (gid == 0) { @@ -424,8 +433,17 @@ double TtfFont::AspectRatio(const std::string &str) { break; } + // Apply Kerning, if any: + FT_Vector kernVector; + if(kerning && FT_Get_Kerning(fontFace, prevGid, gid, FT_KERNING_DEFAULT, &kernVector) == 0) { + dx += (double)kernVector.x / capHeight; + } + dx += (double)fontFace->glyph->advance.x / capHeight; + prevGid = gid; } return dx; } + +} // namespace SolveSpace diff --git a/src/ttf.h b/src/ttf.h index 565e6a9ca..560158d86 100644 --- a/src/ttf.h +++ b/src/ttf.h @@ -9,6 +9,22 @@ #ifndef SOLVESPACE_TTF_H #define SOLVESPACE_TTF_H +#include + +#include "dsc.h" +#include "platform/platform.h" + +// We declare these in advance instead of simply using FT_Library +// (defined as typedef FT_LibraryRec_* FT_Library) because including +// freetype.h invokes indescribable horrors and we would like to avoid +// doing that every time we include solvespace.h. +struct FT_LibraryRec_; +struct FT_FaceRec_; + +namespace SolveSpace { + +class SBezierList; + class TtfFont { public: Platform::Path fontFile; // or resource path/name as res:// @@ -24,8 +40,8 @@ class TtfFont { bool LoadFromResource(FT_LibraryRec_ *fontLibrary, bool keepOpen = false); void PlotString(const std::string &str, - SBezierList *sbl, Vector origin, Vector u, Vector v); - double AspectRatio(const std::string &str); + SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v); + double AspectRatio(const std::string &str, bool kerning); bool ExtractTTFData(bool keepOpen); }; @@ -43,8 +59,10 @@ class TtfFontList { TtfFont *LoadFont(const std::string &font); void PlotString(const std::string &font, const std::string &str, - SBezierList *sbl, Vector origin, Vector u, Vector v); - double AspectRatio(const std::string &font, const std::string &str); + SBezierList *sbl, bool kerning, Vector origin, Vector u, Vector v); + double AspectRatio(const std::string &font, const std::string &str, bool kerning); }; +} // namespace SolveSpace + #endif diff --git a/src/ui.h b/src/ui.h index 026d57de7..3bb789a12 100644 --- a/src/ui.h +++ b/src/ui.h @@ -8,6 +8,26 @@ #ifndef SOLVESPACE_UI_H #define SOLVESPACE_UI_H +#include +#include +#include +#include +#include + +#include "dsc.h" +#include "platform/gui.h" +#include "sketch.h" + +namespace SolveSpace { + +class Camera; +class Lighting; +class Canvas; +class UiCanvas; +class BatchCanvas; +class ViewportCanvas; +class Pixmap; + class Locale { public: std::string language; @@ -82,6 +102,7 @@ enum class Command : uint32_t { SHOW_GRID, DIM_SOLID_MODEL, PERSPECTIVE_PROJ, + EXPLODE_SKETCH, ONTO_WORKPLANE, NEAREST_ORTHO, NEAREST_ISO, @@ -89,6 +110,7 @@ enum class Command : uint32_t { SHOW_TOOLBAR, SHOW_TEXT_WND, UNITS_INCHES, + UNITS_FEET_INCHES, UNITS_MM, UNITS_METERS, FULL_SCREEN, @@ -170,6 +192,7 @@ enum class Command : uint32_t { // Help LOCALE, WEBSITE, + GITHUB, ABOUT, }; @@ -312,12 +335,15 @@ class TextWindow { EXPORT_OFFSET = 110, CANVAS_SIZE = 111, G_CODE_DEPTH = 112, - G_CODE_PASSES = 113, - G_CODE_FEED = 114, - G_CODE_PLUNGE_FEED = 115, - AUTOSAVE_INTERVAL = 116, - LIGHT_AMBIENT = 117, - FIND_CONSTRAINT_TIMEOUT = 118, + G_CODE_SAFE_HEIGHT = 113, + G_CODE_PASSES = 114, + G_CODE_FEED = 115, + G_CODE_PLUNGE_FEED = 116, + AUTOSAVE_INTERVAL = 117, + LIGHT_AMBIENT = 118, + FIND_CONSTRAINT_TIMEOUT = 119, + EXPLODE_DISTANCE = 120, + ANIMATION_SPEED = 121, // For TTF text TTF_TEXT = 300, // For the step dimension screen @@ -342,7 +368,9 @@ class TextWindow { VIEW_PROJ_RIGHT = 702, VIEW_PROJ_UP = 703, // For tangent arc - TANGENT_ARC_RADIUS = 800 + TANGENT_ARC_RADIUS = 800, + // For helix pitch + HELIX_PITCH = 802 }; struct { bool showAgain; @@ -395,6 +423,7 @@ class TextWindow { // All of these are callbacks from the GUI code; first from when // we're describing an entity static void ScreenEditTtfText(int link, uint32_t v); + static void ScreenToggleTtfKerning(int link, uint32_t v); static void ScreenSetTtfFont(int link, uint32_t v); static void ScreenUnselectAll(int link, uint32_t v); @@ -435,11 +464,14 @@ class TextWindow { static void ScreenShowEditView(int link, uint32_t v); static void ScreenGoToWebsite(int link, uint32_t v); + static void ScreenChangeArcDimDefault(int link, uint32_t v); + static void ScreenChangeShowFullFilePath(int link, uint32_t v); static void ScreenChangeFixExportColors(int link, uint32_t v); static void ScreenChangeExportBackgroundColor(int link, uint32_t v); static void ScreenChangeBackFaces(int link, uint32_t v); static void ScreenChangeShowContourAreas(int link, uint32_t v); static void ScreenChangeCheckClosedContour(int link, uint32_t v); + static void ScreenChangeCameraNav(int link, uint32_t v); static void ScreenChangeTurntableNav(int link, uint32_t v); static void ScreenChangeImmediatelyEditDimension(int link, uint32_t v); static void ScreenChangeAutomaticLineConstraints(int link, uint32_t v); @@ -473,6 +505,8 @@ class TextWindow { static void ScreenChangeExprA(int link, uint32_t v); static void ScreenChangeGroupName(int link, uint32_t v); static void ScreenChangeGroupScale(int link, uint32_t v); + static void ScreenChangeHelixPitch(int link, uint32_t v); + static void ScreenChangePitchOption(int link, uint32_t v); static void ScreenChangeLightDirection(int link, uint32_t v); static void ScreenChangeLightIntensity(int link, uint32_t v); static void ScreenChangeLightAmbient(int link, uint32_t v); @@ -483,6 +517,7 @@ class TextWindow { static void ScreenChangeExportMaxSegments(int link, uint32_t v); static void ScreenChangeCameraTangent(int link, uint32_t v); static void ScreenChangeGridSpacing(int link, uint32_t v); + static void ScreenChangeExplodeDistance(int link, uint32_t v); static void ScreenChangeDigitsAfterDecimal(int link, uint32_t v); static void ScreenChangeDigitsAfterDecimalDegree(int link, uint32_t v); static void ScreenChangeUseSIPrefixes(int link, uint32_t v); @@ -491,6 +526,7 @@ class TextWindow { static void ScreenChangeGCodeParameter(int link, uint32_t v); static void ScreenChangeAutosaveInterval(int link, uint32_t v); static void ScreenChangeFindConstraintTimeout(int link, uint32_t v); + static void ScreenChangeAnimationSpeed(int link, uint32_t v); static void ScreenChangeStyleName(int link, uint32_t v); static void ScreenChangeStyleMetric(int link, uint32_t v); static void ScreenChangeStyleTextAngle(int link, uint32_t v); @@ -535,6 +571,7 @@ class GraphicsWindow { Platform::MenuItemRef showGridMenuItem; Platform::MenuItemRef dimSolidModelMenuItem; Platform::MenuItemRef perspectiveProjMenuItem; + Platform::MenuItemRef explodeMenuItem; Platform::MenuItemRef showToolbarMenuItem; Platform::MenuItemRef showTextWndMenuItem; Platform::MenuItemRef fullScreenMenuItem; @@ -542,6 +579,7 @@ class GraphicsWindow { Platform::MenuItemRef unitsMmMenuItem; Platform::MenuItemRef unitsMetersMenuItem; Platform::MenuItemRef unitsInchesMenuItem; + Platform::MenuItemRef unitsFeetInchesMenuItem; Platform::MenuItemRef inWorkplaneMenuItem; Platform::MenuItemRef in3dMenuItem; @@ -610,6 +648,7 @@ class GraphicsWindow { void HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin, double *wmin, bool usePerspective, const Camera &camera); + void ZoomToMouse(double delta); void LoopOverPoints(const std::vector &entities, const std::vector &constraints, const std::vector &faces, @@ -737,6 +776,7 @@ class GraphicsWindow { Selection hover; bool hoverWasSelectedOnMousedown; List selection; + const unsigned MAX_SELECTABLE_FACES = 3u; Selection ChooseFromHoverToSelect(); Selection ChooseFromHoverToDrag(); @@ -787,21 +827,27 @@ class GraphicsWindow { bool ToolbarMouseDown(int x, int y); Command toolbarHovered; + // This sets what gets displayed. bool showWorkplanes; bool showNormals; bool showPoints; bool showConstruction; - bool showConstraints; + + enum class ShowConstraintMode : unsigned { SCM_NOSHOW, SCM_SHOW_ALL, SCM_SHOW_DIM }; + ShowConstraintMode showConstraints; + bool showTextWindow; bool showShaded; bool showEdges; bool showOutlines; bool showFaces; + bool showFacesDrawing; + bool showFacesNonDrawing; bool showMesh; void ToggleBool(bool *v); - enum class DrawOccludedAs { INVISIBLE, STIPPLED, VISIBLE }; + enum class DrawOccludedAs : unsigned { INVISIBLE, STIPPLED, VISIBLE }; DrawOccludedAs drawOccludedAs; bool showSnapGrid; @@ -828,7 +874,7 @@ class GraphicsWindow { void MouseLeftDoubleClick(double x, double y); void MouseMiddleOrRightDown(double x, double y); void MouseRightUp(double x, double y); - void MouseScroll(double x, double y, double delta); + void MouseScroll(double delta); void MouseLeave(); bool KeyboardEvent(Platform::KeyboardEvent event); void EditControlDone(const std::string &s); @@ -838,5 +884,6 @@ class GraphicsWindow { void SixDofEvent(Platform::SixDofEvent event); }; +} // namespace SolveSpace #endif diff --git a/src/undoredo.cpp b/src/undoredo.cpp index 8ea8a0679..17d1a376b 100644 --- a/src/undoredo.cpp +++ b/src/undoredo.cpp @@ -7,6 +7,8 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + void SolveSpaceUI::UndoRemember() { unsaved = true; PushFromCurrentOnto(&undo); @@ -152,3 +154,4 @@ void SolveSpaceUI::UndoClearState(UndoState *ut) { *ut = {}; } +} // namespace SolveSpace diff --git a/src/util.cpp b/src/util.cpp index f14417cc9..d1ea15a30 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -4,18 +4,25 @@ // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- +#include +#include +#include +#include + #include "solvespace.h" -void SolveSpace::AssertFailure(const char *file, unsigned line, const char *function, +namespace SolveSpace { + +void AssertFailure(const char *file, unsigned line, const char *function, const char *condition, const char *message) { std::string formattedMsg; formattedMsg += ssprintf("File %s, line %u, function %s:\n", file, line, function); formattedMsg += ssprintf("Assertion failed: %s.\n", condition); formattedMsg += ssprintf("Message: %s.\n", message); - SolveSpace::Platform::FatalError(formattedMsg); + Platform::FatalError(formattedMsg); } -std::string SolveSpace::ssprintf(const char *fmt, ...) +std::string ssprintf(const char *fmt, ...) { va_list va; @@ -57,13 +64,13 @@ char32_t utf8_iterator::operator*() return result; } -int64_t SolveSpace::GetMilliseconds() +int64_t GetMilliseconds() { auto timestamp = std::chrono::steady_clock::now().time_since_epoch(); return std::chrono::duration_cast(timestamp).count(); } -void SolveSpace::MakeMatrix(double *mat, +void MakeMatrix(double *mat, double a11, double a12, double a13, double a14, double a21, double a22, double a23, double a24, double a31, double a32, double a33, double a34, @@ -87,7 +94,7 @@ void SolveSpace::MakeMatrix(double *mat, mat[15] = a44; } -void SolveSpace::MultMatrix(double *mata, double *matb, double *matr) { +void MultMatrix(double *mata, double *matb, double *matr) { for(int i = 0; i < 4; i++) { for(int j = 0; j < 4; j++) { double s = 0.0; @@ -178,21 +185,21 @@ static void MessageBox(const char *fmt, va_list va, bool error, dialog->ShowModal(); #endif } -void SolveSpace::Error(const char *fmt, ...) +void Error(const char *fmt, ...) { va_list f; va_start(f, fmt); MessageBox(fmt, f, /*error=*/true); va_end(f); } -void SolveSpace::Message(const char *fmt, ...) +void Message(const char *fmt, ...) { va_list f; va_start(f, fmt); MessageBox(fmt, f, /*error=*/false); va_end(f); } -void SolveSpace::MessageAndRun(std::function onDismiss, const char *fmt, ...) +void MessageAndRun(std::function onDismiss, const char *fmt, ...) { va_list f; va_start(f, fmt); @@ -1025,7 +1032,7 @@ bool BBox::Contains(const Point2d &p, double r) const { p.y <= (maxp.y + r); } -const std::vector& SolveSpace::StipplePatternDashes(StipplePattern pattern) { +const std::vector& StipplePatternDashes(StipplePattern pattern) { static bool initialized; static std::vector dashes[(size_t)StipplePattern::LAST + 1]; if(!initialized) { @@ -1054,7 +1061,7 @@ const std::vector& SolveSpace::StipplePatternDashes(StipplePattern patte return dashes[(size_t)pattern]; } -double SolveSpace::StipplePatternLength(StipplePattern pattern) { +double StipplePatternLength(StipplePattern pattern) { static bool initialized; static double lengths[(size_t)StipplePattern::LAST + 1]; if(!initialized) { @@ -1070,3 +1077,5 @@ double SolveSpace::StipplePatternLength(StipplePattern pattern) { return lengths[(size_t)pattern]; } + +} // namespace SolveSpace diff --git a/src/util.h b/src/util.h new file mode 100644 index 000000000..585b5decd --- /dev/null +++ b/src/util.h @@ -0,0 +1,105 @@ +#ifndef SOLVESPACE_UTIL_H +#define SOLVESPACE_UTIL_H + +#include +#include +#include +#include +#include +#include + +// The few floating-point equality comparisons in SolveSpace have been +// carefully considered, so we disable the -Wfloat-equal warning for them +#ifdef __clang__ +# define EXACT(expr) \ + (_Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wfloat-equal\"") \ + (expr) \ + _Pragma("clang diagnostic pop")) +#else +# define EXACT(expr) (expr) +#endif + +// Debugging functions +#if defined(__GNUC__) +#define ssassert(condition, message) \ + do { \ + if(__builtin_expect((condition), true) == false) { \ + SolveSpace::AssertFailure(__FILE__, __LINE__, __func__, #condition, message); \ + __builtin_unreachable(); \ + } \ + } while(0) +#else +#define ssassert(condition, message) \ + do { \ + if((condition) == false) { \ + SolveSpace::AssertFailure(__FILE__, __LINE__, __func__, #condition, message); \ + std::abort(); \ + } \ + } while(0) +#endif + +#define CO(v) (v).x, (v).y, (v).z + +#define dbp SolveSpace::Platform::DebugPrint + +namespace SolveSpace { + +namespace Platform { + +// Debug print function. +void DebugPrint(const char *fmt, ...); + +} // namespace Platform + +[[noreturn]] +void AssertFailure(const char *file, unsigned line, const char *function, + const char *condition, const char *message); + +#if defined(__GNUC__) +__attribute__((__format__ (__printf__, 1, 2))) +#endif +std::string ssprintf(const char *fmt, ...); + +inline bool IsReasonable(double x) { + return std::isnan(x) || x > 1e11 || x < -1e11; +} + +inline int WRAP(int v, int n) { + // Clamp it to the range [0, n) + while(v >= n) v -= n; + while(v < 0) v += n; + return v; +} +inline double WRAP_NOT_0(double v, double n) { + // Clamp it to the range (0, n] + while(v > n) v -= n; + while(v <= 0) v += n; + return v; +} +inline double WRAP_SYMMETRIC(double v, double n) { + // Clamp it to the range (-n/2, n/2] + while(v > n/2) v -= n; + while(v <= -n/2) v += n; + return v; +} + +template +inline constexpr size_t arraylen(const T (&)[N]) { + return N; +} + +void MakeMatrix(double *mat, double a11, double a12, double a13, double a14, + double a21, double a22, double a23, double a24, + double a31, double a32, double a33, double a34, + double a41, double a42, double a43, double a44); +void MultMatrix(double *mata, double *matb, double *matr); + +int64_t GetMilliseconds(); +void Message(const char *fmt, ...); +void MessageAndRun(std::function onDismiss, const char *fmt, ...); +void Error(const char *fmt, ...); + +} // namespace SolveSpace + +#endif // !SOLVESPACE_UTIL_H diff --git a/src/view.cpp b/src/view.cpp index 14d09ba35..62602531a 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -1,11 +1,13 @@ //----------------------------------------------------------------------------- -// The View menu, stuff to snap to certain special vews of the model, and to +// The View menu, stuff to snap to certain special views of the model, and to // display our current view of the model to the user. // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" +namespace SolveSpace { + void TextWindow::ShowEditView() { Printf(true, "%Ft3D VIEW PARAMETERS%E"); @@ -35,8 +37,29 @@ void TextWindow::ShowEditView() { Printf(false, "%Ba %Ftout%E (%3, %3, %3)", CO(n)); Printf(false, ""); - Printf(false, "The perspective may be changed in the"); - Printf(false, "configuration screen."); + Printf(false, "%Ft perspective factor (0 for parallel)%E"); + Printf(false, "%Ba %# %Fl%Ll%f%D[change]%E", + SS.cameraTangent*1000, + &ScreenChangeCameraTangent, 0); + + Printf(false, ""); + Printf(false, "%Ft light direction intensity"); + for(int i = 0; i < 2; i++) { + Printf(false, "%Bp #%d (%2,%2,%2)%Fl%D%f%Ll[c]%E " + "%2 %Fl%D%f%Ll[c]%E", + (i & 1) ? 'd' : 'a', i, + CO(SS.lightDir[i]), i, &ScreenChangeLightDirection, + SS.lightIntensity[i], i, &ScreenChangeLightIntensity); + } + Printf(false, "%Ba ambient lighting %2 %Fl%f%Ll[c]%E", + SS.ambientIntensity, &ScreenChangeLightAmbient); + + Printf(false, ""); + Printf(false, "%Ft explode distance%E"); + Printf(false, "%Ba %s %Fl%Ll%f%D[change]%E", + SS.MmToString(SS.explodeDistance).c_str(), + &ScreenChangeExplodeDistance, 0); + } void TextWindow::ScreenChangeViewScale(int link, uint32_t v) { @@ -51,9 +74,9 @@ void TextWindow::ScreenChangeViewToFullScale(int link, uint32_t v) { void TextWindow::ScreenChangeViewOrigin(int link, uint32_t v) { std::string edit_value = ssprintf("%s, %s, %s", - SS.MmToString(-SS.GW.offset.x).c_str(), - SS.MmToString(-SS.GW.offset.y).c_str(), - SS.MmToString(-SS.GW.offset.z).c_str()); + SS.MmToString(-SS.GW.offset.x, true).c_str(), + SS.MmToString(-SS.GW.offset.y, true).c_str(), + SS.MmToString(-SS.GW.offset.z, true).c_str()); SS.TW.edit.meaning = Edit::VIEW_ORIGIN; SS.TW.ShowEditControl(3, edit_value); @@ -66,6 +89,34 @@ void TextWindow::ScreenChangeViewProjection(int link, uint32_t v) { SS.TW.ShowEditControl(10, edit_value); } +void TextWindow::ScreenChangeLightDirection(int link, uint32_t v) { + SS.TW.ShowEditControl(8, ssprintf("%.2f, %.2f, %.2f", CO(SS.lightDir[v]))); + SS.TW.edit.meaning = Edit::LIGHT_DIRECTION; + SS.TW.edit.i = v; +} + +void TextWindow::ScreenChangeLightIntensity(int link, uint32_t v) { + SS.TW.ShowEditControl(31, ssprintf("%.2f", SS.lightIntensity[v])); + SS.TW.edit.meaning = Edit::LIGHT_INTENSITY; + SS.TW.edit.i = v; +} + +void TextWindow::ScreenChangeLightAmbient(int link, uint32_t v) { + SS.TW.ShowEditControl(31, ssprintf("%.2f", SS.ambientIntensity)); + SS.TW.edit.meaning = Edit::LIGHT_AMBIENT; + SS.TW.edit.i = 0; +} + +void TextWindow::ScreenChangeCameraTangent(int link, uint32_t v) { + SS.TW.ShowEditControl(3, ssprintf("%.3f", 1000*SS.cameraTangent)); + SS.TW.edit.meaning = Edit::CAMERA_TANGENT; +} + +void TextWindow::ScreenChangeExplodeDistance(int link, uint32_t v) { + SS.TW.ShowEditControl(3, SS.MmToString(SS.explodeDistance, true)); + SS.TW.edit.meaning = Edit::EXPLODE_DISTANCE; +} + bool TextWindow::EditControlDoneForView(const std::string &s) { switch(edit.meaning) { case Edit::VIEW_SCALE: { @@ -120,3 +171,4 @@ bool TextWindow::EditControlDoneForView(const std::string &s) { return true; } +} // namespace SolveSpace diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index db812bfab..eb6cf60b4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -76,6 +76,9 @@ target_link_libraries(solvespace-testsuite solvespace-headless ${COVERAGE_LIBRARY}) +target_include_directories(solvespace-testsuite + PRIVATE + ${EIGEN3_INCLUDE_DIRS}) add_dependencies(solvespace-testsuite resources) diff --git a/test/analysis/contour_area/test.cpp b/test/analysis/contour_area/test.cpp index 748f0eb05..eae410d6f 100644 --- a/test/analysis/contour_area/test.cpp +++ b/test/analysis/contour_area/test.cpp @@ -1,3 +1,5 @@ +#include "solvespace.h" + #include "harness.h" TEST_CASE(normal_roundtrip) { diff --git a/test/constraint/pt_on_circle/test.cpp b/test/constraint/pt_on_circle/test.cpp index ce38ba511..3b4c470c6 100644 --- a/test/constraint/pt_on_circle/test.cpp +++ b/test/constraint/pt_on_circle/test.cpp @@ -1,3 +1,5 @@ +#include "solvespace.h" + #include "harness.h" TEST_CASE(normal_roundtrip) { diff --git a/test/core/expr/test.cpp b/test/core/expr/test.cpp index 5e73a0293..bbd117696 100644 --- a/test/core/expr/test.cpp +++ b/test/core/expr/test.cpp @@ -1,3 +1,5 @@ +#include "expr.h" + #include "harness.h" #define CHECK_PARSE(var, expr) \ diff --git a/test/core/locale/test.cpp b/test/core/locale/test.cpp index 46ad9e124..2b0a92b1f 100644 --- a/test/core/locale/test.cpp +++ b/test/core/locale/test.cpp @@ -1,3 +1,5 @@ +#include "ui.h" + #include "harness.h" TEST_CASE(parseable) { diff --git a/test/core/path/test.cpp b/test/core/path/test.cpp index 86accf1f3..90ff12455 100644 --- a/test/core/path/test.cpp +++ b/test/core/path/test.cpp @@ -125,11 +125,13 @@ TEST_CASE(join) { CHECK_EQ_STR(path.Join(Path::From("bar")).raw, "foo" S "bar"); path = Path::From(""); - CHECK_TRUE(path.Join(Path::From("bar")).IsEmpty()); + CHECK_EQ_STR(path.Join(Path::From("bar")).raw, "." S "bar"); path = Path::From("foo"); - CHECK_TRUE(path.Join(Path::From("")).IsEmpty()); + CHECK_EQ_STR(path.Join(Path::From("")).raw, "foo" S); path = Path::From("foo"); CHECK_TRUE(path.Join(Path::From(R S "bar")).IsEmpty()); + path = Path::From(""); + CHECK_EQ_STR(path.Join(Path::From("")).raw, "." S); } TEST_CASE(expand) { diff --git a/test/debugtool.cpp b/test/debugtool.cpp index aa66c1fee..aea6b8475 100644 --- a/test/debugtool.cpp +++ b/test/debugtool.cpp @@ -3,7 +3,11 @@ // // Copyright 2017 whitequark //----------------------------------------------------------------------------- -#include "solvespace.h" + +#include "expr.h" +#include "platform/platform.h" + +using namespace SolveSpace; int main(int argc, char **argv) { std::vector args = Platform::InitCli(argc, argv); @@ -16,7 +20,7 @@ int main(int argc, char **argv) { } else { fprintf(stderr, "%g\n", e->Eval()); } - FreeAllTemporary(); + Platform::FreeAllTemporary(); } else { fprintf(stderr, "Usage: %s \n", args[0].c_str()); //-----------------------------------------------------------------------------> 80 col */ diff --git a/test/group/translate_asy/test.cpp b/test/group/translate_asy/test.cpp index 957dccefd..979081d94 100644 --- a/test/group/translate_asy/test.cpp +++ b/test/group/translate_asy/test.cpp @@ -1,3 +1,5 @@ +#include "solvespace.h" + #include "harness.h" TEST_CASE(normal_roundtrip) { diff --git a/test/harness.cpp b/test/harness.cpp index 2eee03738..a41db4393 100644 --- a/test/harness.cpp +++ b/test/harness.cpp @@ -3,9 +3,15 @@ // // Copyright 2016 whitequark //----------------------------------------------------------------------------- +#include +#include +#include +#include #include #include +#include "solvespace.h" + #include "harness.h" #if defined(WIN32) @@ -16,7 +22,7 @@ namespace SolveSpace { namespace Platform { - // These are defined in headless.cpp, and aren't exposed in solvespace.h. + // These are defined in guinone.cpp, and aren't exposed in solvespace.h. extern std::vector fontFiles; } } @@ -337,19 +343,29 @@ int Test::Case::Register(Test::Case testCase) { int main(int argc, char **argv) { std::vector args = Platform::InitCli(argc, argv); - std::regex filter(".*"); + std::string filterPattern = ".*"; + unsigned int seed = std::random_device{}(); if(args.size() == 1) { } else if(args.size() == 2) { - filter = args[1]; + filterPattern = args[1]; + } else if(args.size() == 3) { + filterPattern = args[1]; + seed = std::stoul(args[2]); } else { - fprintf(stderr, "Usage: %s [test filter regex]\n", args[0].c_str()); + fprintf(stderr, "Usage: %s [test filter regex] [shuffle seed]\n", args[0].c_str()); return 1; } + fprintf(stderr, "info: using test filter `%s' and seed %u\n", filterPattern.c_str(), seed); + Platform::fontFiles.push_back(HostRoot().Join("Gentium-R.ttf")); + std::mt19937 g(seed); + // Wreck order dependencies between tests! - std::random_shuffle(testCasesPtr->begin(), testCasesPtr->end()); + std::shuffle(testCasesPtr->begin(), testCasesPtr->end(), g); + + std::regex filter(filterPattern); auto testStartTime = std::chrono::steady_clock::now(); size_t ranTally = 0, skippedTally = 0, checkTally = 0, failTally = 0; diff --git a/test/harness.h b/test/harness.h index fa959000b..91b6d60bb 100644 --- a/test/harness.h +++ b/test/harness.h @@ -3,7 +3,10 @@ // // Copyright 2016 whitequark //----------------------------------------------------------------------------- -#include "solvespace.h" +#include +#include + +#include "platform/platform.h" // Hack... we should rename the ones in ui.h instead. #undef CHECK_TRUE diff --git a/test/request/circle/test.cpp b/test/request/circle/test.cpp index 82cd8781e..9a5d53659 100644 --- a/test/request/circle/test.cpp +++ b/test/request/circle/test.cpp @@ -1,3 +1,5 @@ +#include "solvespace.h" + #include "harness.h" TEST_CASE(normal_roundtrip) { diff --git a/test/request/ttf_text/kerning.png b/test/request/ttf_text/kerning.png new file mode 100644 index 000000000..4d6eab035 Binary files /dev/null and b/test/request/ttf_text/kerning.png differ diff --git a/test/request/ttf_text/kerning.slvs b/test/request/ttf_text/kerning.slvs new file mode 100644 index 000000000..c0f8a7fc4 --- /dev/null +++ b/test/request/ttf_text/kerning.slvs @@ -0,0 +1,331 @@ +SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040016 +Param.val=23.08769405528209262002 +AddParam + +Param.h.v.=00040017 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040019 +Param.val=23.08769405528209262002 +AddParam + +Param.h.v.=0004001a +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=600 +Request.extraPoints=1 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +Request.str=Text +Request.font=Gentium-R.ttf +Request.aspectRatio=2.80876940552820908437 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=15000 +Entity.construction=0 +Entity.str=Text +Entity.font=Gentium-R.ttf +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.point[3].v=00040004 +Entity.extraPoints=1 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=23.08769405528209262002 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040004 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=23.08769405528209262002 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/ttf_text/test.cpp b/test/request/ttf_text/test.cpp index 5e9db1b1e..2937f79c8 100644 --- a/test/request/ttf_text/test.cpp +++ b/test/request/ttf_text/test.cpp @@ -6,6 +6,12 @@ TEST_CASE(normal_roundtrip) { CHECK_SAVE("normal.slvs"); } +TEST_CASE(kerning_roundtrip) { + CHECK_LOAD("kerning.slvs"); + CHECK_RENDER("kerning.png"); + CHECK_SAVE("kerning.slvs"); +} + TEST_CASE(normal_migrate_from_v20) { CHECK_LOAD("normal_v20.slvs"); CHECK_SAVE("normal.slvs");