diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 24a800026..640e5b536 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,18 +1,17 @@ ### System information -SolveSpace version: (e.g. 3.0~3dd2fc00; go to Help → About...) - -Operating system: (e.g. Debian testing) +- **SolveSpace version:** +- **Operating system:** ### Expected behavior -What should have happened? + ### Actual behavior -What actually happened? + ### Additional information -For bugs, please attach a savefile that shows the problematic behavior. -You can attach `.slvs` files by archiving them into a `.zip` first. + diff --git a/.github/scripts/build-macos.sh b/.github/scripts/build-macos.sh new file mode 100755 index 000000000..bc9155652 --- /dev/null +++ b/.github/scripts/build-macos.sh @@ -0,0 +1,53 @@ +#!/bin/sh -xe + +ENABLE_SANITIZERS="OFF" +if [ "$1" = "release" ]; then + BUILD_TYPE="RelWithDebInfo" + ENABLE_LTO="ON" +else + BUILD_TYPE="Debug" + ENABLE_LTO="OFF" +fi + +# this is an option for our Github CI only, since it doesn't have a macos arm64 image yet +CMAKE_GENERATOR="Unix Makefiles" +CMAKE_PREFIX_PATH="" +if [ "$2" = "arm64" ]; then + OSX_ARCHITECTURE="arm64" + CMAKE_PREFIX_PATH=$(find /tmp/libomp-arm64/libomp -depth 1) + git apply cmake/libpng-macos-arm64.patch || echo "Could not apply patch, probably already patched..." + mkdir build-arm64 || true + cd build-arm64 +elif [ "$2" = "x86_64" ]; then + OSX_ARCHITECTURE="x86_64" + CMAKE_PREFIX_PATH=$(find /tmp/libomp-x86_64/libomp -depth 1) + mkdir build || true + cd build +else + mkdir build || true + cd build +fi + +if [ "$3" = "xcode" ]; then + CMAKE_GENERATOR="Xcode" +fi + +cmake \ + -G "${CMAKE_GENERATOR}" \ + -D CMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" \ + -D CMAKE_OSX_ARCHITECTURES="${OSX_ARCHITECTURE}" \ + -D CMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -D ENABLE_OPENMP="ON" \ + -D ENABLE_SANITIZERS="${ENABLE_SANITIZERS}" \ + -D ENABLE_LTO="${ENABLE_LTO}" \ + -D CMAKE_POLICY_VERSION_MINIMUM=3.5 \ + .. + +if [ "$3" = "xcode" ]; then + open solvespace.xcodeproj +else + cmake --build . --config "${BUILD_TYPE}" -j$(sysctl -n hw.logicalcpu) + if [ $(uname -m) = "$2" ]; then + make -j$(sysctl -n hw.logicalcpu) test_solvespace + fi +fi diff --git a/.github/scripts/build-snap.sh b/.github/scripts/build-snap.sh new file mode 100755 index 000000000..665e56145 --- /dev/null +++ b/.github/scripts/build-snap.sh @@ -0,0 +1,3 @@ +#!/bin/sh -xe + +./pkg/snap/build.sh --use-lxd diff --git a/.github/scripts/build-ubuntu.sh b/.github/scripts/build-ubuntu.sh new file mode 100755 index 000000000..d7e903ae1 --- /dev/null +++ b/.github/scripts/build-ubuntu.sh @@ -0,0 +1,12 @@ +#!/bin/sh -xe + +mkdir build +cd build +cmake \ + -DCMAKE_BUILD_TYPE="Debug" \ + -DENABLE_OPENMP="ON" \ + -DENABLE_SANITIZERS="ON" \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + .. +make -j$(nproc) VERBOSE=1 +make test_solvespace diff --git a/.github/scripts/build-wasmlib.sh b/.github/scripts/build-wasmlib.sh new file mode 100755 index 000000000..e8eaee489 --- /dev/null +++ b/.github/scripts/build-wasmlib.sh @@ -0,0 +1,22 @@ +#!/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 build-wasmlib || true +cd build-wasmlib +emcmake cmake .. \ + -DCMAKE_RELEASE_TYPE=Debug \ + -DENABLE_GUI="OFF" \ + -DENABLE_CLI="OFF" \ + -DENABLE_TESTS="OFF" \ + -DENABLE_COVERAGE="OFF" \ + -DENABLE_OPENMP="OFF" \ + -DFORCE_VENDORED_Eigen3="ON" \ + -DENABLE_LTO="ON" \ + -DENABLE_EMSCRIPTEN_LIB="ON" +cmake --build . -j$(nproc) diff --git a/.github/scripts/build-windows.sh b/.github/scripts/build-windows.sh new file mode 100755 index 000000000..6b322e8a6 --- /dev/null +++ b/.github/scripts/build-windows.sh @@ -0,0 +1,38 @@ +#!/bin/sh -xe + +mkdir build +cd build + +if [ "$1" = "release" ]; then + if [ "$2" = "openmp" ]; then + ENABLE_OPENMP="ON" + else + ENABLE_OPENMP="OFF" + fi + BUILD_TYPE=RelWithDebInfo + cmake \ + -G "Visual Studio 16 2019" \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -DENABLE_OPENMP="${ENABLE_OPENMP}" \ + -DENABLE_LTO=ON \ + -DCMAKE_GENERATOR_PLATFORM="Win32" \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + .. +else + BUILD_TYPE=Debug + cmake \ + -G "Visual Studio 16 2019" \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -DENABLE_OPENMP="ON" \ + -DCMAKE_GENERATOR_PLATFORM="Win32" \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + .. +fi + +cmake --build . --config "${BUILD_TYPE}" -- -maxcpucount + +bin/$BUILD_TYPE/solvespace-testsuite.exe + +if [ "$2" = "openmp" ]; then + mv bin/$BUILD_TYPE/solvespace.exe bin/$BUILD_TYPE/solvespace-openmp.exe +fi diff --git a/.github/scripts/install-macos.sh b/.github/scripts/install-macos.sh new file mode 100755 index 000000000..33e55a1be --- /dev/null +++ b/.github/scripts/install-macos.sh @@ -0,0 +1,16 @@ +#!/bin/sh -xe + +if [ "$1" = "ci" ]; then + armloc=$(brew fetch --bottle-tag=arm64_ventura libomp | grep -i downloaded | grep tar.gz | cut -f2 -d:) + x64loc=$(brew fetch --bottle-tag=ventura 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 + 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 extlib/eigen diff --git a/.github/scripts/install-snap.sh b/.github/scripts/install-snap.sh new file mode 100755 index 000000000..e10a91da8 --- /dev/null +++ b/.github/scripts/install-snap.sh @@ -0,0 +1,6 @@ +#!/bin/sh -xe + +sudo /snap/bin/lxd waitready +sudo /snap/bin/lxd init --auto +sudo chgrp travis /var/snap/lxd/common/lxd/unix.socket +mkdir -p "$TRAVIS_BUILD_DIR/snaps-cache" diff --git a/.travis/install-debian.sh b/.github/scripts/install-ubuntu.sh similarity index 65% rename from .travis/install-debian.sh rename to .github/scripts/install-ubuntu.sh index c8b7d70b2..6033f7351 100755 --- a/.travis/install-debian.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/flatbuffers extlib/q3d +git submodule update --init extlib/libdxfrw extlib/mimalloc extlib/eigen diff --git a/.travis/install-macos.sh b/.github/scripts/install-windows.sh similarity index 51% rename from .travis/install-macos.sh rename to .github/scripts/install-windows.sh index 792126cae..738d67dd7 100755 --- a/.travis/install-macos.sh +++ b/.github/scripts/install-windows.sh @@ -1,5 +1,3 @@ #!/bin/sh -xe -brew update -brew install freetype cairo git submodule update --init diff --git a/.github/scripts/sign-macos.sh b/.github/scripts/sign-macos.sh new file mode 100755 index 000000000..63f5734a7 --- /dev/null +++ b/.github/scripts/sign-macos.sh @@ -0,0 +1,86 @@ +#!/bin/bash -xe + +lipo \ + -create \ + build/bin/SolveSpace.app/Contents/Resources/libomp.dylib \ + build-arm64/bin/SolveSpace.app/Contents/Resources/libomp.dylib \ + -output \ + build/bin/SolveSpace.app/Contents/Resources/libomp.dylib + +lipo \ + -create \ + build/bin/SolveSpace.app/Contents/MacOS/SolveSpace \ + build-arm64/bin/SolveSpace.app/Contents/MacOS/SolveSpace \ + -output \ + build/bin/SolveSpace.app/Contents/MacOS/SolveSpace + +lipo \ + -create \ + build/bin/SolveSpace.app/Contents/MacOS/solvespace-cli \ + build-arm64/bin/SolveSpace.app/Contents/MacOS/solvespace-cli \ + -output \ + build/bin/SolveSpace.app/Contents/MacOS/solvespace-cli + +cd build + +openmp="bin/SolveSpace.app/Contents/Resources/libomp.dylib" +app="bin/SolveSpace.app" +dmg="bin/SolveSpace.dmg" +bundle_id="com.solvespace.solvespace" + +if [ "$CI" = "true" ]; then + # get the signing certificate (this is the Developer ID:Application: Your Name, exported to a p12 file, then converted to base64, e.g.: cat ~/Desktop/certificate.p12 | base64 | pbcopy) + echo $MACOS_CERTIFICATE_P12 | base64 --decode > certificate.p12 + + # create a keychain + security create-keychain -p secret build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p secret build.keychain + + # import the key + security import certificate.p12 -k build.keychain -P "${MACOS_CERTIFICATE_PASSWORD}" -T /usr/bin/codesign + + security set-key-partition-list -S apple-tool:,apple: -s -k secret build.keychain + + # check if all is good + security find-identity -v +fi + +# sign openmp +codesign -s "${MACOS_DEVELOPER_ID}" --timestamp --options runtime -f --deep "${openmp}" + +# sign the .app +codesign -s "${MACOS_DEVELOPER_ID}" --timestamp --options runtime -f --deep "${app}" + +# create the .dmg from the signed .app +hdiutil create -srcfolder "${app}" "${dmg}" + +# sign the .dmg +codesign -s "${MACOS_DEVELOPER_ID}" --timestamp --options runtime -f --deep "${dmg}" + +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}" diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 000000000..77b95b45e --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,193 @@ +name: CD + +on: + push: + branches: + - master + release: + types: + - created + +jobs: + cancel_previous_runs: + runs-on: ubuntu-latest + name: Cancel Previous Runs + permissions: + actions: write + steps: + - uses: styfle/cancel-workflow-action@0.12.1 + with: + access_token: ${{ github.token }} + + test_ubuntu: + needs: [cancel_previous_runs] + runs-on: ubuntu-latest + name: Test Ubuntu + steps: + - uses: actions/checkout@v4 + - name: Install Dependencies + run: .github/scripts/install-ubuntu.sh + - name: Build & Test + run: .github/scripts/build-ubuntu.sh + + test_windows: + needs: [cancel_previous_runs] + runs-on: windows-2019 + name: Test Windows + 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 + shell: bash + + test_macos: + needs: [cancel_previous_runs] + runs-on: macos-latest + name: Test macOS + steps: + - 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 + + build_release_windows: + needs: [test_ubuntu, test_windows, test_macos] + name: Build Release Windows + runs-on: windows-2019 + 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 + shell: bash + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: windows + path: build/bin/RelWithDebInfo/solvespace.exe + + build_release_windows_openmp: + needs: [test_ubuntu, test_windows, test_macos] + name: Build Release Windows (OpenMP) + runs-on: windows-2019 + steps: + - uses: actions/checkout@v4 + - name: Install Dependencies + run: .github/scripts/install-windows.sh + shell: bash + - name: Build & Test + run: .github/scripts/build-windows.sh release openmp + shell: bash + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: windows-openmp + path: build/bin/RelWithDebInfo/solvespace-openmp.exe + + build_release_macos: + needs: [test_ubuntu, test_windows, test_macos] + name: Build Release macOS + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Install Dependencies + run: .github/scripts/install-macos.sh ci + - name: Build & Test + run: .github/scripts/build-macos.sh release arm64 && .github/scripts/build-macos.sh release x86_64 + - name: Sign Build + run: .github/scripts/sign-macos.sh + env: + MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }} + MACOS_CERTIFICATE_P12: ${{ secrets.MACOS_CERTIFICATE_P12 }} + MACOS_APPSTORE_APP_PASSWORD: ${{ secrets.MACOS_APPSTORE_APP_PASSWORD }} + MACOS_APPSTORE_USERNAME: ${{ secrets.MACOS_APPSTORE_USERNAME }} + MACOS_DEVELOPER_ID: ${{ secrets.MACOS_DEVELOPER_ID }} + - name: Upload artifact + uses: actions/upload-artifact@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@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] + if: "!cancelled() && github.event_name == 'release'" + runs-on: ubuntu-latest + steps: + - name: Download All Workflow Artifacts + uses: actions/download-artifact@v4 + - 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 solvespace.exe + uses: actions/upload-release-asset@v1 + continue-on-error: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_upload_url.outputs.upload_url }} + asset_path: windows/solvespace.exe + asset_name: solvespace.exe + asset_content_type: binary/octet-stream + - name: Upload solvespace-openmp.exe + uses: actions/upload-release-asset@v1 + continue-on-error: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_upload_url.outputs.upload_url }} + asset_path: windows-openmp/solvespace-openmp.exe + asset_name: solvespace-openmp.exe + asset_content_type: binary/octet-stream + - name: Upload SolveSpace.dmg + uses: actions/upload-release-asset@v1 + continue-on-error: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_upload_url.outputs.upload_url }} + asset_path: macos/SolveSpace.dmg + asset_name: SolveSpace.dmg + asset_content_type: binary/octet-stream 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 new file mode 100644 index 000000000..423bd91aa --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,61 @@ +name: Test + +on: + pull_request: + branches: + - master + push: + branches-ignore: + - master + tags-ignore: + - v* + +jobs: + test_ubuntu: + runs-on: ubuntu-latest + name: Test Ubuntu + steps: + - 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 + name: Test Windows + 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 + shell: bash + + test_macos: + runs-on: macos-latest + name: Test macOS + steps: + - 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..d64590f2a --- /dev/null +++ b/.github/workflows/wasmlib.yml @@ -0,0 +1,74 @@ +name: WASM Library + +on: + push: + branches: [ master, python, experiments, stateful-c-lib ] + tags: [ v* ] + +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 5301e1426..2b76d0635 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,13 @@ /debian/libslvs1-dev/ /obj-*/ /*.slvs +.vscode/ + +# Visual Studio +out/ +.vs/ +CMakeSettings.json +__pycache__/ +solvespace.egg-info/ +dist/ +.DS_Store \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 51dd07929..48d3b8c1c 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://git.savannah.nongnu.org/git/freetype/freetype2.git [submodule "extlib/libdxfrw"] path = extlib/libdxfrw url = https://github.com/solvespace/libdxfrw.git @@ -20,9 +20,9 @@ [submodule "extlib/angle"] path = extlib/angle url = https://github.com/solvespace/angle -[submodule "extlib/flatbuffers"] - path = extlib/flatbuffers - url = https://github.com/google/flatbuffers -[submodule "extlib/q3d"] - path = extlib/q3d - url = https://github.com/q3k/q3d +[submodule "extlib/mimalloc"] + path = extlib/mimalloc + url = https://github.com/microsoft/mimalloc +[submodule "extlib/eigen"] + path = extlib/eigen + url = https://gitlab.com/libeigen/eigen.git diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9e3fa7929..000000000 --- a/.travis.yml +++ /dev/null @@ -1,50 +0,0 @@ -language: c -git: - submodules: false -jobs: - include: - - stage: test - name: "Debian" - os: linux - dist: bionic - install: ./.travis/install-debian.sh - script: ./.travis/build-debian.sh - - stage: deploy - name: "OSX" - os: osx - osx_image: xcode8.3 - install: ./.travis/install-macos.sh - # the awk command is a workaround for https://github.com/travis-ci/travis-ci/issues/4704. - script: ./.travis/build-macos.sh | awk '/.{0,32}/ {print $0}' - deploy: - provider: releases - api_key: - secure: dDlkIawHcODlW9B/20/cQCtzeoocvs0hKuNngRKXKqzXLWTRq33oq/B7+39tAixWbmv6exTpijiKrRNFiSCW5Z4iwHLwaRD4XJznxw63e/Hus/dxg2Tvqx7XFpkCz8mT1Z+gZQE5YxAngeZPpI/sZbZtF1UO3yH5eLeeokZ15p26ZskQUPoYuzrTgTzYL3XfpG3F+20rNBawH1ycsCTVD/08/n31d2m3CrKAsbW7er92ek6w4fzKr7NW8WeXjrPJETVpw5fQg1Od3pRGW8dPQaJcvKQEogMp8Mm0ETYd0qigg89/giBz7QwOgmAWQ4dH+DfZH4Ojl//127QztBolMvyDMQBykWrtJoGcij05sT6K2IJr2FHeUBO12MAEdjiVvhQj3DtTzjPiZAHHDBSLWxLKWWhlhHE4pq7g1MQhqXkaAHI2BLNzwLmaowbMT0bECf9yfz6xx18h6XPQFX44oOktraobVALFlyHqeKa8zdcUt22LF6uAL1m5dxL0tny3eXCIPE4UH/RZgua/cHV9G3cUvKQa/QnFSLRhvWVSbGB+7YsHouBJcsUOOW1gmd5442XuC7mpppccRldh+GSxUk6TBJRAx7TeQ0ybDUaoco9MUqp2twv3KreR2+8Q12PDaAhfQVNEGdF3wTm1sShImjCN4VN3eSLlBEbve1QRQXM= - skip_cleanup: true - file: build/SolveSpace.dmg - on: - repo: solvespace/solvespace - tags: true - - stage: deploy - name: "Snap" - os: linux - dist: bionic - addons: - snaps: - - name: snapcraft - confinement: classic - script: ./.travis/build-snap.sh - deploy: - - provider: script - script: ./.travis/deploy-snap.sh edge - skip_cleanup: true - on: - branch: master - tags: false - - provider: script - script: ./.travis/deploy-snap.sh edge,beta - skip_cleanup: true - on: - branch: master - tags: true - diff --git a/.travis/build-debian.sh b/.travis/build-debian.sh deleted file mode 100755 index bb678daf8..000000000 --- a/.travis/build-debian.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -xe - -if echo $TRAVIS_TAG | grep ^v; then BUILD_TYPE=RelWithDebInfo; else BUILD_TYPE=Debug; fi - -mkdir build -cd build -cmake .. \ - -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ - -DENABLE_SANITIZERS=ON -make VERBOSE=1 -make test_solvespace diff --git a/.travis/build-macos.sh b/.travis/build-macos.sh deleted file mode 100755 index b87e2fdbb..000000000 --- a/.travis/build-macos.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -xe - -if echo $TRAVIS_TAG | grep ^v; then BUILD_TYPE=RelWithDebInfo; else BUILD_TYPE=Debug; fi - -mkdir build -cd build -cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.7 -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. -make VERBOSE=1 -make test_solvespace diff --git a/.travis/build-snap.sh b/.travis/build-snap.sh deleted file mode 100755 index d824b3836..000000000 --- a/.travis/build-snap.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -xe - -lp_data_dir="$HOME/.local/share/snapcraft/provider/launchpad" -lp_credentials="$lp_data_dir/credentials" - -mkdir -p "$lp_data_dir" -openssl aes-256-cbc -K $encrypted_c4bc81f026a2_key -iv $encrypted_c4bc81f026a2_iv \ - -in .travis/launchpad-credentials.enc \ - -out "$lp_credentials" -d -chmod 600 "$lp_credentials" - -./pkg/snap/build.sh remote-build \ - --launchpad-user solvespace \ - --launchpad-accept-public-upload \ - --build-on=amd64,arm64,armhf,i386 diff --git a/.travis/deploy-snap.sh b/.travis/deploy-snap.sh deleted file mode 100755 index 91674a7c6..000000000 --- a/.travis/deploy-snap.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -e - -channels="$1" -echo "$SNAP_TOKEN" | snapcraft login --with - - -for snap in ./pkg/snap/*.snap; do - snapcraft push "$snap" --release "$channels" -done diff --git a/.travis/launchpad-credentials.enc b/.travis/launchpad-credentials.enc deleted file mode 100644 index 5eba3cca8..000000000 Binary files a/.travis/launchpad-credentials.enc and /dev/null differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 9787a9cd5..902ef0c42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,252 +1,411 @@ Changelog ========= +3.x - development +--- + +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 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 "⁵" + * fiexd crash when typing "£" + +Other + +* Merged and improved the experimental Web version (Emscripten port). +* 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. + +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 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. + +* 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. + +* 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: - * 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. - * 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. + +* 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". + +* 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: - * 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. + +* 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: - * 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. + +* 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 d442eee96..8c980fe5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,7 @@ # cmake configuration +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 @@ -7,12 +10,9 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) " mkdir build && cd build && cmake ..") endif() -cmake_minimum_required(VERSION 3.7.2 FATAL_ERROR) -if(NOT CMAKE_VERSION VERSION_LESS 3.11.0) - cmake_policy(VERSION 3.11.0) -endif() -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") + set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED YES) @@ -22,6 +22,15 @@ set(CMAKE_USER_MAKE_RULES_OVERRIDE set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX "${CMAKE_SOURCE_DIR}/cmake/cxx_flag_overrides.cmake") +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 @@ -31,10 +40,10 @@ include(GetGitCommitHash) # and instead uncomment the following, adding the complete git hash of the checkout you are using: # set(GIT_COMMIT_HASH 0000000000000000000000000000000000000000) -project(solvespace) -set(solvespace_VERSION_MAJOR 3) -set(solvespace_VERSION_MINOR 0) string(SUBSTRING "${GIT_COMMIT_HASH}" 0 8 solvespace_GIT_HASH) +project(solvespace + VERSION 3.1 + LANGUAGES C CXX ASM) set(ENABLE_GUI ON CACHE BOOL "Whether the graphical interface is enabled") @@ -46,6 +55,23 @@ set(ENABLE_COVERAGE OFF CACHE BOOL "Whether code coverage information will be collected") set(ENABLE_SANITIZERS OFF CACHE BOOL "Whether to enable Clang's AddressSanitizer and UndefinedBehaviorSanitizer") +set(ENABLE_OPENMP OFF CACHE BOOL + "Whether geometric operations will be parallelized using OpenMP") +set(ENABLE_LTO OFF CACHE BOOL + "Whether interprocedural (global) optimizations are enabled") +option(USE_GTK4 + "Use GTK4 instead of GTK3" + OFF) + +# GTK4 requires C++14 or higher and more permissive compilation +if(USE_GTK4) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive") +endif() +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)") @@ -55,10 +81,6 @@ if("${CMAKE_GENERATOR}" STREQUAL "Xcode") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_BINARY_DIR}/bin>) endif() -if(NOT CMAKE_C_COMPILER_ID STREQUAL CMAKE_CXX_COMPILER_ID) - message(FATAL_ERROR "C and C++ compilers should be supplied by the same vendor") -endif() - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) # GCC 4.8/4.9 ship with broken but present . meh. @@ -67,28 +89,57 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") endif() # common compiler flags - include(CheckCXXCompilerFlag) -set(FILE_PREFIX_MAP "-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.") -check_cxx_compiler_flag("${FILE_PREFIX_MAP}" HAS_FILE_PREFIX_MAP) -if(HAS_FILE_PREFIX_MAP) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FILE_PREFIX_MAP}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FILE_PREFIX_MAP}") +if (NOT APPLE) + set(FILE_PREFIX_MAP "-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.") + check_cxx_compiler_flag("${FILE_PREFIX_MAP}" HAS_FILE_PREFIX_MAP) + if(HAS_FILE_PREFIX_MAP) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FILE_PREFIX_MAP}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FILE_PREFIX_MAP}") + endif() endif() if(MINGW) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++") + # Link 32 bit SolveSpace with --large-address-aware which allows it to access + # up to 3GB on a properly configured 32 bit Windows and up to 4GB on 64 bit. + # See https://msdn.microsoft.com/en-us/library/aa366778 + set(CMAKE_EXE_LINKER_FLAGS "-Wl,--large-address-aware") +endif() - if(TRIPLE STREQUAL "i686-w64-mingw32") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse2") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2") +# Ensure that all platforms use 64-bit IEEE floating point operations for consistency; +# this is most important for the testsuite, which compares savefiles directly +# and depends on consistent rounding of intermediate results. +if(CMAKE_SYSTEM_PROCESSOR STREQUAL "i686" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "X86") + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(FLOAT_FLAGS "-mfpmath=sse -msse2") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(FLOAT_FLAGS "-msse2") endif() + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FLOAT_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FLOAT_FLAGS}") endif() -if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") +if(ENABLE_LTO) + include(CheckIPOSupported) + check_ipo_supported() + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +endif() + +if(ENABLE_OPENMP) + find_package( OpenMP REQUIRED ) + if(OPENMP_FOUND) + add_library(slvs_openmp INTERFACE) + target_compile_options(slvs_openmp INTERFACE ${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() if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") @@ -96,12 +147,38 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") endif() if(ENABLE_SANITIZERS) - list(APPEND SANITIZERS address alignment bounds) - list(APPEND SANITIZERS shift signed-integer-overflow integer-divide-by-zero) - list(APPEND SANITIZERS null bool enum) - list(APPEND SANITIZERS return) - string(REPLACE ";" "," SANITIZERS "${SANITIZERS}") - set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZERS} -fno-sanitize-recover=address,undefined") + if(NOT SANITIZERS) + set(SANITIZERS "address;undefined") + 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() + + string(REPLACE ";" "," SANITIZE_OPTIONS "${SANITIZE_OPTIONS}") + + if (NOT APPLE) + set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZE_OPTIONS} -fno-sanitize-recover=address,undefined") + else() + set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZE_OPTIONS}") + endif() if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls") @@ -118,78 +195,105 @@ 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(EMSCRIPTEN) + set(M_LIBRARY "" CACHE STRING "libm (not necessary)" FORCE) +endif() + +if(ENABLE_GUI OR ENABLE_CLI) + message(STATUS "Using in-tree libdxfrw") + add_subdirectory(extlib/libdxfrw) +endif() -message(STATUS "Using in-tree flatbuffers") -set(FLATBUFFERS_BUILD_FLATLIB ON CACHE BOOL "") -set(FLATBUFFERS_BUILD_FLATC ON CACHE BOOL "") -set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "") -set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "") -add_subdirectory(extlib/flatbuffers EXCLUDE_FROM_ALL) +message(STATUS "Using in-tree 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 "") +add_subdirectory(extlib/mimalloc EXCLUDE_FROM_ALL) +set(MIMALLOC_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/mimalloc/include) + +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() + 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() -message(STATUS "Using in-tree q3d") -add_subdirectory(extlib/q3d) -set(Q3D_INCLUDE_DIR ${CMAKE_BINARY_DIR}/extlib/q3d) -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. +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) + include(FindVendoredPackage) + include(AddVendoredSubdirectory) - if(APPLE) set(FORCE_VENDORED_ZLIB ON) set(FORCE_VENDORED_PNG ON) set(FORCE_VENDORED_Freetype ON) - endif() - 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_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") - add_vendored_subdirectory(extlib/pixman) - set(PIXMAN_FOUND YES) - set(PIXMAN_LIBRARY pixman) - set(PIXMAN_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/pixman/pixman) - list(APPEND PIXMAN_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/extlib/pixman/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) - pkg_check_modules(CAIRO REQUIRED cairo) + 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 + 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) + find_package(Cairo REQUIRED) + endif() endif() # GUI dependencies @@ -223,12 +327,19 @@ if(ENABLE_GUI) elseif(APPLE) find_package(OpenGL REQUIRED) find_library(APPKIT_LIBRARY AppKit REQUIRED) + 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(USE_GTK4) + pkg_check_modules(GTKMM REQUIRED gtkmm-4.0 pangomm-2.48 x11) + add_definitions(-DUSE_GTK4) + else() + pkg_check_modules(GTKMM REQUIRED gtkmm-3.0>=3.18 pangomm-1.4 x11) + endif() endif() endif() @@ -293,9 +404,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") @@ -331,15 +452,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 4caf715ba..0169567c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,11 +14,11 @@ Bug reports are always welcome! When reporting a bug, please include the followi GitHub does not allow attaching `*.slvs` files, but it does allow attaching `*.zip` files, so any savefiles should first be archived. -Signing the CLA +Licensing --------------- -To contribute code, translations, artwork, or other resources to SolveSpace, it is necessary to -sign a [Contributor License Agreement](https://cla-assistant.io/solvespace/solvespace). +SolveSpace is licensed under the GPLv3 or later and any contributions +must be made available under the terms of that license. Contributing translations ------------------------- @@ -48,6 +48,7 @@ Contributing code SolveSpace is written in C++, and currently targets all compilers compliant with C++11. This includes GCC 5 and later, Clang 3.3 and later, and Visual Studio 12 (2013) and later. +For GTK4 builds (enabled with USE_GTK4=ON), C++17 is required due to GTKmm-4 dependencies. ### High-level conventions diff --git a/README.md b/README.md index 94eb67f31..96ddaa7b0 100644 --- a/README.md +++ b/README.md @@ -1,168 +1,262 @@ -SolveSpace -========== +# SolveSpace + +SolveSpace Logo + +[![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: +```sh +# for the latest stable release: +snap install solvespace + +# for the bleeding edge builds from master: +snap install solvespace --edge ``` -snap install --edge solvespace -``` -### Via third-party binary packages +### Via automated edge builds + +> :warning: **Edge builds might be unstable or contain severe bugs!** +> They are intended for experienced users to test new features or verify bugfixes. + +Cutting edge builds from the latest master commit are available as zip archives +from the following links: -_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. +- [macOS](https://nightly.link/solvespace/solvespace/workflows/cd/master/macos.zip) +- [Windows](https://nightly.link/solvespace/solvespace/workflows/cd/master/windows.zip) +- [Windows with OpenMP enabled](https://nightly.link/solvespace/solvespace/workflows/cd/master/windows-openmp.zip) -[notesalexp]: https://notesalexp.org/packages/en/source/solvespace/ +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) or gtkmm 4.0 for GTK4 builds, +pangomm 1.4 (or pangomm 2.48 for GTK4), 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 +For GTK3 builds (default) 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 libgtkmm-3.0-dev libpangomm-1.4-dev \ + libgl-dev libglu-dev libspnav-dev +``` + +For GTK4 builds (Ubuntu 24.04 or newer recommended): + +```sh +sudo apt install git build-essential cmake zlib1g-dev libpng-dev \ + libcairo2-dev libfreetype6-dev libjson-c-dev \ + libfontconfig1-dev libgtkmm-4.0-dev libpangomm-2.48-dev \ + libgl-dev libglu-dev libspnav-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: +For GTK3 builds (default): +```sh +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 +``` + +For GTK4 builds (Fedora 38 or newer recommended): +```sh +sudo dnf install git gcc-c++ cmake zlib-devel libpng-devel \ + cairo-devel freetype-devel json-c-devel \ + fontconfig-devel gtkmm4.0-devel pangomm2_48-devel \ + mesa-libGL-devel mesa-libGLU-devel libspnav-devel +``` - git clone https://github.com/solvespace/solvespace - cd solvespace - git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d +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 - sudo make install +```sh +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_OPENMP=ON +make -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. +# Optionally +sudo make install +``` -### Building for Windows +Link Time Optimization is supported by adding `-DENABLE_LTO=ON` to cmake at the +expense of longer build time. -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: +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. - apt-get install git build-essential cmake mingw-w64 +### Building for Windows -Before building, check out the project and the necessary submodules: +Ubuntu will require 20.04 or above. Cross-compiling with WSL is also confirmed +to work. - git clone https://github.com/solvespace/solvespace - cd solvespace - git submodule update --init +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: -After that, build 32-bit SolveSpace as following: +```sh +apt-get install git build-essential cmake mingw-w64 +``` - mkdir build - cd build - cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw32.cmake \ - -DCMAKE_BUILD_TYPE=Release - make +Before building, [check out the project and the necessary submodules](#via-source-code). -Or, build 64-bit SolveSpace as following: +Build 64-bit SolveSpace with the following: - mkdir build - cd build - cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw64.cmake \ - -DCMAKE_BUILD_TYPE=Release - 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) -Building on macOS ------------------ +**Please note that this port contains many critical bugs and unimplemented core functions.** -You will need git, XCode tools and CMake. Git and CMake can be installed +You will need the usual build tools, cmake and [Emscripten][]. On a Debian derivative (e.g. Ubuntu) dependencies other than Emscripten can be installed with: + +```sh +apt-get install git build-essential cmake +``` + +First, install and prepare `emsdk`: + +```sh +git clone https://github.com/emscripten-core/emsdk +cd emsdk +./emsdk install latest +./emsdk activate latest +source ./emsdk_env.sh +cd .. +``` + +Before building, [check out the project and the necessary submodules](#via-source-code). + +After that, build SolveSpace as following: + +```sh +mkdir build +cd build +emcmake cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_LTO="ON" -DENABLE_TESTS="OFF" -DENABLE_CLI="OFF" -DENABLE_COVERAGE="OFF" +make +``` + +The graphical interface is built as multiple files in the `build/bin` directory with names +starting with `solvespace`. It can be run locally with `emrun build/bin/solvespace.html`. + +The command-line interface is not available. + +[emscripten]: https://emscripten.org/ + +## Building on macOS + +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 - 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 +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 @@ -171,26 +265,32 @@ 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. +You will need git, cmake, libexecinfo, libpng, and GTK dependencies. These can be installed from the ports tree: - pkg_add -U git cmake libexecinfo png json-c gtk3mm pangomm +For GTK3 builds (default): +```sh +pkg_add -U git cmake libexecinfo png json-c gtk3mm pangomm +``` -Before building, check out the project and the necessary submodules: +For GTK4 builds: +```sh +pkg_add -U git cmake libexecinfo png json-c gtk4mm pangomm2_48 +``` - git clone https://github.com/solvespace/solvespace - cd solvespace - git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3do +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 @@ -198,31 +298,35 @@ 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 Visual C++. +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 @@ -232,27 +336,22 @@ Space Navigator support will be disabled. First, ensure that git and gcc are in your `$PATH`. Then, run the following in bash: - git clone https://github.com/solvespace/solvespace - cd solvespace - git submodule update --init - mkdir build - cd build - cmake .. -DCMAKE_BUILD_TYPE=Release - make +```sh +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make +``` [gitwin]: https://git-scm.com/download/win [cmakewin]: http://www.cmake.org/download/#latest [mingw]: http://www.mingw.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 license](COPYING.txt). It is possible -to license SolveSpace for use in a commercial application; to do so, -[contact](http://solvespace.com/contact.pl) the developers. +SolveSpace is distributed under the terms of the [GPL v3](COPYING.txt) or later. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index bb4c6d9b2..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,29 +0,0 @@ -version: '{build}' -clone_depth: 1 -before_build: - - git submodule update --init - - set tag=x%APPVEYOR_REPO_TAG_NAME% - - if %tag:~,2% == xv (set BUILD_TYPE=RelWithDebInfo) else (set BUILD_TYPE=Debug) - - mkdir build - - cmake -G"Visual Studio 12" -Tv120 -Bbuild -H. -build_script: - - msbuild "build\src\solvespace.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - - msbuild "build\src\solvespace-cli.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - - msbuild "build\test\solvespace-testsuite.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" -test_script: - - build\bin\%BUILD_TYPE%\solvespace-testsuite.exe -artifacts: - - path: build\bin\%BUILD_TYPE%\solvespace.exe - name: solvespace.exe - - path: build\bin\%BUILD_TYPE%\solvespace-cli.exe - name: solvespace-cli.exe - - path: build\bin\%BUILD_TYPE%\solvespace.pdb - name: solvespace.pdb -deploy: - - provider: GitHub - auth_token: - secure: P9/pf2nM+jlWKe7pCjMp41HycBNP/+5AsmE/TETrDUoBOa/9WFHelqdVFrbRn9IC - description: "" - artifact: solvespace.exe,solvespace-cli.exe,solvespace.pdb - on: - appveyor_repo_tag: true diff --git a/bench/harness.cpp b/bench/harness.cpp index 1f625c6be..4180558ad 100644 --- a/bench/harness.cpp +++ b/bench/harness.cpp @@ -41,7 +41,7 @@ static bool RunBenchmark(std::function setupFn, } int main(int argc, char **argv) { - std::vector args = InitPlatform(argc, argv); + std::vector args = Platform::InitCli(argc, argv); std::string mode; Platform::Path filename; 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/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..93dfd202c --- /dev/null +++ b/cmake/Platform/Emscripten.cmake @@ -0,0 +1,20 @@ +set(EMSCRIPTEN 1) + +set(CMAKE_C_OUTPUT_EXTENSION ".o") +set(CMAKE_CXX_OUTPUT_EXTENSION ".o") +set(CMAKE_EXECUTABLE_SUFFIX ".html") + +set(CMAKE_SIZEOF_VOID_P 4) + +set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE) + +# FIXME(emscripten): Suppress non-c-typedef-for-linkage warnings in solvespace.h +add_compile_options(-Wno-non-c-typedef-for-linkage) +add_link_options(-s EXPORTED_RUNTIME_METHODS=[allocate]) + +# Enable optimization. Workaround for "too many locals" error when runs on browser. +if(CMAKE_BUILD_TYPE STREQUAL Release) + add_compile_options(-O2) +else() + add_compile_options(-O1) +endif() diff --git a/cmake/Toolchain-emscripten.cmake b/cmake/Toolchain-emscripten.cmake new file mode 100644 index 000000000..ad20ea16c --- /dev/null +++ b/cmake/Toolchain-emscripten.cmake @@ -0,0 +1,8 @@ +set(CMAKE_SYSTEM_NAME Emscripten) + +set(TRIPLE asmjs-unknown-emscripten) + +set(CMAKE_C_COMPILER emcc) +set(CMAKE_CXX_COMPILER em++) + +set(M_LIBRARY m) diff --git a/cmake/c_flag_overrides.cmake b/cmake/c_flag_overrides.cmake index b21f00e3e..978b6b659 100644 --- a/cmake/c_flag_overrides.cmake +++ b/cmake/c_flag_overrides.cmake @@ -3,4 +3,8 @@ 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 +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 new file mode 100644 index 000000000..2d0e15cfa --- /dev/null +++ b/cmake/libpng-macos-arm64.patch @@ -0,0 +1,117 @@ +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 new file mode 100644 index 000000000..5fe17916e --- /dev/null +++ b/developer_docs/IdLists_Entities_and_Remap.txt @@ -0,0 +1,49 @@ +Some notes about Entities, Entity IDs and the IdList structure +============================================================== +Sketch entities in SolveSpace are all of the same type without use of language +support for polymorphism. The entity class is defined in sketch.h. That class +contains an enum for each entity to define its type (line, arc, etc...) and some +other members that can be used to store different things depending on the entity +type. This means all entities are the same size, so some data may be reference +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 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 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 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 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 +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) 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 +constraints still refer to it if the number of copies changes. When an entity is +copied like this a new handle is created unless there is already an entity in +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 entities in a group (but not the global sketch). + diff --git a/developer_docs/Solver_Transforms.txt b/developer_docs/Solver_Transforms.txt new file mode 100644 index 000000000..98300f8b1 --- /dev/null +++ b/developer_docs/Solver_Transforms.txt @@ -0,0 +1,154 @@ + +CREATING NEW GROUPS in 3D +================================= + +New Transformed Entity Types: +----------------------------- +New construction tools may require new transformations of entities - Points, +Normals, and Faces. A number of "Types" exist to transform an entity to a new +location. The term "Type" refers to the type of transformation an entity was +created from, and should not be confused with the 3 kinds of Entity (there are +other entities but they rely on others of the 3 base types for position and +orientation information, so they don't directly transform). A list of +Types is at the top of the EntityBase class definition: + + enum class Type : uint32_t { + POINT_IN_3D = 2000, + POINT_IN_2D = 2001, + POINT_N_TRANS = 2010, + POINT_N_ROT_TRANS = 2011, + POINT_N_COPY = 2012, + POINT_N_ROT_AA = 2013, + POINT_N_ROT_AXIS_TRANS = 2014, + + NORMAL_IN_3D = 3000, + NORMAL_IN_2D = 3001, + NORMAL_N_COPY = 3010, + NORMAL_N_ROT = 3011, + NORMAL_N_ROT_AA = 3012, + + FACE_NORMAL_PT = 5000, + FACE_XPROD = 5001, + FACE_N_ROT_TRANS = 5002, + FACE_N_TRANS = 5003, + FACE_N_ROT_AA = 5004, + +Some of the point definitions with _N_ in the name are points defined by +N application of a transformation. The number of times a particular entity +is transformed is given in the member variable "timesApplied". The following +is a dectription of the various transformation Types. + +POINT_N_TRANS: Translates a point by a vector defined by param[0],param[1],param[2] + the vector is multiplied by timesApplied. + +POINT_N_ROT_TRANS: Rotates a point via quaternion param[3],param[4],param[5],param[6] + and then translates it by vector param[0],param[1],param[2] + +POINT_N_COPY: A non-transformed copy of a point - numeric copy? + +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] + +POINT_N_ROT_AXIS_TRANS: Same as POINT_N_ROT_AA but after rotation, the point is + translated along the rotation axis by distance param[7]. + +NORMAL_N_COPY A non-transformed copy of a normal - numeric copy? + +NORMAL_N_ROT: A normal rotated by a quaternion defeined by param[0],param[1],param[2],param[3] + +NORMAL_N_ROT_AA A normal rotated by timesApplied*param[0] around the axis + specified by param[1],param[2],parma[3] + +FACE_N_ROT_TRANS A face rotated then translated. Rotation is defined by + quaternion param[3],param[4],param[5],param[6]. The translation + is defined by param[0],param[1],param[2]. + +FACE_N_TRANS: Translates a face by a vector defined by param[0],param[1],param[2] + faces are defined by a point and a normal, so this just moves the point. + +FACE_N_ROT_AA Face rotated about point param[0],param[1],param[2]. The axis + is param[4],param[5],param[6]. Angle is timesApplied*param[3]. + + +All entities are copied by the function Group::CopyEntity() which has a CopyAs +parameter to indicate which kind of copy is to be made. The mapping from CopyAs to +Entity Types can be found in that function. Most point types get copied the same +way depending on CopyAs. Several of the normals get copied to the same new +entity Type because they are unaffected by translation - you don't care if CopyAs +specified a translation with rotation or just a rotation, a normal is affected the +same in either case. The mapping from entity type to new entity type has to be +decoded from the cases and if-else logic in that function. + +It is important that a transformation be properly applied to all three of the +fundamental entities - points, normals, and surfaces. + +FUNCTIONS THAT MAY NEED TO BE EXTENDED when new entity types are defined: + +These functions have default cases, so they only need to be extended to return True: +EntityBase::IsPoint() +EntityBase::IsNormal() +EntityBAse::IsFace() + +For new normal transforms the following should be filled in: +Quaternion EntityBase::NormalGetNum() +void EntityBase::NormalForceTo(Quaternion q) +ExprQuaternion EntityBase::NormalGetExprs() + +For points: +Vector EntityBase::PointGetNum() +void EntityBase::PointForceTo(Vector p) +ExprVector EntityBase::PointGetExprs() + +For Faces: +ExprVector EntityBase::FaceGetNormalExprs() +Vector EntityBase::FaceGetNormalNum() +ExprVector EntityBase::FaceGetPointExprs() +Vector EntityBase::FaceGetPointNum() + +The basic model for these transformed entities is that the group containing them +will have parameters that define the transformed entity in terms of the one it +was copied from. For example, in PointGetNum() under the case POINT_N_TRANS we see: + + Vector trans = Vector::From(param[0], param[1], param[2]); + p = numPoint.Plus(trans.ScaledBy(timesApplied)); + break; + +This take the original "numPoint" and adds a vector created from 3 parameters and +multiplied by "timesApplied". This is used in the step-translating groups. It's also +used in extrude groups where there are constraints on the 3 parameters to keep the +vector perpendicular to the sketch it was extruded from. This function returns a +numerical version of the copied point, hence "GetNum" in the name. + +Another function PointGetExprs() returns the same coordinates of the point but +returns an expression vector. When the user applies a constraint to a point, this +function is called to get algebraic expressions for the point that are suitable for +use in the solver. Most points will not be constrained directly, so expressions +are not needed for them. It is also notable that the expressions are not part of +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 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 +numerical copy (xxxGetNum). That should allow display. Dragging will not work +until ForceTo is implemented, and user constraints will not work until GetExprs() +is completed. Most of these functions have an assert that will fire if the required +new case is missing. This is may not be a complete list of everything you need to +make new entity transformations. + +One thing of note, parameters in both entities and groups are stored by their handle. +An entity can have up to 8 parameters to define how it is transformed from another +entity. By convention this array of parameter handles matches that of the group but +this is probably not strictly necessary. + +ADDING GROUP CONSTRAINTS: +The example above where a point is copied via POINT_N_TRANS for both EXTRUDE and +STEP TRANSLATING groups is a good example here. For the EXTRUDE case, two constraint +equations are added to keep the offset vector perpendicular to the sketch. These +equations are created in Group::GenerateEquations() and are nothing more than an +expression which is implicitly set equal to zero in the solver. + diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..794854bee --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,33 @@ +FROM ubuntu:24.04 + +# Install required packages +RUN apt-get update && apt-get install -y \ + build-essential \ + cmake \ + libgtkmm-4.0-dev \ + libgtk-4-dev \ + libjson-c-dev \ + libfontconfig-dev \ + libspnav-dev \ + libgl1-mesa-dev \ + git \ + zlib1g-dev \ + libpng-dev \ + libfreetype6-dev \ + libcairo2-dev \ + libpangomm-2.48-dev \ + libx11-dev \ + gettext \ + pkg-config + +# Set up working directory +WORKDIR /app + +# Clone the repository (optional, can also be mounted) +# RUN git clone https://github.com/solvespace/solvespace.git + +# Set working directory +WORKDIR /app/solvespace + +# Command to run when container starts +CMD ["/bin/bash"] diff --git a/exposed/CDemo.c b/exposed/CDemo.c index cf6107e79..bc3d67729 100644 --- a/exposed/CDemo.c +++ b/exposed/CDemo.c @@ -27,6 +27,36 @@ 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, 0); + printf("res: %i\n", res.result); + printf("rank: %i\n", res.rank); + 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. @@ -251,6 +281,8 @@ 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])); diff --git a/exposed/CMakeLists.txt b/exposed/CMakeLists.txt index bdc3fc39c..db72d4d4f 100644 --- a/exposed/CMakeLists.txt +++ b/exposed/CMakeLists.txt @@ -6,3 +6,8 @@ add_executable(CDemo target_link_libraries(CDemo slvs) + +if(EMSCRIPTEN) + set_target_properties(CDemo PROPERTIES + LINK_FLAGS "-s TOTAL_MEMORY=134217728") +endif() diff --git a/extlib/angle b/extlib/angle index 17dc43e04..8776e911b 160000 --- a/extlib/angle +++ b/extlib/angle @@ -1 +1 @@ -Subproject commit 17dc43e04c94ff526e195ae66b47b6d7e43dd949 +Subproject commit 8776e911bf71f68518ddd1995ce09d80db8c1216 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/flatbuffers b/extlib/flatbuffers deleted file mode 160000 index a1f14005a..000000000 --- a/extlib/flatbuffers +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a1f14005ab823adc1300754fd37c01e9842ed4bc 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 03fa5f30f..0b7b7b709 160000 --- a/extlib/libdxfrw +++ b/extlib/libdxfrw @@ -1 +1 @@ -Subproject commit 03fa5f30f1a1db7231a25653c9dd38044fe06640 +Subproject commit 0b7b7b709d9299565db603f878214656ef5e9ddf diff --git a/extlib/libpng b/extlib/libpng index e9c3d83d5..dbe3e0c43 160000 --- a/extlib/libpng +++ b/extlib/libpng @@ -1 +1 @@ -Subproject commit e9c3d83d5a04835806287f1e8c0f2d3a962d6673 +Subproject commit dbe3e0c43e549a1602286144d94b0666549b18e6 diff --git a/extlib/mimalloc b/extlib/mimalloc new file mode 160000 index 000000000..f819dbb4e --- /dev/null +++ b/extlib/mimalloc @@ -0,0 +1 @@ +Subproject commit f819dbb4e4813fab464aee16770f39f11476bfea diff --git a/extlib/q3d b/extlib/q3d deleted file mode 160000 index 880db1d34..000000000 --- a/extlib/q3d +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 880db1d34706778f216a2308fd82a9a3adacb314 diff --git a/include/slvs.h b/include/slvs.h index 1c97fbf82..4f9d407d1 100644 --- a/include/slvs.h +++ b/include/slvs.h @@ -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; @@ -189,11 +194,16 @@ 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 rank; + int bad; +} Slvs_SolveResult; /* Our base coordinate system has basis vectors * (1, 0, 0) (0, 1, 0) (0, 0, 1) @@ -397,6 +407,95 @@ 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 Slvs_Entity SLVS_E_NONE = { 0 }; +static 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 Slvs_SolveResult Slvs_SolveSketch(uint32_t hg, int calculateFaileds); +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..940515042 --- /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; + rank: number; + bad: number; +} + +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..372e2f0fe 100644 --- a/pkg/flatpak/com.solvespace.SolveSpace.json +++ b/pkg/flatpak/com.solvespace.SolveSpace.json @@ -1,140 +1,314 @@ { + "$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", + "config-opts": [ + "-Dwarning_level=0" + ], + "build-options": { + "env": { + "MESON_ARGS": "--wrap-mode=nofallback" + } + }, "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": "cmake-ninja", + "builddir": true, "config-opts": [ - "--disable-documentation" + "-DBUILD_EXAMPLES=OFF" ], "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++/3.0/libsigc++-3.0.0.tar.xz", + "sha256": "50a0855c1eb26e6044ffe888dbe061938ab4241f96d8f3754ea7ead38ab8ed06", + "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" ], + "build-options": { + "env": { + "MESON_ARGS": "--wrap-mode=nofallback" + } + }, "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.68/glibmm-2.68.2.tar.xz", + "sha256": "91e0a8618f7b82db4aaf2648932ea2bcfe626ad030068c18fa2d106fd838d8ad", + "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*" ] }, { "name": "cairomm", + "buildsystem": "meson", "config-opts": [ - "--disable-documentation" + "-Dbuild-examples=false", + "-Dbuild-tests=false" ], + "build-options": { + "env": { + "MESON_ARGS": "--wrap-mode=nofallback" + } + }, "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.15/cairomm-1.15.4.tar.xz", + "sha256": "4cd9fd959538953dfa606aaa7a31381e3193eebf14d814d97ef928684ee9feb5", + "x-checker-data": { + "type": "gnome", + "name": "cairomm", + "stable-only": true, + "versions": { + ">=": "1.16.0" + } + } } + ], + "cleanup": [ + "/lib/cairomm-*" ] }, { "name": "pangomm", + "buildsystem": "meson", "config-opts": [ - "--disable-documentation" + "-Dbuild-examples=false", + "-Dbuild-tests=false" ], + "build-options": { + "env": { + "MESON_ARGS": "--wrap-mode=nofallback" + } + }, "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.50/pangomm-2.50.1.tar.xz", + "sha256": "ccc9923413e408c2bff637df663248327d72822f11e394b423e1c5652b7d9214", + "x-checker-data": { + "type": "gnome", + "name": "pangomm", + "stable-only": true, + "versions": { + ">=": "2.48.0" + } + } } + ], + "cleanup": [ + "/lib/pangomm-*" ] }, { "name": "atkmm", + "buildsystem": "meson", "config-opts": [ - "--disable-documentation" + "-Dbuild-examples=false", + "-Dbuild-tests=false" ], + "build-options": { + "env": { + "MESON_ARGS": "--wrap-mode=nofallback" + } + }, "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.36/atkmm-2.36.2.tar.xz", + "sha256": "6f62dd99f746985e573605937577ccfc944368f606a71ca46342d70e1cdae079", + "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" ], + "build-options": { + "env": { + "MESON_ARGS": "--wrap-mode=nofallback" + } + }, "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/4.8/gtkmm-4.8.0.tar.xz", + "sha256": "c82786d46e2b07346b6397ca7f1929d952f4922fa5c9db3dee08498b9a136cf5", + "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, + "build-options": { + "env": { + "MESON_ARGS": "--wrap-mode=nofallback" + } + }, "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" + ], + "build-options": { + "env": { + "MESON_ARGS": "--wrap-mode=nofallback" + } + }, "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" + "-DENABLE_TESTS=OFF", + "-DCMAKE_POLICY_VERSION_MINIMUM=3.5", + "-DUSE_GTK4=ON" + ], + "build-options": { + "env": { + "MESON_ARGS": "--wrap-mode=nofallback" + } + }, + "sources": [ + { + "type": "dir", + "path": "../.." + } + ], + "cleanup": [ + "/lib/libslvs*.so*" ] } ] diff --git a/pkg/snap/snap/snapcraft.yaml b/pkg/snap/snap/snapcraft.yaml index e17ecda19..a2e1ce87e 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,21 +14,24 @@ description: | confinement: strict license: GPL-3.0 +compression: lzo +grade: stable layout: /usr/share/solvespace: - bind: $SNAP/usr/share/solvespace + symlink: $SNAP/usr/share/solvespace apps: solvespace: command: usr/bin/solvespace desktop: solvespace.desktop - extensions: [gnome-3-28] + extensions: [gnome] plugs: [opengl, unity7, home, removable-media, gsettings, network] environment: - __EGL_VENDOR_LIBRARY_DIRS: $SNAP/usr/share/glvnd/egl_vendor.d + GTK_USE_PORTAL: "0" cli: command: usr/bin/solvespace-cli + extensions: [gnome] plugs: [home, removable-media, network] parts: @@ -37,40 +40,49 @@ 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/flatbuffers extlib/q3d - 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 - -DSNAP=ON + - -DENABLE_OPENMP=ON + - -DENABLE_LTO=ON + - -DCMAKE_POLICY_VERSION_MINIMUM=3.5 build-packages: - 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 + - libsigc++-2.0-dev - libspnav-dev - git + - g++ + - libc6-dev stage-packages: - libspnav0 - - libatkmm-1.6-1v5 - - libcairomm-1.0-1v5 - - libgtkmm-3.0-1v5 - - libglibmm-2.4-1v5 - - libpangomm-1.4-1v5 - libsigc++-2.0-0v5 - - libglew2.0 - - libegl-mesa0 - - libdrm2 + + cleanup: + after: [solvespace] + plugin: nil + build-snaps: [gnome-42-2204] + override-prime: | + set -eux + 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..71d8fec70 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +[build-system] +requires = ["scikit-build-core", "Cython", "cmake", "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" +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 3783f2425..2862b5f41 100644 --- a/res/CMakeLists.txt +++ b/res/CMakeLists.txt @@ -1,6 +1,7 @@ # First, set up registration functions for the kinds of resources we handle. set(resource_root ${CMAKE_CURRENT_SOURCE_DIR}/) set(resource_list) +set(resource_names) if(WIN32) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/win32/versioninfo.rc.in ${CMAKE_CURRENT_BINARY_DIR}/win32/versioninfo.rc) @@ -31,7 +32,7 @@ if(WIN32) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${source}") endfunction() elseif(APPLE) - set(app_resource_dir ${CMAKE_BINARY_DIR}/bin/SolveSpace.app/Contents/Resources) + set(app_resource_dir ${CMAKE_BINARY_DIR}/Resources) set(cli_resource_dir ${CMAKE_BINARY_DIR}/res) function(add_resource name) @@ -83,6 +84,23 @@ elseif(APPLE) DEPENDS ${source} VERBATIM) endfunction() +elseif(EMSCRIPTEN) + set(resource_dir ${CMAKE_BINARY_DIR}/src/res) + + function(add_resource name) + set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) + set(target ${resource_dir}/${name}) + set(resource_list "${resource_list};${target}" PARENT_SCOPE) + set(resource_names "${resource_names};res/${name}" PARENT_SCOPE) + + add_custom_command( + OUTPUT ${target} + COMMAND ${CMAKE_COMMAND} -E make_directory ${resource_dir} + COMMAND ${CMAKE_COMMAND} -E copy ${source} ${target} + COMMENT "Copying resource ${name}" + DEPENDS ${source} + VERBATIM) + endfunction() else() # Unix include(GNUInstallDirs) @@ -111,7 +129,8 @@ endif() function(add_resources) foreach(name ${ARGN}) add_resource(${name}) - set(resource_list "${resource_list}" PARENT_SCOPE) + set(resource_list "${resource_list}" PARENT_SCOPE) + set(resource_names "${resource_names}" PARENT_SCOPE) endforeach() endfunction() @@ -134,6 +153,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) @@ -176,6 +202,13 @@ else() install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/solvespace.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications) + set(DESKTOP_FILE_NAME 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-mime.xml DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mime/packages RENAME solvespace-slvs.xml) @@ -185,7 +218,7 @@ else() 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 +226,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() @@ -213,6 +241,7 @@ add_resources( icons/graphics-window/construction.png icons/graphics-window/equal.png icons/graphics-window/extrude.png + icons/graphics-window/helix.png icons/graphics-window/horiz.png icons/graphics-window/image.png icons/graphics-window/in3d.png @@ -227,6 +256,7 @@ add_resources( icons/graphics-window/point.png icons/graphics-window/rectangle.png icons/graphics-window/ref.png + icons/graphics-window/revolve.png icons/graphics-window/same-orientation.png icons/graphics-window/sketch-in-3d.png icons/graphics-window/sketch-in-plane.png @@ -238,6 +268,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 @@ -251,11 +283,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 @@ -281,7 +318,7 @@ add_resources( shaders/edge.frag shaders/edge.vert shaders/outline.vert - threejs/three-r76.js.gz + threejs/three-r111.min.js.gz threejs/hammer-2.0.8.js.gz threejs/SolveSpaceControls.js) @@ -290,4 +327,6 @@ add_custom_target(resources DEPENDS ${resource_list}) if(WIN32) set_property(TARGET resources PROPERTY EXTRA_SOURCES ${rc_file}) +elseif(EMSCRIPTEN) + set_property(TARGET resources PROPERTY NAMES ${resource_names}) endif() diff --git a/res/fonts/unicode.lff.gz b/res/fonts/unicode.lff.gz index ec0501dd9..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..0479aae44 --- /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 + ryan_AT_ryanpavlik.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..e9b78de23 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}/solvespace %f MimeType=application/x-solvespace Icon=solvespace Type=Application -Categories=Graphics +Categories=Graphics;3DGraphics;Engineering; Keywords=parametric;cad;2d;3d; diff --git a/res/icons/graphics-window/angle.png b/res/icons/graphics-window/angle.png index f70252a93..8b82ba3da 100644 Binary files a/res/icons/graphics-window/angle.png and b/res/icons/graphics-window/angle.png differ diff --git a/res/icons/graphics-window/arc.png b/res/icons/graphics-window/arc.png index 807b7366d..0acded45c 100644 Binary files a/res/icons/graphics-window/arc.png and b/res/icons/graphics-window/arc.png differ diff --git a/res/icons/graphics-window/assemble.png b/res/icons/graphics-window/assemble.png index 4dad1f72b..8436dcdd8 100644 Binary files a/res/icons/graphics-window/assemble.png and b/res/icons/graphics-window/assemble.png differ diff --git a/res/icons/graphics-window/bezier.png b/res/icons/graphics-window/bezier.png index 9d2c240a4..fd7d438a4 100644 Binary files a/res/icons/graphics-window/bezier.png and b/res/icons/graphics-window/bezier.png differ diff --git a/res/icons/graphics-window/circle.png b/res/icons/graphics-window/circle.png index c176b4404..6ec2c83fb 100644 Binary files a/res/icons/graphics-window/circle.png and b/res/icons/graphics-window/circle.png differ diff --git a/res/icons/graphics-window/construction.png b/res/icons/graphics-window/construction.png index 35ddcb274..6f48aedef 100644 Binary files a/res/icons/graphics-window/construction.png and b/res/icons/graphics-window/construction.png differ diff --git a/res/icons/graphics-window/equal.png b/res/icons/graphics-window/equal.png index 6037aed0f..ba9053aad 100644 Binary files a/res/icons/graphics-window/equal.png and b/res/icons/graphics-window/equal.png differ diff --git a/res/icons/graphics-window/extrude.png b/res/icons/graphics-window/extrude.png index 5b57a0eb1..a8eb88bcc 100644 Binary files a/res/icons/graphics-window/extrude.png and b/res/icons/graphics-window/extrude.png differ diff --git a/res/icons/graphics-window/helix.png b/res/icons/graphics-window/helix.png new file mode 100644 index 000000000..ac4f50962 Binary files /dev/null and b/res/icons/graphics-window/helix.png differ diff --git a/res/icons/graphics-window/horiz.png b/res/icons/graphics-window/horiz.png index dc94540ac..3c23d6776 100644 Binary files a/res/icons/graphics-window/horiz.png and b/res/icons/graphics-window/horiz.png differ diff --git a/res/icons/graphics-window/image.png b/res/icons/graphics-window/image.png index d652d8e77..5df953a7c 100644 Binary files a/res/icons/graphics-window/image.png and b/res/icons/graphics-window/image.png differ diff --git a/res/icons/graphics-window/in3d.png b/res/icons/graphics-window/in3d.png index bad460f0d..bf395f628 100644 Binary files a/res/icons/graphics-window/in3d.png and b/res/icons/graphics-window/in3d.png differ diff --git a/res/icons/graphics-window/lathe.png b/res/icons/graphics-window/lathe.png index 8cee0583c..cb91631b2 100644 Binary files a/res/icons/graphics-window/lathe.png and b/res/icons/graphics-window/lathe.png differ diff --git a/res/icons/graphics-window/length.png b/res/icons/graphics-window/length.png index b48715e09..552c87c62 100644 Binary files a/res/icons/graphics-window/length.png and b/res/icons/graphics-window/length.png differ diff --git a/res/icons/graphics-window/line.png b/res/icons/graphics-window/line.png index b99a0d86c..356130f98 100644 Binary files a/res/icons/graphics-window/line.png and b/res/icons/graphics-window/line.png differ diff --git a/res/icons/graphics-window/ontoworkplane.png b/res/icons/graphics-window/ontoworkplane.png index fbf6ff30f..66642a330 100644 Binary files a/res/icons/graphics-window/ontoworkplane.png and b/res/icons/graphics-window/ontoworkplane.png differ diff --git a/res/icons/graphics-window/other-supp.png b/res/icons/graphics-window/other-supp.png index b2ea5a95d..a5577760f 100644 Binary files a/res/icons/graphics-window/other-supp.png and b/res/icons/graphics-window/other-supp.png differ diff --git a/res/icons/graphics-window/parallel.png b/res/icons/graphics-window/parallel.png index fe89e2f10..5f76105d1 100644 Binary files a/res/icons/graphics-window/parallel.png and b/res/icons/graphics-window/parallel.png differ diff --git a/res/icons/graphics-window/perpendicular.png b/res/icons/graphics-window/perpendicular.png index e9bf5d5f7..212771437 100644 Binary files a/res/icons/graphics-window/perpendicular.png and b/res/icons/graphics-window/perpendicular.png differ diff --git a/res/icons/graphics-window/point.png b/res/icons/graphics-window/point.png index d50ffffbd..a1a56660c 100644 Binary files a/res/icons/graphics-window/point.png and b/res/icons/graphics-window/point.png differ diff --git a/res/icons/graphics-window/pointonx.png b/res/icons/graphics-window/pointonx.png index d0f6b674c..0fa950659 100644 Binary files a/res/icons/graphics-window/pointonx.png and b/res/icons/graphics-window/pointonx.png differ diff --git a/res/icons/graphics-window/rectangle.png b/res/icons/graphics-window/rectangle.png index d36dc4c5c..3f9ea7cce 100644 Binary files a/res/icons/graphics-window/rectangle.png and b/res/icons/graphics-window/rectangle.png differ diff --git a/res/icons/graphics-window/ref.png b/res/icons/graphics-window/ref.png index 19f3b88a7..6bc2894fe 100644 Binary files a/res/icons/graphics-window/ref.png and b/res/icons/graphics-window/ref.png differ diff --git a/res/icons/graphics-window/revolve.png b/res/icons/graphics-window/revolve.png new file mode 100644 index 000000000..0569907cd Binary files /dev/null and b/res/icons/graphics-window/revolve.png differ diff --git a/res/icons/graphics-window/same-orientation.png b/res/icons/graphics-window/same-orientation.png index 850d2da89..7223d0cc7 100644 Binary files a/res/icons/graphics-window/same-orientation.png and b/res/icons/graphics-window/same-orientation.png differ diff --git a/res/icons/graphics-window/sketch-in-3d.png b/res/icons/graphics-window/sketch-in-3d.png index 0a73b6b56..d15a0d32c 100644 Binary files a/res/icons/graphics-window/sketch-in-3d.png and b/res/icons/graphics-window/sketch-in-3d.png differ diff --git a/res/icons/graphics-window/sketch-in-plane.png b/res/icons/graphics-window/sketch-in-plane.png index c52c40cf3..30c99bebe 100644 Binary files a/res/icons/graphics-window/sketch-in-plane.png and b/res/icons/graphics-window/sketch-in-plane.png differ diff --git a/res/icons/graphics-window/step-rotate.png b/res/icons/graphics-window/step-rotate.png index 22969f65f..a64eae44c 100644 Binary files a/res/icons/graphics-window/step-rotate.png and b/res/icons/graphics-window/step-rotate.png differ diff --git a/res/icons/graphics-window/step-translate.png b/res/icons/graphics-window/step-translate.png index 2901f9e52..493f85335 100644 Binary files a/res/icons/graphics-window/step-translate.png and b/res/icons/graphics-window/step-translate.png differ diff --git a/res/icons/graphics-window/symmetric.png b/res/icons/graphics-window/symmetric.png index e6e80ebe4..41926842b 100644 Binary files a/res/icons/graphics-window/symmetric.png and b/res/icons/graphics-window/symmetric.png differ diff --git a/res/icons/graphics-window/tangent-arc.png b/res/icons/graphics-window/tangent-arc.png index d9dc40ac6..88feddbd1 100644 Binary files a/res/icons/graphics-window/tangent-arc.png and b/res/icons/graphics-window/tangent-arc.png differ diff --git a/res/icons/graphics-window/text.png b/res/icons/graphics-window/text.png index 8923573ab..886a80aa3 100644 Binary files a/res/icons/graphics-window/text.png and b/res/icons/graphics-window/text.png differ diff --git a/res/icons/graphics-window/trim.png b/res/icons/graphics-window/trim.png index 248e448ce..de11e17aa 100644 Binary files a/res/icons/graphics-window/trim.png and b/res/icons/graphics-window/trim.png differ diff --git a/res/icons/graphics-window/vert.png b/res/icons/graphics-window/vert.png index f137913c9..996b191eb 100644 Binary files a/res/icons/graphics-window/vert.png and b/res/icons/graphics-window/vert.png differ 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/icons/text-window/constraint.png b/res/icons/text-window/constraint.png index bc1a7178a..431ed754b 100644 Binary files a/res/icons/text-window/constraint.png and b/res/icons/text-window/constraint.png differ diff --git a/res/icons/text-window/construction.png b/res/icons/text-window/construction.png index 35ddcb274..0ecc9a130 100644 Binary files a/res/icons/text-window/construction.png and b/res/icons/text-window/construction.png differ diff --git a/res/icons/text-window/edges.png b/res/icons/text-window/edges.png index fcfd009ff..f428de17b 100644 Binary files a/res/icons/text-window/edges.png and b/res/icons/text-window/edges.png differ diff --git a/res/icons/text-window/faces.png b/res/icons/text-window/faces.png index e92cd5dce..57345dff4 100644 Binary files a/res/icons/text-window/faces.png and b/res/icons/text-window/faces.png differ diff --git a/res/icons/text-window/mesh.png b/res/icons/text-window/mesh.png index efc6278a2..260b083f6 100644 Binary files a/res/icons/text-window/mesh.png and b/res/icons/text-window/mesh.png differ diff --git a/res/icons/text-window/normal.png b/res/icons/text-window/normal.png index c87ea5176..95a833915 100644 Binary files a/res/icons/text-window/normal.png and b/res/icons/text-window/normal.png differ diff --git a/res/icons/text-window/occluded-invisible.png b/res/icons/text-window/occluded-invisible.png index 5ab4f82fb..ed353012d 100644 Binary files a/res/icons/text-window/occluded-invisible.png and b/res/icons/text-window/occluded-invisible.png differ diff --git a/res/icons/text-window/occluded-stippled.png b/res/icons/text-window/occluded-stippled.png index 6d7a33f22..04ee458eb 100644 Binary files a/res/icons/text-window/occluded-stippled.png and b/res/icons/text-window/occluded-stippled.png differ diff --git a/res/icons/text-window/occluded-visible.png b/res/icons/text-window/occluded-visible.png index 2ccd73e6c..23415c300 100644 Binary files a/res/icons/text-window/occluded-visible.png and b/res/icons/text-window/occluded-visible.png differ diff --git a/res/icons/text-window/outlines.png b/res/icons/text-window/outlines.png index 2c07fba05..f947ec7fe 100644 Binary files a/res/icons/text-window/outlines.png and b/res/icons/text-window/outlines.png differ diff --git a/res/icons/text-window/point.png b/res/icons/text-window/point.png index 421688105..a1a56660c 100644 Binary files a/res/icons/text-window/point.png and b/res/icons/text-window/point.png differ diff --git a/res/icons/text-window/workplane.png b/res/icons/text-window/workplane.png index 7df689757..8e293cd50 100644 Binary files a/res/icons/text-window/workplane.png and b/res/icons/text-window/workplane.png differ diff --git a/res/locales.txt b/res/locales.txt index 9a3f102d7..377d65754 100644 --- a/res/locales.txt +++ b/res/locales.txt @@ -1,7 +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 d24c19b41..ab509c33c 100644 --- a/res/locales/de_DE.po +++ b/res/locales/de_DE.po @@ -1,54 +1,54 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR the PACKAGE authors +# German translations for the SolveSpace package. +# Copyright (C) 2018 the SolveSpace authors # This file is distributed under the same license as the SolveSpace package. -# FIRST AUTHOR , YEAR. # Guido Hoss , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: SolveSpace 3.0\n" -"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" -"POT-Creation-Date: 2018-07-12 22:40+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" -"PO-Revision-Date: 2018-07-19 06:55+0000\n" -"Last-Translator: Guido Hoss \n" -"Language-Team: LANGUAGE \n" -"Language: de\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:274 +#: clipboard.cpp:314 msgid "" "Cut, paste, and copy work only in a workplane.\n" "\n" "Activate one with Sketch -> In Workplane." msgstr "" -"Ausschneiden, Einfügen und Kopieren sind nur in einer Arbeitsebene zulässig.\n" +"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:291 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." msgstr "Zwischenablage ist leer; es gibt nichts einzufügen." -#: clipboard.cpp:338 +#: 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:354 textscreens.cpp:709 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "Maßstab kann nicht Null sein." -#: clipboard.cpp:392 +#: 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:404 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." msgstr "Wählen Sie zwei Punkte, um den Verschiebungsvektor zu definieren." -#: clipboard.cpp:414 +#: 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:418 +#: 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:423 +#: clipboard.cpp:467 msgid "No workplane active." msgstr "Es ist keine Arbeitsebene aktiv." -#: confscreen.cpp:336 +#: 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:347 style.cpp:653 textscreens.cpp:730 +#: 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:372 +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." @@ -82,24 +82,25 @@ msgstr "" "Der Perspektivfaktor wird sich nicht auswirken, bis Sie Ansicht -> " "Perspektive Projektion aktivieren." -#: confscreen.cpp:386 -msgid "Specify between 0 and 8 digits after the decimal." -msgstr "Geben Sie 0 bis 8 Ziffern nach dem Dezimalzeichen an." +#: 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:398 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" msgstr "Der Exportmaßstab darf nicht Null sein!" -#: confscreen.cpp:410 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" msgstr "Der Werkzeugradialabstand darf nicht negativ sein!" -#: confscreen.cpp:464 +#: 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:467 +#: confscreen.cpp:560 msgid "Bad format: specify interval in integral minutes" msgstr "Ungültiges Format: geben Sie das Interval in ganzen Minuten an" @@ -170,115 +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:160 +#: 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.\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.\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: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 "" +"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.\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: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 "" +"Die Kurven müssen einen gemeinsamen Endpunkt haben. Schränken Sie mit " +"\"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:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply " "to:\n" @@ -302,81 +409,83 @@ msgstr "" " * eine Seitenfläche und ein Punkt [minimaler Abstand]\n" " * ein Kreis oder ein Bogen [Durchmesser]\n" -#: constraint.cpp:213 +#: 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:275 +#: 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:314 +#: 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:331 +#: constraint.cpp:515 msgid "" -"Bad selection for length difference constraint. This constraint can apply to:" -"\n" +"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:357 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" @@ -390,7 +499,7 @@ msgstr "" " * ein Liniensegment und eine Arbeitsebene [Mittelpunkt der Linie auf " "Ebene]\n" -#: constraint.cpp:415 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -411,7 +520,7 @@ msgstr "" " * eine Arbeitsebene und zwei Punkte oder ein Liniensegment [symmetrisch " "zu Arbeitsebene]\n" -#: constraint.cpp:429 +#: constraint.cpp:623 msgid "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." @@ -419,7 +528,7 @@ msgstr "" "Eine Arbeitsebene muss aktiv sein, um die Symmetrie ohne explizite " "Symmetrieebene einzuschränken." -#: constraint.cpp:459 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." @@ -427,23 +536,24 @@ msgstr "" "Aktivieren Sie eine Arbeitsebene (mit Skizze -> In Arbeitsebene), bevor Sie " "horizontal oder vertikal einschränken." -#: constraint.cpp:472 +#: 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:493 +#: constraint.cpp:697 msgid "" -"Bad selection for same orientation constraint. This constraint can apply to:\n" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" "\n" " * two normals\n" msgstr "" @@ -452,85 +562,76 @@ msgstr "" "\n" " * zwei Normale\n" -#: constraint.cpp:545 +#: constraint.cpp:748 msgid "Must select an angle constraint." msgstr "Sie müssen einen eingeschränkten Winkel auswählen." -#: constraint.cpp:557 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." msgstr "Sie müssen eine Einschränkung mit zugeordneter Kennzeichnung angeben." -#: constraint.cpp:568 +#: 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:625 -msgid "" -"The tangent arc and line segment must share an endpoint. Constrain them with " -"Constrain -> On Point before constraining tangent." -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" - -#: constraint.cpp:649 -msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them " -"with Constrain -> On Point before constraining tangent." -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" - -#: constraint.cpp:659 +#: 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:677 -msgid "" -"The curves must share an endpoint. Constrain them with Constrain -> On Point " -"before constraining 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" - -#: constraint.cpp:686 +#: 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 line segments, arcs, or beziers, that share an endpoint " -"(tangent)\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:704 +#: 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:719 +#: constraint.cpp:931 msgid "" "Bad selection for lock point where dragged constraint. This constraint can " "apply to:\n" @@ -554,19 +656,23 @@ msgstr "" "\n" " * einen Punkt\n" -#: constraint.cpp:730 +#: 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" -#: export.cpp:18 +#: 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 "" "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:60 +#: export.cpp:61 msgid "" "Bad selection for export section. Please select:\n" "\n" @@ -577,38 +683,39 @@ msgid "" msgstr "" "Ungültige Auswahl für teilweisen Export. Bitte wählen Sie aus:\n" "\n" -" * nichts, mit einer aktiven Arbeitsebene [Arbeitsebene ist Schnittebene]\n" +" * nichts, mit einer aktiven Arbeitsebene [Arbeitsebene ist " +"Schnittebene]\n" " * eine Seitenfläche [Schnittebene durch Seitenfläche]\n" " * einen Punkt und zwei Liniensegmente [Schnittebene durch Punkt und " "parallel zu Linien]\n" -#: export.cpp:805 +#: 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:590 +#: exportvector.cpp:592 msgid "" "Some aspects of the drawing have no DXF equivalent and were not exported:\n" msgstr "" -"Eine Teile der Skizze haben kein Gegenstück in DXF und wurden nicht " +"Teile der Zeichnung haben keine Entsprechung in DXF und wurden nicht " "exportiert:\n" -#: exportvector.cpp:807 +#: exportvector.cpp:838 msgid "" "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." msgstr "" "Die PDF-Seitengröße überschreitet 200 x 200 Zoll; die Datei kann u.U. in " "vielen Programmen nicht geöffnet werden." -#: file.cpp:44 group.cpp:95 +#: file.cpp:44 group.cpp:91 msgctxt "group-name" msgid "sketch-in-plane" msgstr "Skizze-in-Ebene" @@ -618,7 +725,11 @@ msgctxt "group-name" msgid "#references" msgstr "#Referenzen" -#: file.cpp:539 +#: file.cpp:555 +msgid "The file is empty. It may be corrupt." +msgstr "Die Datei ist leer. Es kann beschädigt sein." + +#: file.cpp:560 msgid "" "Unrecognized data in file. This file may be corrupt, or from a newer version " "of the program." @@ -626,432 +737,515 @@ msgstr "" "Nicht erkannte Daten in der Datei. Diese Datei könnte beschädigt sein oder " "von einer neueren Version des Programms stammen." -#: graphicswin.cpp:29 +#: file.cpp:876 +msgctxt "title" +msgid "Missing File" +msgstr "Fehlende Datei" + +#: file.cpp:877 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "Die verlinkte Datei “%s” fehlt." + +#: 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 "" +"Möchten Sie sie selber auswählen?\n" +"Falls Sie ablehnen, wird jegliche mit der fehlenden Datei verknüpfte " +"Geometrie verworfen." + +#: file.cpp:882 +msgctxt "button" +msgid "&Yes" +msgstr "&Ja" + +#: file.cpp:884 +msgctxt "button" +msgid "&No" +msgstr "&Nein" + +#: file.cpp:886 solvespace.cpp:652 +msgctxt "button" +msgid "&Cancel" +msgstr "&Abbrechen" + +#: graphicswin.cpp:41 msgid "&File" msgstr "&Datei" -#: graphicswin.cpp:30 +#: graphicswin.cpp:42 msgid "&New" msgstr "&Neu" -#: graphicswin.cpp:31 +#: graphicswin.cpp:43 msgid "&Open..." -msgstr "&Öffnen" +msgstr "&Öffnen..." -#: graphicswin.cpp:32 +#: graphicswin.cpp:44 msgid "Open &Recent" msgstr "Öffne &letzte" -#: graphicswin.cpp:33 +#: graphicswin.cpp:45 msgid "&Save" msgstr "&Speichern" -#: graphicswin.cpp:34 +#: graphicswin.cpp:46 msgid "Save &As..." msgstr "Speichern &Als…" -#: graphicswin.cpp:36 +#: graphicswin.cpp:48 msgid "Export &Image..." msgstr "Exportiere &Bild…" -#: graphicswin.cpp:37 +#: graphicswin.cpp:49 msgid "Export 2d &View..." msgstr "Exportiere 2D-Ansicht…" -#: graphicswin.cpp:38 +#: graphicswin.cpp:50 msgid "Export 2d &Section..." msgstr "Exportiere 2D-Auswahl…" -#: graphicswin.cpp:39 +#: graphicswin.cpp:51 msgid "Export 3d &Wireframe..." -msgstr "Exportiere 3D-Drahtgittermodell" +msgstr "Exportiere 3D-Drahtgittermodell..." -#: graphicswin.cpp:40 +#: graphicswin.cpp:52 msgid "Export Triangle &Mesh..." msgstr "Exportiere Dreiecksnetz…" -#: graphicswin.cpp:41 +#: graphicswin.cpp:53 msgid "Export &Surfaces..." msgstr "Exportiere Oberflächen…" -#: graphicswin.cpp:42 +#: graphicswin.cpp:54 msgid "Im&port..." -msgstr "Im&port…" +msgstr "Im&portiere…" -#: graphicswin.cpp:45 +#: graphicswin.cpp:57 msgid "E&xit" msgstr "Beenden" -#: graphicswin.cpp:48 +#: graphicswin.cpp:60 msgid "&Edit" msgstr "&Bearbeiten" -#: graphicswin.cpp:49 +#: graphicswin.cpp:61 msgid "&Undo" msgstr "&Rückgängig machen" -#: graphicswin.cpp:50 +#: graphicswin.cpp:62 msgid "&Redo" msgstr "&Wiederholen" -#: graphicswin.cpp:51 +#: graphicswin.cpp:63 msgid "Re&generate All" -msgstr "Alles neu zeichnen" +msgstr "Alles &neu zeichnen" -#: graphicswin.cpp:53 +#: graphicswin.cpp:65 msgid "Snap Selection to &Grid" -msgstr "Auswahl auf Raster ausrichten" +msgstr "Auswahl auf &Raster ausrichten" -#: graphicswin.cpp:54 +#: graphicswin.cpp:66 msgid "Rotate Imported &90°" msgstr "Importierte Objekte &90° drehen" -#: graphicswin.cpp:56 +#: graphicswin.cpp:68 msgid "Cu&t" msgstr "Ausschneiden" -#: graphicswin.cpp:57 +#: graphicswin.cpp:69 msgid "&Copy" msgstr "Kopieren" -#: graphicswin.cpp:58 +#: graphicswin.cpp:70 msgid "&Paste" msgstr "Einfügen" -#: graphicswin.cpp:59 +#: graphicswin.cpp:71 msgid "Paste &Transformed..." -msgstr "Transformiert einfügen…" +msgstr "&Transformiert einfügen…" -#: graphicswin.cpp:60 +#: graphicswin.cpp:72 msgid "&Delete" -msgstr "Löschen" +msgstr "&Löschen" -#: graphicswin.cpp:62 +#: graphicswin.cpp:74 msgid "Select &Edge Chain" -msgstr "Kantenverlauf auswählen" +msgstr "&Kantenverlauf auswählen" -#: graphicswin.cpp:63 +#: graphicswin.cpp:75 msgid "Select &All" -msgstr "Alle auswählen" +msgstr "&Alle auswählen" -#: graphicswin.cpp:64 +#: graphicswin.cpp:76 msgid "&Unselect All" -msgstr "Alle deselektieren" +msgstr "Alle &deselektieren" -#: graphicswin.cpp:66 +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "&Linien Stile..." + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "An&sichts Projektion..." + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "&Einstellungen..." + +#: graphicswin.cpp:84 msgid "&View" msgstr "Ansicht" -#: graphicswin.cpp:67 +#: graphicswin.cpp:85 msgid "Zoom &In" msgstr "Zoom größer" -#: graphicswin.cpp:68 +#: graphicswin.cpp:86 msgid "Zoom &Out" msgstr "Zoom kleiner" -#: graphicswin.cpp:69 +#: graphicswin.cpp:87 msgid "Zoom To &Fit" msgstr "Zoom anpassen" -#: graphicswin.cpp:71 +#: graphicswin.cpp:89 msgid "Align View to &Workplane" msgstr "Ansicht auf Arbeitsebene ausrichten" -#: graphicswin.cpp:72 +#: graphicswin.cpp:90 msgid "Nearest &Ortho View" msgstr "Nächste Ortho-Ansicht" -#: graphicswin.cpp:73 +#: graphicswin.cpp:91 msgid "Nearest &Isometric View" msgstr "Nächste isometrische Ansicht" -#: graphicswin.cpp:74 +#: graphicswin.cpp:92 msgid "&Center View At Point" msgstr "Ansicht auf Punkt zentrieren" -#: graphicswin.cpp:76 +#: graphicswin.cpp:94 msgid "Show Snap &Grid" msgstr "Arbeitsraster anzeigen" -#: graphicswin.cpp:77 +#: graphicswin.cpp:95 +msgid "Darken Inactive Solids" +msgstr "Dunklere inaktive Festkörper" + +#: graphicswin.cpp:96 msgid "Use &Perspective Projection" msgstr "Perspektivische Projektion" -#: graphicswin.cpp:78 -msgid "Dimension &Units" -msgstr "Masseinheit" +#: graphicswin.cpp:97 +msgid "Show E&xploded View" +msgstr "Zeige e&xplodierte Ansicht" -#: graphicswin.cpp:79 -msgid "Dimensions in &Inches" -msgstr "Maße in Zoll" +#: graphicswin.cpp:98 +msgid "Dimension &Units" +msgstr "Maßeinheit" -#: graphicswin.cpp:80 +#: graphicswin.cpp:99 msgid "Dimensions in &Millimeters" msgstr "Maße in Millimeter" -#: graphicswin.cpp:81 +#: graphicswin.cpp:100 msgid "Dimensions in M&eters" msgstr "Masse in M&etern" -#: graphicswin.cpp:83 +#: 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:84 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" msgstr "Attributbrowser anzeigen" -#: graphicswin.cpp:86 +#: graphicswin.cpp:107 msgid "&Full Screen" msgstr "Vollbildschirm" -#: graphicswin.cpp:88 +#: graphicswin.cpp:109 msgid "&New Group" msgstr "Neue Gruppe" -#: graphicswin.cpp:89 +#: graphicswin.cpp:110 msgid "Sketch In &3d" msgstr "In 3D skizzieren" -#: graphicswin.cpp:90 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" msgstr "In neuer Arbeitsebene skizzieren" -#: graphicswin.cpp:92 +#: graphicswin.cpp:113 msgid "Step &Translating" msgstr "Kopieren und verschieben" -#: graphicswin.cpp:93 +#: graphicswin.cpp:114 msgid "Step &Rotating" msgstr "Kopieren und drehen" -#: graphicswin.cpp:95 +#: graphicswin.cpp:116 msgid "E&xtrude" -msgstr "Extrudieren" +msgstr "E&xtrudieren" -#: graphicswin.cpp:96 +#: graphicswin.cpp:117 +msgid "&Helix" +msgstr "&Helix" + +#: graphicswin.cpp:118 msgid "&Lathe" -msgstr "Rotieren" +msgstr "R&otieren" -#: graphicswin.cpp:98 +#: graphicswin.cpp:119 +msgid "Re&volve" +msgstr "D&rehen" + +#: graphicswin.cpp:121 msgid "Link / Assemble..." -msgstr "Verknüpfen / Zusammensetzen" +msgstr "Verknüpfen / Zusammensetzen..." -#: graphicswin.cpp:99 +#: graphicswin.cpp:122 msgid "Link Recent" msgstr "Letzte verknüpfen" -#: graphicswin.cpp:101 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "&Skizze" -#: graphicswin.cpp:102 +#: graphicswin.cpp:125 msgid "In &Workplane" msgstr "In Arbeitsebene" -#: graphicswin.cpp:103 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" msgstr "Im 3D-Raum" -#: graphicswin.cpp:105 +#: graphicswin.cpp:128 msgid "Datum &Point" msgstr "Bezugspunkt" -#: graphicswin.cpp:106 -msgid "&Workplane" -msgstr "Arbeitsebene" +#: graphicswin.cpp:129 +msgid "Wor&kplane" +msgstr "Arbeits&ebene" -#: graphicswin.cpp:108 +#: graphicswin.cpp:131 msgid "Line &Segment" -msgstr "Linie" +msgstr "&Linie" -#: graphicswin.cpp:109 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" -msgstr "Konstruktionslinie" +msgstr "K&onstruktionslinie" -#: graphicswin.cpp:110 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "&Rechteck" -#: graphicswin.cpp:111 +#: graphicswin.cpp:134 msgid "&Circle" msgstr "&Kreis" -#: graphicswin.cpp:112 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" msgstr "Kreisbogen" -#: graphicswin.cpp:113 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" -msgstr "Kubischer Bezier-Spline" +msgstr "Kubischer &Bezier-Spline" -#: graphicswin.cpp:115 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" msgstr "&Text in Truetype-Font" -#: graphicswin.cpp:116 -msgid "&Image" -msgstr "Bild" +#: graphicswin.cpp:139 +msgid "I&mage" +msgstr "B&ild" -#: graphicswin.cpp:118 +#: graphicswin.cpp:141 msgid "To&ggle Construction" msgstr "Konstruktionselement an/aus" -#: graphicswin.cpp:119 -msgid "Tangent &Arc at Point" +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" msgstr "Bogentangente an Punkt" -#: graphicswin.cpp:120 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" msgstr "Kurven im Schnittpunkt trennen" -#: graphicswin.cpp:122 +#: graphicswin.cpp:145 msgid "&Constrain" msgstr "&Einschränkung" -#: graphicswin.cpp:123 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "Abstand / Durchmesser" -#: graphicswin.cpp:124 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" msgstr "Referenzangabe" -#: graphicswin.cpp:125 -msgid "A&ngle" -msgstr "Winkel" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "Winkel / Gleicher Winkel" -#: graphicswin.cpp:126 +#: graphicswin.cpp:149 msgid "Reference An&gle" msgstr "Referenzwinkel" -#: graphicswin.cpp:127 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" msgstr "Komplementärwinkel" -#: graphicswin.cpp:128 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" msgstr "Referenzangabe ein/aus" -#: graphicswin.cpp:130 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "Horizontal" -#: graphicswin.cpp:131 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "&Vertikal" -#: graphicswin.cpp:133 +#: graphicswin.cpp:156 msgid "&On Point / Curve / Plane" msgstr "Auf Punkt / Kurve / Ebene" -#: graphicswin.cpp:134 -msgid "E&qual Length / Radius / Angle" -msgstr "Gleicher Abstand / Radius / Winkel" +#: graphicswin.cpp:157 +msgid "E&qual Length / Radius" +msgstr "Gleicher Abstand / Radius" -#: graphicswin.cpp:135 -msgid "Length Ra&tio" -msgstr "Längenverhältnis" +#: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "Länge / Bogen Verhäl&tnis" -#: graphicswin.cpp:136 -msgid "Length Diff&erence" -msgstr "Längendifferenz" +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Länge / Bogen Diff&erenz" -#: graphicswin.cpp:137 +#: graphicswin.cpp:160 msgid "At &Midpoint" msgstr "Auf &Mittelpunkt" -#: graphicswin.cpp:138 +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "Symmetrisch" -#: graphicswin.cpp:139 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "Paral&llel / Tangente" -#: graphicswin.cpp:140 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "Rechtwinklig" -#: graphicswin.cpp:141 +#: graphicswin.cpp:164 msgid "Same Orient&ation" msgstr "Gleiche Orientierung" -#: graphicswin.cpp:142 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "Punkt an Position fixieren" -#: graphicswin.cpp:144 +#: graphicswin.cpp:167 msgid "Comment" msgstr "Kommentar" -#: graphicswin.cpp:146 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "&Analyse" -#: graphicswin.cpp:147 +#: graphicswin.cpp:170 msgid "Measure &Volume" msgstr "&Volumen bestimmen" -#: graphicswin.cpp:148 +#: graphicswin.cpp:171 msgid "Measure A&rea" msgstr "Fläche bestimmen" -#: graphicswin.cpp:149 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" msgstr "Umfang bestimmen" -#: graphicswin.cpp:150 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" msgstr "Überlagernde Teile anzeigen" -#: graphicswin.cpp:151 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" msgstr "Freiliegende Kanten anzeigen" -#: graphicswin.cpp:152 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" msgstr "Massenmittelpunkt anzeigen" -#: graphicswin.cpp:154 -msgid "Show Degrees of &Freedom" -msgstr "Freiheitsgrade anzeigen" +#: graphicswin.cpp:177 +msgid "Show &Underconstrained Points" +msgstr "&Unterbeschränkte Punkte anzeigen" -#: graphicswin.cpp:156 +#: graphicswin.cpp:179 msgid "&Trace Point" msgstr "Punkt nachzeichnen" -#: graphicswin.cpp:157 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." -msgstr "Nachzeichnen beenden" +msgstr "Nachzeichnen beenden..." -#: graphicswin.cpp:158 +#: graphicswin.cpp:181 msgid "Step &Dimension..." msgstr "Schrittgröße…" -#: graphicswin.cpp:160 +#: graphicswin.cpp:183 msgid "&Help" msgstr "&Hilfe" -#: graphicswin.cpp:161 +#: graphicswin.cpp:184 +msgid "&Language" +msgstr "Sprache" + +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "&Website / Anleitung" -#: graphicswin.cpp:162 -msgid "&Language" -msgstr "Sprache" +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "&Gehe zu GitHub commit" -#: graphicswin.cpp:164 +#: graphicswin.cpp:188 msgid "&About" msgstr "Über" -#: graphicswin.cpp:491 +#: graphicswin.cpp:362 +msgid "(no recent files)" +msgstr "(keine vorhergehenden Dateien)" + +#: graphicswin.cpp:370 +#, c-format +msgid "File '%s' does not exist." +msgstr "Datei '%s' existiert nicht." + +#: 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:500 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel " "projection.\n" @@ -1065,20 +1259,20 @@ msgstr "" "Ändern Sie den Faktor für die Perspektivprojektion in der " "Konfigurationsmaske. Ein typischer Wert ist ca. 0,3." -#: graphicswin.cpp:581 +#: 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:862 +#: 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:882 +#: 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." @@ -1086,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:906 +#: graphicswin.cpp:1234 msgid "" "No workplane is active. Activate a workplane (with Sketch -> In Workplane) " "to define the plane for the snap grid." @@ -1095,7 +1289,7 @@ msgstr "" "(mit Skizze -> In Arbeitsebene), um die Ebene für das Gitterraster zu " "definieren." -#: graphicswin.cpp:913 +#: 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." @@ -1104,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:979 +#: 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:984 +#: 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-" @@ -1120,7 +1314,7 @@ msgstr "" "standardmäßige Arbeitsebene. Wählen Sie eine Arbeitsebene aus, oder " "erstellen Sie eine Gruppe in einer neuen Arbeitsebene." -#: graphicswin.cpp:1008 +#: graphicswin.cpp:1350 msgid "" "Bad selection for tangent arc at point. Select a single point, or select " "nothing to set up arc parameters." @@ -1128,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:1019 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" msgstr "" "Erstellen Sie einen Punkt auf dem Bogen (zeichnet im Gegenuhrzeigersinn)" -#: graphicswin.cpp:1020 +#: graphicswin.cpp:1362 msgid "click to place datum point" msgstr "Klicken Sie, um einen Bezugspunkt zu platzieren" -#: graphicswin.cpp:1021 +#: graphicswin.cpp:1363 msgid "click first point of line segment" msgstr "Klicken Sie auf den ersten Punkt des Liniensegments" -#: graphicswin.cpp:1023 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" msgstr "Klicken Sie auf den ersten Punkt der Konstruktionslinie" -#: graphicswin.cpp:1024 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" msgstr "Klicken Sie auf den ersten Punkt der kubischen Linie" -#: graphicswin.cpp:1025 +#: graphicswin.cpp:1367 msgid "click center of circle" msgstr "Klicken Sie auf den Kreismittelpunkt" -#: graphicswin.cpp:1026 +#: graphicswin.cpp:1368 msgid "click origin of workplane" msgstr "Klicken Sie auf den Ursprungspunkt der Arbeitsebene" -#: graphicswin.cpp:1027 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" msgstr "Klicken Sie auf eine Ecke des Rechtecks" -#: graphicswin.cpp:1028 +#: graphicswin.cpp:1370 msgid "click top left of text" msgstr "Klicken Sie auf die obere linke Ecke des Texts" -#: graphicswin.cpp:1034 +#: graphicswin.cpp:1376 msgid "click top left of image" msgstr "Klicken Sie auf die obere linke Ecke des Bilds" -#: graphicswin.cpp:1047 +#: graphicswin.cpp:1402 msgid "" "No entities are selected. Select entities before trying to toggle their " "construction state." @@ -1177,42 +1371,46 @@ msgstr "" "Es wurden keine Objekte ausgewählt Wählen Sie Objekte aus, bevor Sie sie von/" "zu Konstruktionsmerkmalen umwandeln." -#: group.cpp:90 +#: group.cpp:86 msgctxt "group-name" msgid "sketch-in-3d" msgstr "Skizze-in-3D" -#: group.cpp:146 +#: 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:158 +#: 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:167 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" msgstr "Extrusion" -#: group.cpp:179 +#: 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:195 msgid "" "Bad selection for new lathe group. This group can be created with:\n" "\n" @@ -1220,77 +1418,130 @@ msgid "" "to line / normal, through point)\n" " * a line segment (revolved about line segment)\n" msgstr "" -"Ungültige Auswahl für neue Gruppe mit Drehquerschnitt Diese Gruppe kann " +"Ungültige Auswahl für neue Gruppe mit Drehquerschnitt. Diese Gruppe kann " "erstellt werden mit:\n" "\n" " * einem Punkt und einem Liniensegment oder einer Normale (Drehung um " "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:202 +#: 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: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 "" +"Ungültige Auswahl für eine neue Revolve Gruppe. Diese Gruppe kann erzeugt " +"werden mit:\n" +"\n" +" * einem Punkt und einem Liniensegment oder Normale (gedreht um eine " +"Achse parallel zu Linie / Normale, durch Punkt)\n" +" * einem Liniensegment (gedreht um Liniensegment)\n" + +#: group.cpp:233 +msgctxt "group-name" +msgid "revolve" +msgstr "Revolve" + +#: 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: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 "" +"Ungültige Auswahl für eine neue Helix Gruppe. Diese Gruppe kann erzeugt " +"werden mit:\n" +"\n" +" * einem Punkt und einem Liniensegment oder Normale (gedreht um eine " +"Achse parallel zu Linie / Normale, durch Punkt)\n" +" * einem Liniensegment (gedreht um Liniensegment)\n" + +#: group.cpp:261 +msgctxt "group-name" +msgid "helix" +msgstr "Helix" + +#: 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, 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 "" -"Ungültige Auswahl für neue Rotation Diese Gruppe kann erstellt werden mit:\n" +"Ungültige Auswahl für neue Rotation. Diese Gruppe kann erstellt werden mit:\n" "\n" -" * einem Punkt in vorgegebener Arbeitsebene (in der Ebene um den Punkt " -"drehen)\n" -" * einem Punkt und einer Linie oder einer Normale (um eine Achse durch " -"den Punkt drehen, parallel zur Linie / Normale)\n" +" * einem Punkt in vorgegebener Arbeitsebene (gedreht in der Ebene um den " +"Punkt)\n" +" * einem Punkt und einer Linie oder einer Normale (gedreht um eine Achse " +"durch den Punkt, parallel zur Linie / Normale)\n" -#: group.cpp:215 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" msgstr "Drehen" -#: group.cpp:226 +#: group.cpp:298 msgctxt "group-name" msgid "translate" msgstr "Versetzen" -#: group.cpp:340 +#: group.cpp:422 msgid "(unnamed)" msgstr "unbenannt" -#: groupmesh.cpp:626 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" msgstr "Kontur nicht geschlossen, oder kein einheitlicher Linientyp!" -#: groupmesh.cpp:639 +#: groupmesh.cpp:723 msgid "points not all coplanar!" msgstr "Punkte sind nicht alle koplanar!" -#: groupmesh.cpp:641 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" msgstr "Kontur überschneidet sich selbst!" -#: groupmesh.cpp:643 +#: groupmesh.cpp:727 msgid "zero-length edge!" msgstr "Kante mit Länge Null!" -#: modify.cpp:237 +#: 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:284 +#: 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:371 +#: modify.cpp:386 msgid "" "Couldn't round this corner. Try a smaller radius, or try creating the " "desired geometry by hand with tangency constraints." @@ -1299,17 +1550,17 @@ msgstr "" "Radius, oder erstellen Sie die gewünschte Geometrie von Hand mit " "\"Tangente\"-Einschränkungen." -#: modify.cpp:575 +#: 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:601 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." msgstr "Trennen ist nur in einer Arbeitsebene möglich." -#: modify.cpp:608 +#: 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)." @@ -1317,109 +1568,109 @@ 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:713 +#: modify.cpp:734 msgid "Can't split; no intersection found." msgstr "Trennen nicht möglich; keine Überschneidung gefunden." -#: mouse.cpp:522 +#: mouse.cpp:558 +msgid "Assign to Style" +msgstr "Linientyp zuordnen" + +#: mouse.cpp:574 msgid "No Style" msgstr "Kein Linientyp" -#: mouse.cpp:523 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." msgstr "Neu erstellter benutzerdefinierter Linientyp…" -#: mouse.cpp:571 -msgid "Assign to Style" -msgstr "Linientyp zuordnen" - -#: mouse.cpp:574 +#: mouse.cpp:584 msgid "Group Info" msgstr "Info zu Gruppe" -#: mouse.cpp:577 +#: mouse.cpp:604 msgid "Style Info" msgstr "Info zu Linientyp" -#: mouse.cpp:580 +#: mouse.cpp:624 msgid "Select Edge Chain" msgstr "Kantenverlauf auswählen" -#: mouse.cpp:585 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" msgstr "Von/zu Referenzangabe wechseln" -#: mouse.cpp:591 +#: mouse.cpp:636 msgid "Other Supplementary Angle" msgstr "Anderer Komplementärwinkel" -#: mouse.cpp:596 +#: mouse.cpp:641 msgid "Snap to Grid" msgstr "Auf Raster ausrichten" -#: mouse.cpp:604 +#: mouse.cpp:650 msgid "Remove Spline Point" msgstr "Spline-Punkt löschen" -#: mouse.cpp:615 +#: mouse.cpp:685 msgid "Add Spline Point" msgstr "Spline-Punkt hinzufügen" -#: mouse.cpp:619 +#: 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:714 msgid "Toggle Construction" msgstr "Konstruktionselement an/aus" -#: mouse.cpp:633 +#: mouse.cpp:730 msgid "Delete Point-Coincident Constraint" msgstr "Einschränkung \"Punkte deckungsgleich\" löschen" -#: mouse.cpp:639 +#: mouse.cpp:748 msgid "Cut" msgstr "Ausschneiden" -#: mouse.cpp:640 +#: mouse.cpp:750 msgid "Copy" msgstr "Kopieren" -#: mouse.cpp:643 +#: mouse.cpp:754 msgid "Select All" msgstr "Alle auswählen" -#: mouse.cpp:647 +#: mouse.cpp:759 msgid "Paste" msgstr "Einfügen" -#: mouse.cpp:648 +#: mouse.cpp:761 msgid "Paste Transformed..." msgstr "Einfügen und transformieren…" -#: mouse.cpp:652 +#: mouse.cpp:766 msgid "Delete" msgstr "Löschen" -#: mouse.cpp:654 +#: mouse.cpp:769 msgid "Unselect All" msgstr "Alle deselektieren" -#: mouse.cpp:660 +#: mouse.cpp:776 msgid "Unselect Hovered" msgstr "Aktive deselektieren" -#: mouse.cpp:665 +#: mouse.cpp:785 msgid "Zoom to Fit" msgstr "Zoom an Bildschirm anpassen" -#: mouse.cpp:805 -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:1009 mouse.cpp:1292 +#: 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:1015 +#: mouse.cpp:993 msgid "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." @@ -1427,15 +1678,15 @@ msgstr "" "Ein Rechteck kann nicht in 3D erstellt werden. Aktivieren Sie zuerst eine " "Arbeitsebene mit \"Skizze -> In Arbeitsebene\"." -#: mouse.cpp:1049 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" msgstr "Klicken Sie auf die gegenüberliegende Ecke des Rechtecks" -#: mouse.cpp:1069 +#: mouse.cpp:1048 msgid "click to set radius" msgstr "Klicken Sie, um den Radius festzulegen" -#: mouse.cpp:1074 +#: mouse.cpp:1053 msgid "" "Can't draw arc in 3d; first, activate a workplane with Sketch -> In " "Workplane." @@ -1443,24 +1694,23 @@ msgstr "" "Ein Kreisbogen kann nicht in 3D erstellt werden. Aktivieren Sie zuerst eine " "Arbeitsebene mit \"Skizze -> In Arbeitsebene\"." -#: mouse.cpp:1093 +#: mouse.cpp:1072 msgid "click to place point" msgstr "Klicken Sie, um einen Punkt zu platzieren" -#: mouse.cpp:1109 +#: mouse.cpp:1088 msgid "click next point of cubic, or press Esc" msgstr "" "Klicken Sie auf den nächsten Punkt der kubischen Linie, oder drücken Sie Esc" -#: mouse.cpp:1114 +#: mouse.cpp:1093 msgid "" "Sketching in a workplane already; sketch in 3d before creating new workplane." -"" msgstr "" "Eine Arbeitsebene ist bereits aktiv. Skizzieren Sie in 3D, bevor Sie eine " "neue Arbeitsebene erstellen." -#: mouse.cpp:1130 +#: mouse.cpp:1109 msgid "" "Can't draw text in 3d; first, activate a workplane with Sketch -> In " "Workplane." @@ -1468,11 +1718,11 @@ msgstr "" "Text kann nicht in 3D erstellt werden. Aktivieren Sie zuerst eine " "Arbeitsebene mit \"Skizze -> In Arbeitsebene\"." -#: mouse.cpp:1146 -msgid "click to place bottom left of text" -msgstr "Klicken Sie auf die untere linke Ecke des Texts" +#: mouse.cpp:1126 +msgid "click to place bottom right of text" +msgstr "Klicken Sie auf die untere rechte Ecke des Texts" -#: mouse.cpp:1152 +#: mouse.cpp:1132 msgid "" "Can't draw image in 3d; first, activate a workplane with Sketch -> In " "Workplane." @@ -1480,196 +1730,430 @@ msgstr "" "Das Bild kann nicht in 3D erstellt werden. Aktivieren Sie zuerst eine " "Arbeitsebene mit \"Skizze -> In Arbeitsebene\"." -#: mouse.cpp:1178 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" -msgstr "NEUER KOMMENTAR -- DOPPELKLICKEN ZUM BEARBEITEN" +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "SolveSpace-Modelle" -#: platform/cocoamain.mm:481 platform/gtkmain.cpp:607 platform/w32main.cpp:451 -#: platform/w32main.cpp:1388 -msgctxt "title" -msgid "(new sketch)" -msgstr "(Neue Skizze)" +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "ALLE" -#: platform/cocoamain.mm:710 platform/gtkmain.cpp:912 platform/w32main.cpp:1307 -msgid "(no recent files)" -msgstr "(keine vorhergehenden Dateien)" +#: platform/gui.cpp:91 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "IDF Leiterplatte" + +#: 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:100 +msgctxt "file-type" +msgid "STL mesh" +msgstr "STL-Netz" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Wavefront OBJ-Netz" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "Three.js-kompatibles Netz, mit Ansicht" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "Three.js-kompatibles Netz, nur Netz" + +#: platform/gui.cpp:104 +msgctxt "file-type" +msgid "VRML text file" +msgstr "VRML Textdatei" + +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "STEP file" +msgstr "STEP-Datei" -#: platform/cocoamain.mm:828 platform/gtkmain.cpp:1020 +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "PDF file" +msgstr "PDF-Datei" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "Eingebettetes Postscript" + +#: platform/gui.cpp:114 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "Skalierbare Vektorgrafik" + +#: platform/gui.cpp:116 platform/gui.cpp:123 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "DXF-Datei (AutoCAD 2007)" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "HPGL file" +msgstr "HPGL-Datei" + +#: 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 "AutoCAD DXF- und DWG-Dateien" + +#: platform/gui.cpp:131 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "Werte durch Komma getrennt (CSV)" + +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" msgstr "unbenannt" -#: platform/cocoamain.mm:860 -msgid "Do you want to save the changes you made to the new sketch?" -msgstr "Möchten Sie die Änderungen in Ihrer Skizze speichern?" +#: 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/cocoamain.mm:862 -msgid "Your changes will be lost if you don't save them." -msgstr "" -"Ihre Änderungen werden verworfen, wenn sie nicht abgespeichert werden." +#: 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/cocoamain.mm:863 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" -msgid "Save" -msgstr "Speichern" +msgid "_Cancel" +msgstr "_Abbrechen" -#: platform/cocoamain.mm:864 platform/cocoamain.mm:905 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" -msgid "Cancel" -msgstr "Abbrechen" +msgid "_Save" +msgstr "_Speichern" -#: platform/cocoamain.mm:865 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" -msgid "Don't Save" -msgstr "Nicht speichern" +msgid "_Open" +msgstr "_Öffnen" -#: platform/cocoamain.mm:880 -msgid "An autosave file is available for this project." -msgstr "Eine automatisch gespeicherte Datei ist für dieses Projekt vorhanden." +#: solvespace.cpp:175 +msgctxt "title" +msgid "Autosave Available" +msgstr "Automatische Sicherungsdatei verfügbar" + +#: 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." -#: platform/cocoamain.mm:882 +#: solvespace.cpp:177 +msgctxt "dialog" msgid "Do you want to load the autosave file instead?" -msgstr "Möchten Sie die automatische Speicherdatei stattdessen öffnen?" +msgstr "Wollen Sie die automatische Sicherungsdatei stattdessen laden?" -#: platform/cocoamain.mm:883 +#: solvespace.cpp:178 msgctxt "button" -msgid "Load" -msgstr "Öffnen" +msgid "&Load autosave" +msgstr "AutoDatei &öffnen" -#: platform/cocoamain.mm:884 +#: solvespace.cpp:180 msgctxt "button" -msgid "Don't Load" -msgstr "Nicht öffnen" +msgid "Do&n't Load" +msgstr "&Nicht laden" -#: platform/cocoamain.mm:900 -msgid "" -"Do you want to locate it manually?\n" -"If you select “No”, any geometry that depends on the missing file will be " -"removed." -msgstr "" -"Möchten Sie sie selber auswählen?\n" -"Falls Sie \"Nein\" wählen, wird jegliche mit der fehlenden Datei verknüpfte " -"Geometrie verworfen." +#: solvespace.cpp:640 +msgctxt "title" +msgid "Modified File" +msgstr "Geänderte Datei" -#: platform/cocoamain.mm:903 -msgctxt "button" -msgid "Yes" -msgstr "Ja" +#: 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?" -#: platform/cocoamain.mm:906 +#: 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: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:649 msgctxt "button" -msgid "No" -msgstr "Nein" +msgid "&Save" +msgstr "&Sichern" -#: platform/cocoamain.mm:1126 platform/w32main.cpp:183 +#: solvespace.cpp:651 msgctxt "button" -msgid "OK" -msgstr "OK" +msgid "Do&n't Save" +msgstr "&Verwerfen" -#: platform/cocoamain.mm:1211 platform/gtkmain.cpp:1382 platform/w32main.cpp:1410 -#: platform/w32main.cpp:1450 +# solvespace.cpp:557 +#: solvespace.cpp:672 +msgctxt "title" +msgid "(new sketch)" +msgstr "(Neue Skizze)" + +#: solvespace.cpp:683 msgctxt "title" msgid "Property Browser" msgstr "Attribut-Browser" -#: platform/gtkmain.cpp:968 -msgctxt "title" -msgid "Open File" -msgstr "Datei öffnen" +#: 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 "" +"Einschränkungen sind momentan sichtbar und werden exportiert. Das wollen Sie " +"wahrscheinlich nicht. Verstecken Sie sie in dem auf den Link oben im " +"Textfenster klicken." -#: platform/gtkmain.cpp:970 -msgid "_Cancel" -msgstr "_Abbrechen" +#: solvespace.cpp:834 +#, c-format +msgid "" +"Can't identify file type from file extension of filename '%s'; try .dxf or ." +"dwg." +msgstr "" +"Kann den Dateityp der Datei '%s' nicht auf Grund der Dateierweiterung " +"erkennen. Versuchen Sie .dxf oder .dwg." -#: platform/gtkmain.cpp:971 -msgid "_Open" -msgstr "_Öffnen" +#: 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." -#: platform/gtkmain.cpp:1010 -msgctxt "title" -msgid "Save File" -msgstr "Datei speichern" +#: 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." -#: platform/gtkmain.cpp:1013 platform/gtkmain.cpp:1049 platform/gtkmain.cpp:1097 -msgctxt "button" -msgid "_Cancel" -msgstr "_Abbrechen" +#: solvespace.cpp:914 +msgid "The assembly does not interfere, good." +msgstr "Der Zusammenbau funktioniert, gut." -#: platform/gtkmain.cpp:1014 platform/gtkmain.cpp:1047 -msgctxt "button" -msgid "_Save" -msgstr "_Speichern" +#: solvespace.cpp:930 +#, c-format +msgid "" +"The volume of the solid model is:\n" +"\n" +" %s" +msgstr "" +"Das Volumen des Modell ist:\n" +"\n" +" %s" -#: platform/gtkmain.cpp:1042 platform/w32main.cpp:1167 +#: solvespace.cpp:939 +#, c-format msgid "" -"The file has changed since it was last saved.\n" "\n" -"Do you want to save the changes?" +"The volume of current group mesh is:\n" +"\n" +" %s" msgstr "" -"Die Datei wurde seit der letzten Speicherung geändert.\n" "\n" -"Möchten Sie die Änderungen speichern?" +"Das Volumen des Netz der aktiven Gruppe ist:\n" +"\n" +" %s" -#: platform/gtkmain.cpp:1046 platform/w32main.cpp:1169 -msgctxt "title" -msgid "Modified File" -msgstr "Geänderte Datei" +#: solvespace.cpp:944 +msgid "" +"\n" +"\n" +"Curved surfaces have been approximated as triangles.\n" +"This introduces error, typically of around 1%." +msgstr "" +"\n" +"\n" +"Gekrümmte Flächen wurden als Dreiecksnetz angenähert.\n" +"Das verursacht Fehler, typischerweise um 1%." -#: platform/gtkmain.cpp:1048 -msgctxt "button" -msgid "Do_n't Save" -msgstr "Nicht speichern" +#: 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 "" +"Die Fläche der ausgewählten Flächen ist:\n" +"\n" +" %s\n" +"\n" +"Kurven wurden als gerade Linienstücke angenähert.\n" +"Das verursacht Fehler, typischerweise um 1%%." -#: platform/gtkmain.cpp:1066 platform/w32main.cpp:1193 +#: solvespace.cpp:968 msgid "" -"An autosave file is available for this project.\n" +"This group does not contain a correctly-formed 2d closed area. It is open, " +"not coplanar, or self-intersecting." +msgstr "" +"Diese Gruppe beinhaltet keine korrekt geschlossene 2D Fläche. Sie ist offen, " +"nicht koplanar, oder überschneidet sich selbst." + +#: solvespace.cpp:980 +#, c-format +msgid "" +"The area of the region sketched in this group is:\n" +"\n" +" %s\n" "\n" -"Do you want to load the autosave file instead?" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." msgstr "" -"Eine automatisch gespeicherte Datei ist für dieses Projekt vorhanden.\n" +"Die Summe der Flächen in dieser Gruppe ist:\n" +"\n" +" %s\n" "\n" -"Wollen Sie die automatisch gespeicherte Datei öffnen?" +"Kurven wurden als gerade Linienstücke angenähert.\n" +"Das verursacht Fehler, typischerweise um 1%%." -#: platform/gtkmain.cpp:1070 platform/w32main.cpp:1195 -msgctxt "title" -msgid "Autosave Available" -msgstr "Automatische Speicherdatei verfügbar" +#: 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 "" +"Die Gesamtlänge der ausgwählten Elemente ist:\n" +"\n" +" %s\n" +"\n" +"Kurven wurden als gerade Linienstücke angenähert.\n" +"Das verursacht Fehler, typischerweise um 1%%." -#: platform/gtkmain.cpp:1071 -msgctxt "button" -msgid "_Load autosave" -msgstr "AutoDatei öffnen" +#: 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." -#: platform/gtkmain.cpp:1072 -msgctxt "button" -msgid "Do_n't Load" -msgstr "Nicht öffnen" +#: solvespace.cpp:1022 +msgid "Bad selection for trace; select a single point." +msgstr "Falsche Auswahl für Punkt nachzeichnen. Wähle einen einzelnen Punkt." -#: platform/gtkmain.cpp:1093 platform/w32main.cpp:1223 -msgctxt "title" -msgid "Missing File" -msgstr "Fehlende Datei" +#: solvespace.cpp:1049 +#, c-format +msgid "Couldn't write to '%s'" +msgstr "Konnte '%s' nicht schreiben" -#: platform/gtkmain.cpp:1094 -msgctxt "button" -msgid "_Yes" -msgstr "_Ja" +#: solvespace.cpp:1079 +msgid "The mesh is self-intersecting (NOT okay, invalid)." +msgstr "Das Netz schneidet sich selbst: Falsch, ungültig." -#: platform/gtkmain.cpp:1095 -msgctxt "button" -msgid "_No" -msgstr "_Nein" +#: solvespace.cpp:1080 +msgid "The mesh is not self-intersecting (okay, valid)." +msgstr "Das Netz schneidet sich nicht: Gut, gültig." -#: platform/gtkmain.cpp:1306 platform/w32main.cpp:179 -msgctxt "title" -msgid "Error" -msgstr "Fehler" +#: solvespace.cpp:1082 +msgid "The mesh has naked edges (NOT okay, invalid)." +msgstr "Das Netz hat lose Kanten: Falsch, ungültig." -#: platform/gtkmain.cpp:1306 platform/w32main.cpp:179 -msgctxt "title" -msgid "Message" -msgstr "Mitteilung" +#: solvespace.cpp:1083 +msgid "The mesh is watertight (okay, valid)." +msgstr "Das Netz hat keine lose Kanten: Gut, gültig." + +#: solvespace.cpp:1086 +#, c-format +msgid "" +"\n" +"\n" +"The model contains %d triangles, from %d surfaces." +msgstr "" +"\n" +"\n" +"Das Modell hat %d Dreiecke, von %d Flächen." + +#: solvespace.cpp:1090 +#, c-format +msgid "" +"%s\n" +"\n" +"%s\n" +"\n" +"Zero problematic edges, good.%s" +msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"Keine problematischen Kanten, gut.%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 problematische Kanten, schlecht.%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 "" +"Das ist SolveSpace version %s.\n" +"\n" +"Für mehr Information siehe http://solvespace.com/\n" +"\n" +"SolveSpace ist freie Software: Sie steht Ihnen frei sie zu ändern\n" +"und/oder zu vervielfältigen unter der Auflage der GNU\n" +"General Public License (GPL) Version 3 oder späterer Versionen.\n" +"\n" +"Es besteht keinerlei Gewährleistung und Haftung für das Programm, soweit " +"dies gesetzlich zulässig ist.\n" +"Für Details siehe http://gnu.org/licenses/\n" +"\n" +"© 2008-%d Jonathan Westhues und andere.\n" -#: style.cpp:160 +#: 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." @@ -1678,27 +2162,27 @@ msgstr "" "Objekt abgeleitet wurde. Versuchen Sie, dem übergeordneten Objekt einen Typ " "zuzuordnen." -#: style.cpp:659 +#: 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:662 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." msgstr "Nicht weniger als 1 Wiederholung möglich." -#: textscreens.cpp:666 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." msgstr "Nicht mehr als 999 Wiederholungen möglich." -#: textscreens.cpp:695 +#: 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:739 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." msgstr "Durchsichtigkeit muss zwischen Null und Eins sein." -#: textscreens.cpp:778 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "Radius darf nicht null oder negativ sein." @@ -1803,99 +2287,331 @@ msgid "New group rotating active sketch" msgstr "Neue Gruppe mit rotierter aktiver Skizze" #: toolbar.cpp:72 +msgid "New group helix from active sketch" +msgstr "Neue Gruppe Helix von aktiver Skizze" + +#: toolbar.cpp:74 +msgid "New group revolve active sketch" +msgstr "Neue Gruppe mit gedrehter aktiver Skizze" + +#: toolbar.cpp:76 msgid "New group step and repeat rotating" msgstr "Neue Gruppe mit kopierter gedrehter Skizze" -#: toolbar.cpp:74 +#: toolbar.cpp:78 msgid "New group step and repeat translating" msgstr "Neue Gruppe mit kopierter versetzter Skizze" -#: toolbar.cpp:76 +#: toolbar.cpp:80 msgid "New group in new workplane (thru given entities)" msgstr "" "Neue Gruppe in neuer Arbeitsebene (definiert durch ausgewählte Objekte)" -#: toolbar.cpp:78 +#: toolbar.cpp:82 msgid "New group in 3d" msgstr "Neue Gruppe in 3D" -#: toolbar.cpp:80 +#: toolbar.cpp:84 msgid "New group linking / assembling file" msgstr "Neue Gruppe mit verknüpfter Datei" -#: toolbar.cpp:84 +#: toolbar.cpp:88 msgid "Nearest isometric view" msgstr "Nächste isometrische Ansicht" -#: toolbar.cpp:86 +#: toolbar.cpp:90 msgid "Align view to active workplane" msgstr "Ansicht auf Arbeitsebene ausrichten" -#: ui.h:69 -msgid "SolveSpace models" -msgstr "SolveSpace-Modelle" - -#: ui.h:74 -msgid "PNG file" -msgstr "PNG-Datei" - -#: ui.h:79 -msgid "STL mesh" -msgstr "STL-Netz" - -#: ui.h:80 -msgid "Wavefront OBJ mesh" -msgstr "Wavefront OBJ-Netz" - -#: ui.h:81 -msgid "Three.js-compatible mesh, with viewer" -msgstr "Three.js-kompatibles Netz, mit Ansicht" - -#: ui.h:82 -msgid "Three.js-compatible mesh, mesh only" -msgstr "Three.js-kompatibles Netz, nur Netz" - -#: ui.h:87 ui.h:95 ui.h:103 -msgid "STEP file" -msgstr "STEP-Datei" - -#: ui.h:92 -msgid "PDF file" -msgstr "PDF-Datei" - -#: ui.h:93 -msgid "Encapsulated PostScript" -msgstr "Eingebettetes Postscript" - -#: ui.h:94 -msgid "Scalable Vector Graphics" -msgstr "Skalierbare Vektorgrafik" - -#: ui.h:96 ui.h:104 -msgid "DXF file (AutoCAD 2007)" -msgstr "DXF-Datei (AutoCAD 2007)" - -#: ui.h:97 -msgid "HPGL file" -msgstr "HPGL-Datei" - -#: ui.h:98 -msgid "G Code" -msgstr "G-Code" +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "Fehler" -#: ui.h:109 -msgid "AutoCAD DXF and DWG files" -msgstr "AutoCAD DXF- und DWG-Dateien" +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "Mitteilung" -#: ui.h:114 -msgid "Comma-separated values" -msgstr "Werte durch Komma getrennt" +#: util.cpp:170 +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" + +#~ msgid "Specify between 0 and 8 digits after the decimal." +#~ msgstr "Geben Sie 0 bis 8 Ziffern nach dem Dezimalzeichen an." + +#~ msgid "click to place bottom left of text" +#~ msgstr "Klicken Sie auf die untere linke Ecke des Texts" + +#~ msgid "Do you want to save the changes you made to the new sketch?" +#~ msgstr "Möchten Sie die Änderungen in Ihrer Skizze speichern?" + +#~ msgid "Your changes will be lost if you don't save them." +#~ msgstr "" +#~ "Ihre Änderungen werden verworfen, wenn sie nicht abgespeichert werden." + +#~ msgctxt "button" +#~ msgid "Save" +#~ msgstr "Speichern" + +#~ msgctxt "button" +#~ msgid "Cancel" +#~ msgstr "Abbrechen" + +#~ msgctxt "button" +#~ msgid "Don't Save" +#~ msgstr "Nicht speichern" + +#~ msgid "An autosave file is available for this project." +#~ msgstr "" +#~ "Eine automatisch gespeicherte Datei ist für dieses Projekt vorhanden." + +#~ msgid "Do you want to load the autosave file instead?" +#~ msgstr "Möchten Sie die automatische Speicherdatei stattdessen öffnen?" + +#~ msgctxt "button" +#~ msgid "Load" +#~ msgstr "Öffnen" + +#~ msgctxt "button" +#~ msgid "Don't Load" +#~ msgstr "Nicht öffnen" + +#~ msgid "" +#~ "Do you want to locate it manually?\n" +#~ "If you select “No”, any geometry that depends on the missing file will be " +#~ "removed." +#~ msgstr "" +#~ "Möchten Sie sie selber auswählen?\n" +#~ "Falls Sie \"Nein\" wählen, wird jegliche mit der fehlenden Datei " +#~ "verknüpfte Geometrie verworfen." + +#~ msgctxt "button" +#~ msgid "Yes" +#~ msgstr "Ja" + +#~ msgctxt "button" +#~ msgid "No" +#~ msgstr "Nein" + +#~ msgctxt "button" +#~ msgid "OK" +#~ msgstr "OK" + +#~ msgid "_Cancel" +#~ msgstr "_Abbrechen" + +#~ msgid "_Open" +#~ msgstr "_Öffnen" + +#~ msgid "" +#~ "The file has changed since it was last saved.\n" +#~ "\n" +#~ "Do you want to save the changes?" +#~ msgstr "" +#~ "Die Datei wurde seit der letzten Speicherung geändert.\n" +#~ "\n" +#~ "Möchten Sie die Änderungen speichern?" + +#~ msgctxt "button" +#~ msgid "Do_n't Save" +#~ msgstr "Nicht speichern" + +#~ msgid "" +#~ "An autosave file is available for this project.\n" +#~ "\n" +#~ "Do you want to load the autosave file instead?" +#~ msgstr "" +#~ "Eine automatisch gespeicherte Datei ist für dieses Projekt vorhanden.\n" +#~ "\n" +#~ "Wollen Sie die automatisch gespeicherte Datei öffnen?" + +#~ msgctxt "button" +#~ msgid "_Load autosave" +#~ msgstr "AutoDatei öffnen" + +#~ msgctxt "button" +#~ msgid "Do_n't Load" +#~ msgstr "Nicht öffnen" + +#~ msgctxt "button" +#~ msgid "_Yes" +#~ msgstr "_Ja" + +#~ msgctxt "button" +#~ msgid "_No" +#~ msgstr "_Nein" diff --git a/res/locales/en_US.po b/res/locales/en_US.po index f46db717e..4de392b9a 100644 --- a/res/locales/en_US.po +++ b/res/locales/en_US.po @@ -2,12 +2,12 @@ # Copyright (C) 2017 the SolveSpace authors # This file is distributed under the same license as the SolveSpace package. # Automatically generated, 2017. -# +# msgid "" msgstr "" "Project-Id-Version: SolveSpace 3.0\n" -"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" -"POT-Creation-Date: 2018-07-12 22:40+0000\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:274 +#: 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:291 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." msgstr "Clipboard is empty; nothing to paste." -#: clipboard.cpp:338 +#: 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:354 textscreens.cpp:709 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "Scale cannot be zero." -#: clipboard.cpp:392 +#: clipboard.cpp:436 msgid "Select one point to define origin of rotation." msgstr "Select one point to define origin of rotation." -#: clipboard.cpp:404 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." msgstr "Select two points to define translation vector." -#: clipboard.cpp:414 +#: 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:418 +#: 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:423 +#: clipboard.cpp:467 msgid "No workplane active." msgstr "No workplane active." -#: confscreen.cpp:336 +#: confscreen.cpp:410 msgid "Bad format: specify coordinates as x, y, z" msgstr "Bad format: specify coordinates as x, y, z" -#: confscreen.cpp:347 style.cpp:653 textscreens.cpp:730 +#: 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:372 +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." @@ -79,23 +79,24 @@ msgstr "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." -#: confscreen.cpp:386 -msgid "Specify between 0 and 8 digits after the decimal." -msgstr "Specify between 0 and 8 digits after the decimal." +#: 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:398 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" msgstr "Export scale must not be zero!" -#: confscreen.cpp:410 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" msgstr "Cutter radius offset must not be negative!" -#: confscreen.cpp:464 +#: confscreen.cpp:557 msgid "Bad value: autosave interval should be positive" msgstr "Bad value: autosave interval should be positive" -#: confscreen.cpp:467 +#: confscreen.cpp:560 msgid "Bad format: specify interval in integral minutes" msgstr "Bad format: specify interval in integral minutes" @@ -166,115 +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:160 +#: 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.\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.\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 "" +"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 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 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: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 "" +"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." + +#: constraint.cpp:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply " "to:\n" @@ -298,77 +403,81 @@ msgstr "" " * a plane face and a point (minimum distance)\n" " * a circle or an arc (diameter)\n" -#: constraint.cpp:213 +#: 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:275 +#: 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:314 +#: 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:331 +#: 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:357 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" @@ -380,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:415 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -400,7 +509,7 @@ msgstr "" " * workplane, and two points or a line segment (symmetric about " "workplane)\n" -#: constraint.cpp:429 +#: constraint.cpp:623 msgid "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." @@ -408,7 +517,7 @@ msgstr "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." -#: constraint.cpp:459 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." @@ -416,21 +525,21 @@ msgstr "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." -#: constraint.cpp:472 +#: 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:493 +#: constraint.cpp:697 msgid "" "Bad selection for same orientation constraint. This constraint can apply " "to:\n" @@ -442,89 +551,83 @@ msgstr "" "\n" " * two normals\n" -#: constraint.cpp:545 +#: constraint.cpp:748 msgid "Must select an angle constraint." msgstr "Must select an angle constraint." -#: constraint.cpp:557 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." msgstr "Must select a constraint with associated label." -#: constraint.cpp:568 +#: 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:625 -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." - -#: constraint.cpp:649 -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." - -#: constraint.cpp:659 +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." msgstr "Curve-curve tangency must apply in workplane." -#: constraint.cpp:677 -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." - -#: constraint.cpp:686 +#: 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:704 +#: 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:719 +#: constraint.cpp:931 msgid "" "Bad selection for lock point where dragged constraint. This constraint can " "apply to:\n" @@ -536,11 +639,15 @@ msgstr "" "\n" " * a point\n" -#: constraint.cpp:730 +#: 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" -#: export.cpp:18 +#: 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." @@ -548,7 +655,7 @@ msgstr "" "No solid model present; draw one with extrudes and revolves, or use Export " "2d View to export bare lines and curves." -#: export.cpp:60 +#: export.cpp:61 msgid "" "Bad selection for export section. Please select:\n" "\n" @@ -564,31 +671,31 @@ msgstr "" " * a point and two line segments (plane through point and parallel to " "lines)\n" -#: export.cpp:805 +#: 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:590 +#: 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:807 +#: exportvector.cpp:838 msgid "" "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." msgstr "" "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." -#: file.cpp:44 group.cpp:95 +#: file.cpp:44 group.cpp:91 msgctxt "group-name" msgid "sketch-in-plane" msgstr "sketch-in-plane" @@ -598,7 +705,11 @@ msgctxt "group-name" msgid "#references" msgstr "#references" -#: file.cpp:539 +#: file.cpp:555 +msgid "The file is empty. It may be corrupt." +msgstr "The file is empty. It may be corrupt." + +#: file.cpp:560 msgid "" "Unrecognized data in file. This file may be corrupt, or from a newer version " "of the program." @@ -606,431 +717,515 @@ msgstr "" "Unrecognized data in file. This file may be corrupt, or from a newer version " "of the program." -#: graphicswin.cpp:29 +#: file.cpp:876 +msgctxt "title" +msgid "Missing File" +msgstr "Missing File" + +#: 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: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 "" +"Do you want to locate it manually?\n" +"\n" +"If you decline, any geometry that depends on the missing file will be " +"permanently removed." + +#: file.cpp:882 +msgctxt "button" +msgid "&Yes" +msgstr "&Yes" + +#: file.cpp:884 +msgctxt "button" +msgid "&No" +msgstr "&No" + +#: file.cpp:886 solvespace.cpp:652 +msgctxt "button" +msgid "&Cancel" +msgstr "&Cancel" + +#: graphicswin.cpp:41 msgid "&File" msgstr "&File" -#: graphicswin.cpp:30 +#: graphicswin.cpp:42 msgid "&New" msgstr "&New" -#: graphicswin.cpp:31 +#: graphicswin.cpp:43 msgid "&Open..." msgstr "&Open..." -#: graphicswin.cpp:32 +#: graphicswin.cpp:44 msgid "Open &Recent" msgstr "Open &Recent" -#: graphicswin.cpp:33 +#: graphicswin.cpp:45 msgid "&Save" msgstr "&Save" -#: graphicswin.cpp:34 +#: graphicswin.cpp:46 msgid "Save &As..." msgstr "Save &As..." -#: graphicswin.cpp:36 +#: graphicswin.cpp:48 msgid "Export &Image..." msgstr "Export &Image..." -#: graphicswin.cpp:37 +#: graphicswin.cpp:49 msgid "Export 2d &View..." msgstr "Export 2d &View..." -#: graphicswin.cpp:38 +#: graphicswin.cpp:50 msgid "Export 2d &Section..." msgstr "Export 2d &Section..." -#: graphicswin.cpp:39 +#: graphicswin.cpp:51 msgid "Export 3d &Wireframe..." msgstr "Export 3d &Wireframe..." -#: graphicswin.cpp:40 +#: graphicswin.cpp:52 msgid "Export Triangle &Mesh..." msgstr "Export Triangle &Mesh..." -#: graphicswin.cpp:41 +#: graphicswin.cpp:53 msgid "Export &Surfaces..." msgstr "Export &Surfaces..." -#: graphicswin.cpp:42 +#: graphicswin.cpp:54 msgid "Im&port..." msgstr "Im&port..." -#: graphicswin.cpp:45 +#: graphicswin.cpp:57 msgid "E&xit" msgstr "E&xit" -#: graphicswin.cpp:48 +#: graphicswin.cpp:60 msgid "&Edit" msgstr "&Edit" -#: graphicswin.cpp:49 +#: graphicswin.cpp:61 msgid "&Undo" msgstr "&Undo" -#: graphicswin.cpp:50 +#: graphicswin.cpp:62 msgid "&Redo" msgstr "&Redo" -#: graphicswin.cpp:51 +#: graphicswin.cpp:63 msgid "Re&generate All" msgstr "Re&generate All" -#: graphicswin.cpp:53 +#: graphicswin.cpp:65 msgid "Snap Selection to &Grid" msgstr "Snap Selection to &Grid" -#: graphicswin.cpp:54 +#: graphicswin.cpp:66 msgid "Rotate Imported &90°" msgstr "Rotate Imported &90°" -#: graphicswin.cpp:56 +#: graphicswin.cpp:68 msgid "Cu&t" msgstr "Cu&t" -#: graphicswin.cpp:57 +#: graphicswin.cpp:69 msgid "&Copy" msgstr "&Copy" -#: graphicswin.cpp:58 +#: graphicswin.cpp:70 msgid "&Paste" msgstr "&Paste" -#: graphicswin.cpp:59 +#: graphicswin.cpp:71 msgid "Paste &Transformed..." msgstr "Paste &Transformed..." -#: graphicswin.cpp:60 +#: graphicswin.cpp:72 msgid "&Delete" msgstr "&Delete" -#: graphicswin.cpp:62 +#: graphicswin.cpp:74 msgid "Select &Edge Chain" msgstr "Select &Edge Chain" -#: graphicswin.cpp:63 +#: graphicswin.cpp:75 msgid "Select &All" msgstr "Select &All" -#: graphicswin.cpp:64 +#: graphicswin.cpp:76 msgid "&Unselect All" msgstr "&Unselect All" -#: graphicswin.cpp:66 +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "&Line Styles..." + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "&View Projection..." + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "Con&figuration..." + +#: graphicswin.cpp:84 msgid "&View" msgstr "&View" -#: graphicswin.cpp:67 +#: graphicswin.cpp:85 msgid "Zoom &In" msgstr "Zoom &In" -#: graphicswin.cpp:68 +#: graphicswin.cpp:86 msgid "Zoom &Out" msgstr "Zoom &Out" -#: graphicswin.cpp:69 +#: graphicswin.cpp:87 msgid "Zoom To &Fit" msgstr "Zoom To &Fit" -#: graphicswin.cpp:71 +#: graphicswin.cpp:89 msgid "Align View to &Workplane" msgstr "Align View to &Workplane" -#: graphicswin.cpp:72 +#: graphicswin.cpp:90 msgid "Nearest &Ortho View" msgstr "Nearest &Ortho View" -#: graphicswin.cpp:73 +#: graphicswin.cpp:91 msgid "Nearest &Isometric View" msgstr "Nearest &Isometric View" -#: graphicswin.cpp:74 +#: graphicswin.cpp:92 msgid "&Center View At Point" msgstr "&Center View At Point" -#: graphicswin.cpp:76 +#: graphicswin.cpp:94 msgid "Show Snap &Grid" msgstr "Show Snap &Grid" -#: graphicswin.cpp:77 +#: graphicswin.cpp:95 +msgid "Darken Inactive Solids" +msgstr "Darken Inactive Solids" + +#: graphicswin.cpp:96 msgid "Use &Perspective Projection" msgstr "Use &Perspective Projection" -#: graphicswin.cpp:78 +#: graphicswin.cpp:97 +msgid "Show E&xploded View" +msgstr "Show E&xploded View" + +#: graphicswin.cpp:98 msgid "Dimension &Units" msgstr "Dimension &Units" -#: graphicswin.cpp:79 -msgid "Dimensions in &Inches" -msgstr "Dimensions in &Inches" - -#: graphicswin.cpp:80 +#: graphicswin.cpp:99 msgid "Dimensions in &Millimeters" msgstr "Dimensions in &Millimeters" -#: graphicswin.cpp:81 +#: graphicswin.cpp:100 msgid "Dimensions in M&eters" msgstr "Dimensions in M&eters" -#: graphicswin.cpp:83 +#: 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:84 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" msgstr "Show Property Bro&wser" -#: graphicswin.cpp:86 +#: graphicswin.cpp:107 msgid "&Full Screen" msgstr "&Full Screen" -#: graphicswin.cpp:88 +#: graphicswin.cpp:109 msgid "&New Group" msgstr "&New Group" -#: graphicswin.cpp:89 +#: graphicswin.cpp:110 msgid "Sketch In &3d" msgstr "Sketch In &3d" -#: graphicswin.cpp:90 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" msgstr "Sketch In New &Workplane" -#: graphicswin.cpp:92 +#: graphicswin.cpp:113 msgid "Step &Translating" msgstr "Step &Translating" -#: graphicswin.cpp:93 +#: graphicswin.cpp:114 msgid "Step &Rotating" msgstr "Step &Rotating" -#: graphicswin.cpp:95 +#: graphicswin.cpp:116 msgid "E&xtrude" msgstr "E&xtrude" -#: graphicswin.cpp:96 +#: graphicswin.cpp:117 +msgid "&Helix" +msgstr "&Helix" + +#: graphicswin.cpp:118 msgid "&Lathe" msgstr "&Lathe" -#: graphicswin.cpp:98 +#: graphicswin.cpp:119 +msgid "Re&volve" +msgstr "Re&volve" + +#: graphicswin.cpp:121 msgid "Link / Assemble..." msgstr "Link / Assemble..." -#: graphicswin.cpp:99 +#: graphicswin.cpp:122 msgid "Link Recent" msgstr "Link Recent" -#: graphicswin.cpp:101 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "&Sketch" -#: graphicswin.cpp:102 +#: graphicswin.cpp:125 msgid "In &Workplane" msgstr "In &Workplane" -#: graphicswin.cpp:103 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" msgstr "Anywhere In &3d" -#: graphicswin.cpp:105 +#: graphicswin.cpp:128 msgid "Datum &Point" msgstr "Datum &Point" -#: graphicswin.cpp:106 -msgid "&Workplane" -msgstr "&Workplane" +#: graphicswin.cpp:129 +msgid "Wor&kplane" +msgstr "Wor&kplane" -#: graphicswin.cpp:108 +#: graphicswin.cpp:131 msgid "Line &Segment" msgstr "Line &Segment" -#: graphicswin.cpp:109 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" msgstr "C&onstruction Line Segment" -#: graphicswin.cpp:110 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "&Rectangle" -#: graphicswin.cpp:111 +#: graphicswin.cpp:134 msgid "&Circle" msgstr "&Circle" -#: graphicswin.cpp:112 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" msgstr "&Arc of a Circle" -#: graphicswin.cpp:113 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" msgstr "&Bezier Cubic Spline" -#: graphicswin.cpp:115 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" msgstr "&Text in TrueType Font" -#: graphicswin.cpp:116 -msgid "&Image" -msgstr "&Image" +#: graphicswin.cpp:139 +msgid "I&mage" +msgstr "I&mage" -#: graphicswin.cpp:118 +#: graphicswin.cpp:141 msgid "To&ggle Construction" msgstr "To&ggle Construction" -#: graphicswin.cpp:119 -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:120 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" msgstr "Split Curves at &Intersection" -#: graphicswin.cpp:122 +#: graphicswin.cpp:145 msgid "&Constrain" msgstr "&Constrain" -#: graphicswin.cpp:123 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "&Distance / Diameter" -#: graphicswin.cpp:124 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" msgstr "Re&ference Dimension" -#: graphicswin.cpp:125 -msgid "A&ngle" -msgstr "A&ngle" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "A&ngle / Equal Angle" -#: graphicswin.cpp:126 +#: graphicswin.cpp:149 msgid "Reference An&gle" msgstr "Reference An&gle" -#: graphicswin.cpp:127 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" msgstr "Other S&upplementary Angle" -#: graphicswin.cpp:128 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" msgstr "Toggle R&eference Dim" -#: graphicswin.cpp:130 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "&Horizontal" -#: graphicswin.cpp:131 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "&Vertical" -#: graphicswin.cpp:133 +#: graphicswin.cpp:156 msgid "&On Point / Curve / Plane" msgstr "&On Point / Curve / Plane" -#: graphicswin.cpp:134 -msgid "E&qual Length / Radius / Angle" -msgstr "E&qual Length / Radius / Angle" +#: graphicswin.cpp:157 +msgid "E&qual Length / Radius" +msgstr "E&qual Length / Radius" -#: graphicswin.cpp:135 -msgid "Length Ra&tio" -msgstr "Length Ra&tio" +#: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "Length / Arc Ra&tio" -#: graphicswin.cpp:136 -msgid "Length Diff&erence" -msgstr "Length Diff&erence" +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Length / Arc Diff&erence" -#: graphicswin.cpp:137 +#: graphicswin.cpp:160 msgid "At &Midpoint" msgstr "At &Midpoint" -#: graphicswin.cpp:138 +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "S&ymmetric" -#: graphicswin.cpp:139 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "Para&llel / Tangent" -#: graphicswin.cpp:140 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "&Perpendicular" -#: graphicswin.cpp:141 +#: graphicswin.cpp:164 msgid "Same Orient&ation" msgstr "Same Orient&ation" -#: graphicswin.cpp:142 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "Lock Point Where &Dragged" -#: graphicswin.cpp:144 +#: graphicswin.cpp:167 msgid "Comment" msgstr "Comment" -#: graphicswin.cpp:146 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "&Analyze" -#: graphicswin.cpp:147 +#: graphicswin.cpp:170 msgid "Measure &Volume" msgstr "Measure &Volume" -#: graphicswin.cpp:148 +#: graphicswin.cpp:171 msgid "Measure A&rea" msgstr "Measure A&rea" -#: graphicswin.cpp:149 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" msgstr "Measure &Perimeter" -#: graphicswin.cpp:150 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" msgstr "Show &Interfering Parts" -#: graphicswin.cpp:151 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" msgstr "Show &Naked Edges" -#: graphicswin.cpp:152 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" msgstr "Show &Center of Mass" -#: graphicswin.cpp:154 -msgid "Show Degrees of &Freedom" -msgstr "Show Degrees of &Freedom" +#: graphicswin.cpp:177 +msgid "Show &Underconstrained Points" +msgstr "Show &Underconstrained Points" -#: graphicswin.cpp:156 +#: graphicswin.cpp:179 msgid "&Trace Point" msgstr "&Trace Point" -#: graphicswin.cpp:157 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." msgstr "&Stop Tracing..." -#: graphicswin.cpp:158 +#: graphicswin.cpp:181 msgid "Step &Dimension..." msgstr "Step &Dimension..." -#: graphicswin.cpp:160 +#: graphicswin.cpp:183 msgid "&Help" msgstr "&Help" -#: graphicswin.cpp:161 +#: graphicswin.cpp:184 +msgid "&Language" +msgstr "&Language" + +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "&Website / Manual" -#: graphicswin.cpp:162 -msgid "&Language" -msgstr "&Language" +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "&Go to GitHub commit" -#: graphicswin.cpp:164 +#: graphicswin.cpp:188 msgid "&About" msgstr "&About" -#: graphicswin.cpp:491 +#: graphicswin.cpp:362 +msgid "(no recent files)" +msgstr "(no recent files)" + +#: graphicswin.cpp:370 +#, c-format +msgid "File '%s' does not exist." +msgstr "File '%s' does not exist." + +#: 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:500 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel " "projection.\n" @@ -1044,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:581 +#: 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:862 +#: 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:882 +#: 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." @@ -1062,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:906 +#: graphicswin.cpp:1234 msgid "" "No workplane is active. Activate a workplane (with Sketch -> In Workplane) " "to define the plane for the snap grid." @@ -1070,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:913 +#: 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." @@ -1078,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:979 +#: 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:984 +#: 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-" @@ -1092,7 +1287,7 @@ msgstr "" "workplane. Try selecting a workplane, or activating a sketch-in-new-" "workplane group." -#: graphicswin.cpp:1008 +#: graphicswin.cpp:1350 msgid "" "Bad selection for tangent arc at point. Select a single point, or select " "nothing to set up arc parameters." @@ -1100,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:1019 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" msgstr "click point on arc (draws anti-clockwise)" -#: graphicswin.cpp:1020 +#: graphicswin.cpp:1362 msgid "click to place datum point" msgstr "click to place datum point" -#: graphicswin.cpp:1021 +#: graphicswin.cpp:1363 msgid "click first point of line segment" msgstr "click first point of line segment" -#: graphicswin.cpp:1023 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" msgstr "click first point of construction line segment" -#: graphicswin.cpp:1024 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" msgstr "click first point of cubic segment" -#: graphicswin.cpp:1025 +#: graphicswin.cpp:1367 msgid "click center of circle" msgstr "click center of circle" -#: graphicswin.cpp:1026 +#: graphicswin.cpp:1368 msgid "click origin of workplane" msgstr "click origin of workplane" -#: graphicswin.cpp:1027 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" msgstr "click one corner of rectangle" -#: graphicswin.cpp:1028 +#: graphicswin.cpp:1370 msgid "click top left of text" msgstr "click top left of text" -#: graphicswin.cpp:1034 +#: graphicswin.cpp:1376 msgid "click top left of image" msgstr "click top left of image" -#: graphicswin.cpp:1047 +#: graphicswin.cpp:1402 msgid "" "No entities are selected. Select entities before trying to toggle their " "construction state." @@ -1148,18 +1343,19 @@ msgstr "" "No entities are selected. Select entities before trying to toggle their " "construction state." -#: group.cpp:90 +#: group.cpp:86 msgctxt "group-name" msgid "sketch-in-3d" msgstr "sketch-in-3d" -#: group.cpp:146 +#: 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" @@ -1167,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:158 +#: group.cpp:170 msgid "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " "will be extruded normal to the workplane." @@ -1177,12 +1374,16 @@ msgstr "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " "will be extruded normal to the workplane." -#: group.cpp:167 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" msgstr "extrude" -#: group.cpp:179 +#: 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:195 msgid "" "Bad selection for new lathe group. This group can be created with:\n" "\n" @@ -1196,12 +1397,58 @@ 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:202 +#: 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: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 "" +"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" + +#: group.cpp:233 +msgctxt "group-name" +msgid "revolve" +msgstr "revolve" + +#: 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: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 "" +"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" + +#: group.cpp:261 +msgctxt "group-name" +msgid "helix" +msgstr "helix" + +#: group.cpp:274 msgid "" "Bad selection for new rotation. This group can be created with:\n" "\n" @@ -1217,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:215 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" msgstr "rotate" -#: group.cpp:226 +#: group.cpp:298 msgctxt "group-name" msgid "translate" msgstr "translate" -#: group.cpp:340 +#: group.cpp:422 msgid "(unnamed)" msgstr "(unnamed)" -#: groupmesh.cpp:626 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" msgstr "not closed contour, or not all same style!" -#: groupmesh.cpp:639 +#: groupmesh.cpp:723 msgid "points not all coplanar!" msgstr "points not all coplanar!" -#: groupmesh.cpp:641 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" msgstr "contour is self-intersecting!" -#: groupmesh.cpp:643 +#: groupmesh.cpp:727 msgid "zero-length edge!" msgstr "zero-length edge!" -#: modify.cpp:237 +#: 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:284 +#: 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." @@ -1259,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:371 +#: modify.cpp:386 msgid "" "Couldn't round this corner. Try a smaller radius, or try creating the " "desired geometry by hand with tangency constraints." @@ -1267,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:575 +#: 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:601 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." msgstr "Must be sketching in workplane to split." -#: modify.cpp:608 +#: 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)." @@ -1283,107 +1534,107 @@ msgstr "" "Select two entities that intersect each other (e.g. two lines/circles/arcs " "or a line/circle/arc and a point)." -#: modify.cpp:713 +#: modify.cpp:734 msgid "Can't split; no intersection found." msgstr "Can't split; no intersection found." -#: mouse.cpp:522 +#: mouse.cpp:558 +msgid "Assign to Style" +msgstr "Assign to Style" + +#: mouse.cpp:574 msgid "No Style" msgstr "No Style" -#: mouse.cpp:523 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." msgstr "Newly Created Custom Style..." -#: mouse.cpp:571 -msgid "Assign to Style" -msgstr "Assign to Style" - -#: mouse.cpp:574 +#: mouse.cpp:584 msgid "Group Info" msgstr "Group Info" -#: mouse.cpp:577 +#: mouse.cpp:604 msgid "Style Info" msgstr "Style Info" -#: mouse.cpp:580 +#: mouse.cpp:624 msgid "Select Edge Chain" msgstr "Select Edge Chain" -#: mouse.cpp:585 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" msgstr "Toggle Reference Dimension" -#: mouse.cpp:591 +#: mouse.cpp:636 msgid "Other Supplementary Angle" msgstr "Other Supplementary Angle" -#: mouse.cpp:596 +#: mouse.cpp:641 msgid "Snap to Grid" msgstr "Snap to Grid" -#: mouse.cpp:604 +#: mouse.cpp:650 msgid "Remove Spline Point" msgstr "Remove Spline Point" -#: mouse.cpp:615 +#: mouse.cpp:685 msgid "Add Spline Point" msgstr "Add Spline Point" -#: mouse.cpp:619 +#: 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:714 msgid "Toggle Construction" msgstr "Toggle Construction" -#: mouse.cpp:633 +#: mouse.cpp:730 msgid "Delete Point-Coincident Constraint" msgstr "Delete Point-Coincident Constraint" -#: mouse.cpp:639 +#: mouse.cpp:748 msgid "Cut" msgstr "Cut" -#: mouse.cpp:640 +#: mouse.cpp:750 msgid "Copy" msgstr "Copy" -#: mouse.cpp:643 +#: mouse.cpp:754 msgid "Select All" msgstr "Select All" -#: mouse.cpp:647 +#: mouse.cpp:759 msgid "Paste" msgstr "Paste" -#: mouse.cpp:648 +#: mouse.cpp:761 msgid "Paste Transformed..." msgstr "Paste Transformed..." -#: mouse.cpp:652 +#: mouse.cpp:766 msgid "Delete" msgstr "Delete" -#: mouse.cpp:654 +#: mouse.cpp:769 msgid "Unselect All" msgstr "Unselect All" -#: mouse.cpp:660 +#: mouse.cpp:776 msgid "Unselect Hovered" msgstr "Unselect Hovered" -#: mouse.cpp:665 +#: mouse.cpp:785 msgid "Zoom to Fit" msgstr "Zoom to Fit" -#: mouse.cpp:805 -msgid "Cannot add spline point: maximum number of points reached." -msgstr "Cannot add spline point: maximum number of points reached." - -#: mouse.cpp:1009 mouse.cpp:1292 +#: 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:1015 +#: mouse.cpp:993 msgid "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." @@ -1391,15 +1642,15 @@ msgstr "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." -#: mouse.cpp:1049 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" msgstr "click to place other corner of rectangle" -#: mouse.cpp:1069 +#: mouse.cpp:1048 msgid "click to set radius" msgstr "click to set radius" -#: mouse.cpp:1074 +#: mouse.cpp:1053 msgid "" "Can't draw arc in 3d; first, activate a workplane with Sketch -> In " "Workplane." @@ -1407,21 +1658,21 @@ msgstr "" "Can't draw arc in 3d; first, activate a workplane with Sketch -> In " "Workplane." -#: mouse.cpp:1093 +#: mouse.cpp:1072 msgid "click to place point" msgstr "click to place point" -#: mouse.cpp:1109 +#: mouse.cpp:1088 msgid "click next point of cubic, or press Esc" msgstr "click next point of cubic, or press Esc" -#: mouse.cpp:1114 +#: mouse.cpp:1093 msgid "" "Sketching in a workplane already; sketch in 3d before creating new workplane." msgstr "" "Sketching in a workplane already; sketch in 3d before creating new workplane." -#: mouse.cpp:1130 +#: mouse.cpp:1109 msgid "" "Can't draw text in 3d; first, activate a workplane with Sketch -> In " "Workplane." @@ -1429,11 +1680,11 @@ msgstr "" "Can't draw text in 3d; first, activate a workplane with Sketch -> In " "Workplane." -#: mouse.cpp:1146 -msgid "click to place bottom left of text" -msgstr "click to place bottom left of text" +#: mouse.cpp:1126 +msgid "click to place bottom right of text" +msgstr "click to place bottom right of text" -#: mouse.cpp:1152 +#: mouse.cpp:1132 msgid "" "Can't draw image in 3d; first, activate a workplane with Sketch -> In " "Workplane." @@ -1441,196 +1692,424 @@ msgstr "" "Can't draw image in 3d; first, activate a workplane with Sketch -> In " "Workplane." -#: mouse.cpp:1178 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" -msgstr "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "SolveSpace models" -#: platform/cocoamain.mm:481 platform/gtkmain.cpp:607 platform/w32main.cpp:451 -#: platform/w32main.cpp:1388 -msgctxt "title" -msgid "(new sketch)" -msgstr "(new sketch)" +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "ALL" -#: platform/cocoamain.mm:710 platform/gtkmain.cpp:912 platform/w32main.cpp:1307 -msgid "(no recent files)" -msgstr "(no recent files)" +#: platform/gui.cpp:91 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "IDF circuit board" + +#: 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:100 +msgctxt "file-type" +msgid "STL mesh" +msgstr "STL mesh" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Wavefront OBJ mesh" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "Three.js-compatible mesh, with viewer" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "Three.js-compatible mesh, mesh only" + +#: platform/gui.cpp:104 +msgctxt "file-type" +msgid "VRML text file" +msgstr "VRML text file" + +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "STEP file" +msgstr "STEP file" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "PDF file" +msgstr "PDF file" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "Encapsulated PostScript" + +#: platform/gui.cpp:114 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "Scalable Vector Graphics" + +#: platform/gui.cpp:116 platform/gui.cpp:123 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "DXF file (AutoCAD 2007)" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "HPGL file" +msgstr "HPGL file" + +#: 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 "AutoCAD DXF and DWG files" + +#: platform/gui.cpp:131 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "Comma-separated values" -#: platform/cocoamain.mm:828 platform/gtkmain.cpp:1020 +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" msgstr "untitled" -#: platform/cocoamain.mm:860 -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?" +#: 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/cocoamain.mm:862 -msgid "Your changes will be lost if you don't save them." -msgstr "Your changes will be lost if you don't save them." +#: 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/cocoamain.mm:863 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" -msgid "Save" -msgstr "Save" +msgid "_Cancel" +msgstr "_Cancel" -#: platform/cocoamain.mm:864 platform/cocoamain.mm:905 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" -msgid "Cancel" -msgstr "Cancel" +msgid "_Save" +msgstr "_Save" -#: platform/cocoamain.mm:865 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" -msgid "Don't Save" -msgstr "Don't Save" +msgid "_Open" +msgstr "_Open" + +#: solvespace.cpp:175 +msgctxt "title" +msgid "Autosave Available" +msgstr "Autosave Available" -#: platform/cocoamain.mm:880 -msgid "An autosave file is available for this project." -msgstr "An autosave file is available for this project." +#: solvespace.cpp:176 +msgctxt "dialog" +msgid "An autosave file is available for this sketch." +msgstr "An autosave file is available for this sketch." -#: platform/cocoamain.mm:882 +#: 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?" -#: platform/cocoamain.mm:883 +#: solvespace.cpp:178 msgctxt "button" -msgid "Load" -msgstr "Load" +msgid "&Load autosave" +msgstr "&Load autosave" -#: platform/cocoamain.mm:884 +#: solvespace.cpp:180 msgctxt "button" -msgid "Don't Load" -msgstr "Don't Load" +msgid "Do&n't Load" +msgstr "Do&n't Load" -#: platform/cocoamain.mm:900 -msgid "" -"Do you want to locate it manually?\n" -"If you select “No”, any geometry that depends on the missing file will be " -"removed." -msgstr "" -"Do you want to locate it manually?\n" -"If you select “No”, any geometry that depends on the missing file will be " -"removed." +#: solvespace.cpp:640 +msgctxt "title" +msgid "Modified File" +msgstr "Modified File" -#: platform/cocoamain.mm:903 -msgctxt "button" -msgid "Yes" -msgstr "Yes" +#: 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: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?" -#: platform/cocoamain.mm:906 +#: 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:649 msgctxt "button" -msgid "No" -msgstr "No" +msgid "&Save" +msgstr "&Save" -#: platform/cocoamain.mm:1126 platform/w32main.cpp:183 +#: solvespace.cpp:651 msgctxt "button" -msgid "OK" -msgstr "OK" +msgid "Do&n't Save" +msgstr "Do&n't Save" -#: platform/cocoamain.mm:1211 platform/gtkmain.cpp:1382 -#: platform/w32main.cpp:1410 platform/w32main.cpp:1450 +#: solvespace.cpp:672 +msgctxt "title" +msgid "(new sketch)" +msgstr "(new sketch)" + +#: solvespace.cpp:683 msgctxt "title" msgid "Property Browser" msgstr "Property Browser" -#: platform/gtkmain.cpp:968 -msgctxt "title" -msgid "Open File" -msgstr "Open File" +#: 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 "" +"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." -#: platform/gtkmain.cpp:970 -msgid "_Cancel" -msgstr "_Cancel" +#: solvespace.cpp:834 +#, c-format +msgid "" +"Can't identify file type from file extension of filename '%s'; try .dxf or ." +"dwg." +msgstr "" +"Can't identify file type from file extension of filename '%s'; try .dxf or ." +"dwg." -#: platform/gtkmain.cpp:971 -msgid "_Open" -msgstr "_Open" +#: 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." -#: platform/gtkmain.cpp:1010 -msgctxt "title" -msgid "Save File" -msgstr "Save File" +#: solvespace.cpp:890 +msgid "Bad selection for step dimension; select a constraint." +msgstr "Bad selection for step dimension; select a constraint." -#: platform/gtkmain.cpp:1013 platform/gtkmain.cpp:1049 -#: platform/gtkmain.cpp:1097 -msgctxt "button" -msgid "_Cancel" -msgstr "_Cancel" +#: solvespace.cpp:914 +msgid "The assembly does not interfere, good." +msgstr "The assembly does not interfere, good." -#: platform/gtkmain.cpp:1014 platform/gtkmain.cpp:1047 -msgctxt "button" -msgid "_Save" -msgstr "_Save" +#: solvespace.cpp:930 +#, c-format +msgid "" +"The volume of the solid model is:\n" +"\n" +" %s" +msgstr "" +"The volume of the solid model is:\n" +"\n" +" %s" + +#: solvespace.cpp:939 +#, c-format +msgid "" +"\n" +"The volume of current group mesh is:\n" +"\n" +" %s" +msgstr "" +"\n" +"The volume of current group mesh is:\n" +"\n" +" %s" -#: platform/gtkmain.cpp:1042 platform/w32main.cpp:1167 +#: solvespace.cpp:944 msgid "" -"The file has changed since it was last saved.\n" "\n" -"Do you want to save the changes?" +"\n" +"Curved surfaces have been approximated as triangles.\n" +"This introduces error, typically of around 1%." msgstr "" -"The file has changed since it was last saved.\n" "\n" -"Do you want to save the changes?" +"\n" +"Curved surfaces have been approximated as triangles.\n" +"This introduces error, typically of around 1%." -#: platform/gtkmain.cpp:1046 platform/w32main.cpp:1169 -msgctxt "title" -msgid "Modified File" -msgstr "Modified File" +#: 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 "" +"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%%." -#: platform/gtkmain.cpp:1048 -msgctxt "button" -msgid "Do_n't Save" -msgstr "Do_n't Save" +#: solvespace.cpp:968 +msgid "" +"This group does not contain a correctly-formed 2d closed area. It is open, " +"not coplanar, or self-intersecting." +msgstr "" +"This group does not contain a correctly-formed 2d closed area. It is open, " +"not coplanar, or self-intersecting." -#: platform/gtkmain.cpp:1066 platform/w32main.cpp:1193 +#: solvespace.cpp:980 +#, c-format msgid "" -"An autosave file is available for this project.\n" +"The area of the region sketched in this group is:\n" +"\n" +" %s\n" "\n" -"Do you want to load the autosave file instead?" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." msgstr "" -"An autosave file is available for this project.\n" +"The area of the region sketched in this group is:\n" "\n" -"Do you want to load the autosave file instead?" +" %s\n" +"\n" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." -#: platform/gtkmain.cpp:1070 platform/w32main.cpp:1195 -msgctxt "title" -msgid "Autosave Available" -msgstr "Autosave Available" +#: 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 "" +"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%%." -#: platform/gtkmain.cpp:1071 -msgctxt "button" -msgid "_Load autosave" -msgstr "_Load autosave" +#: 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." -#: platform/gtkmain.cpp:1072 -msgctxt "button" -msgid "Do_n't Load" -msgstr "Do_n't Load" +#: solvespace.cpp:1022 +msgid "Bad selection for trace; select a single point." +msgstr "Bad selection for trace; select a single point." -#: platform/gtkmain.cpp:1093 platform/w32main.cpp:1223 -msgctxt "title" -msgid "Missing File" -msgstr "Missing File" +#: solvespace.cpp:1049 +#, c-format +msgid "Couldn't write to '%s'" +msgstr "Couldn't write to '%s'" -#: platform/gtkmain.cpp:1094 -msgctxt "button" -msgid "_Yes" -msgstr "_Yes" +#: solvespace.cpp:1079 +msgid "The mesh is self-intersecting (NOT okay, invalid)." +msgstr "The mesh is self-intersecting (NOT okay, invalid)." -#: platform/gtkmain.cpp:1095 -msgctxt "button" -msgid "_No" -msgstr "_No" +#: solvespace.cpp:1080 +msgid "The mesh is not self-intersecting (okay, valid)." +msgstr "The mesh is not self-intersecting (okay, valid)." -#: platform/gtkmain.cpp:1306 platform/w32main.cpp:179 -msgctxt "title" -msgid "Error" -msgstr "Error" +#: solvespace.cpp:1082 +msgid "The mesh has naked edges (NOT okay, invalid)." +msgstr "The mesh has naked edges (NOT okay, invalid)." -#: platform/gtkmain.cpp:1306 platform/w32main.cpp:179 -msgctxt "title" -msgid "Message" -msgstr "Message" +#: solvespace.cpp:1083 +msgid "The mesh is watertight (okay, valid)." +msgstr "The mesh is watertight (okay, valid)." + +#: solvespace.cpp:1086 +#, c-format +msgid "" +"\n" +"\n" +"The model contains %d triangles, from %d surfaces." +msgstr "" +"\n" +"\n" +"The model contains %d triangles, from %d surfaces." + +#: solvespace.cpp:1090 +#, c-format +msgid "" +"%s\n" +"\n" +"%s\n" +"\n" +"Zero problematic edges, good.%s" +msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"Zero problematic edges, good.%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 problematic edges, bad.%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 "" +"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" -#: style.cpp:160 +#: 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." @@ -1638,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:659 +#: style.cpp:735 msgid "Style name cannot be empty" msgstr "Style name cannot be empty" -#: textscreens.cpp:662 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." msgstr "Can't repeat fewer than 1 time." -#: textscreens.cpp:666 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." msgstr "Can't repeat more than 999 times." -#: textscreens.cpp:695 +#: textscreens.cpp:866 msgid "Group name cannot be empty" msgstr "Group name cannot be empty" -#: textscreens.cpp:739 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." msgstr "Opacity must be between zero and one." -#: textscreens.cpp:778 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "Radius cannot be zero or negative." @@ -1763,114 +2242,277 @@ msgid "New group rotating active sketch" msgstr "New group rotating active sketch" #: toolbar.cpp:72 +msgid "New group helix from active sketch" +msgstr "New group helix from active sketch" + +#: toolbar.cpp:74 +msgid "New group revolve active sketch" +msgstr "New group revolve active sketch" + +#: toolbar.cpp:76 msgid "New group step and repeat rotating" msgstr "New group step and repeat rotating" -#: toolbar.cpp:74 +#: toolbar.cpp:78 msgid "New group step and repeat translating" msgstr "New group step and repeat translating" -#: toolbar.cpp:76 +#: toolbar.cpp:80 msgid "New group in new workplane (thru given entities)" msgstr "New group in new workplane (thru given entities)" -#: toolbar.cpp:78 +#: toolbar.cpp:82 msgid "New group in 3d" msgstr "New group in 3d" -#: toolbar.cpp:80 +#: toolbar.cpp:84 msgid "New group linking / assembling file" msgstr "New group linking / assembling file" -#: toolbar.cpp:84 +#: toolbar.cpp:88 msgid "Nearest isometric view" msgstr "Nearest isometric view" -#: toolbar.cpp:86 +#: toolbar.cpp:90 msgid "Align view to active workplane" msgstr "Align view to active workplane" -#: ui.h:69 -msgid "SolveSpace models" -msgstr "SolveSpace models" +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "Error" -#: ui.h:74 -msgid "PNG file" -msgstr "PNG file" +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "Message" -#: ui.h:79 -msgid "STL mesh" -msgstr "STL mesh" +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "&OK" -#: ui.h:80 -msgid "Wavefront OBJ mesh" -msgstr "Wavefront OBJ mesh" +#: view.cpp:127 +msgid "Scale cannot be zero or negative." +msgstr "Scale cannot be zero or negative." -#: ui.h:81 -msgid "Three.js-compatible mesh, with viewer" -msgstr "Three.js-compatible mesh, with viewer" +#: view.cpp:139 view.cpp:148 +msgid "Bad format: specify x, y, z" +msgstr "Bad format: specify x, y, z" -#: ui.h:82 -msgid "Three.js-compatible mesh, mesh only" -msgstr "Three.js-compatible mesh, mesh only" +#~ 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." -#: ui.h:87 ui.h:95 ui.h:103 -msgid "STEP file" -msgstr "STEP file" +#~ 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." -#: ui.h:92 -msgid "PDF file" -msgstr "PDF file" +#~ 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." -#: ui.h:93 -msgid "Encapsulated PostScript" -msgstr "Encapsulated PostScript" +#~ 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" -#: ui.h:94 -msgid "Scalable Vector Graphics" -msgstr "Scalable Vector Graphics" +#~ msgid "&Workplane" +#~ msgstr "&Workplane" -#: ui.h:96 ui.h:104 -msgid "DXF file (AutoCAD 2007)" -msgstr "DXF file (AutoCAD 2007)" +#~ msgid "&Image" +#~ msgstr "&Image" -#: ui.h:97 -msgid "HPGL file" -msgstr "HPGL file" +#~ msgid "Tangent &Arc at Point" +#~ msgstr "Tangent &Arc at Point" -#: ui.h:98 -msgid "G Code" -msgstr "G Code" +#~ 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" -#: ui.h:109 -msgid "AutoCAD DXF and DWG files" -msgstr "AutoCAD DXF and DWG files" +#~ 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" -#: ui.h:114 -msgid "Comma-separated values" -msgstr "Comma-separated values" +#~ 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" -#: view.cpp:78 -msgid "Scale cannot be zero or negative." -msgstr "Scale cannot be zero or negative." +#~ 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" -#: view.cpp:90 view.cpp:99 -msgid "Bad format: specify x, y, z" -msgstr "Bad format: specify x, y, z" +#~ 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 "" -#~ "Select two entities that intersect each other (e.g. two lines or two " -#~ "circles or a circle and a line)." +#~ "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 "" -#~ "Select two entities that intersect each other (e.g. two lines or two " -#~ "circles or a circle and a line)." +#~ "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 "Show Menu &Bar" -#~ msgstr "Show Menu &Bar" +#~ msgid "E&qual Length / Radius / Angle" +#~ msgstr "E&qual Length / Radius / Angle" + +#~ msgid "&Mirror" +#~ msgstr "&Mirror" #~ msgctxt "group-name" -#~ msgid "link" -#~ msgstr "link" +#~ 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 "Scale must not be zero or negative!" -#~ msgstr "Scale must not be zero or negative!" +#~ 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 7bc48765c..4724dc01e 100644 --- a/res/locales/fr_FR.po +++ b/res/locales/fr_FR.po @@ -1,54 +1,53 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR the PACKAGE authors +# French translations for the SolveSpace package. +# Copyright (C) 2018 the SolveSpace authors # This file is distributed under the same license as the SolveSpace package. -# FIRST AUTHOR , YEAR. # whitequark , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: SolveSpace 3.0\n" -"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" -"POT-Creation-Date: 2018-07-12 22:40+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\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" -"X-Generator: Zanata 4.4.5\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 3.2.2\n" -#: clipboard.cpp:274 +#: 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:291 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." -msgstr "Presse papier vide; rien à coller." +msgstr "Presse papier vide ; rien à coller." -#: clipboard.cpp:338 +#: 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:354 textscreens.cpp:709 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "L'échelle ne peut pas être zéro." -#: clipboard.cpp:392 +#: 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:404 +#: 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:414 +#: clipboard.cpp:458 msgid "" "Transformation is identity. So all copies will be exactly on top of each " "other." @@ -56,51 +55,52 @@ msgstr "" "Transformation identique. Donc, toutes les copies seront exactement les unes " "au-dessus des autres." -#: clipboard.cpp:418 +#: 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:423 +#: clipboard.cpp:467 msgid "No workplane active." msgstr "Pas d'espace de travail actif." -#: confscreen.cpp:336 +#: 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:347 style.cpp:653 textscreens.cpp:730 +#: 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:372 +#: 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:386 -msgid "Specify between 0 and 8 digits after the decimal." -msgstr "Spécifiez entre 0 et 8 chiffres après la virgule." +#: confscreen.cpp:464 confscreen.cpp:474 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "Spécifier entre 0 et %d chiffres après la virgule." -#: confscreen.cpp:398 +#: 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:410 +#: 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:464 +#: 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:467 +#: 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,115 +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:160 +#: 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 "" +"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 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 "" +"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: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.\n" +"\n" +"Alternatively select the end points of both curves at which you want the " +"curves to be tangent." +msgstr "" +"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:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply " "to:\n" @@ -291,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" - -#: constraint.cpp:213 +" – 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: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:275 +#: 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:314 +#: 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:331 +#: constraint.cpp:515 msgid "" -"Bad selection for length difference constraint. This constraint can apply to:" -"\n" +"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:357 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" @@ -383,13 +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:415 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -401,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:429 +#: constraint.cpp:623 msgid "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." @@ -418,128 +525,122 @@ msgstr "" "Un plan de travail doit être actif lors d'une contrainte de symétrie sans " "plan de symétrie explicite." -#: constraint.cpp:459 +#: 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:472 +#: 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:493 +#: constraint.cpp:697 msgid "" -"Bad selection for same orientation constraint. This constraint can apply to:\n" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" "\n" " * 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:545 +#: constraint.cpp:748 msgid "Must select an angle constraint." msgstr "Vous devez sélectionner une contrainte d'angle." -#: constraint.cpp:557 +#: 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:568 +#: 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" -"    * Deux segments de ligne\n" -"    * Un segment de ligne et une normale\n" -"    * Deux normales\n" - -#: constraint.cpp:625 -msgid "" -"The tangent arc and line segment must share an endpoint. Constrain them with " -"Constrain -> On Point before constraining tangent." -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\"." - -#: constraint.cpp:649 -msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them " -"with Constrain -> On Point before constraining tangent." -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\"." +"Angle entre :\n" +" – Deux segments de ligne\n" +" – Un segment de ligne et une normale\n" +" – Deux normales\n" +"\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:659 +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." -msgstr "Courbe-Courbe tangence doit s'appliquer dans le plan de travail." - -#: constraint.cpp:677 -msgid "" -"The curves must share an endpoint. Constrain them with Constrain -> On Point " -"before constraining tangent." -msgstr "" -"Les courbes doivent partager un point final. Contraignez-les avec " -"\"Contrainte -> Sur point avant de contraindre la tangente\"." +msgstr "La tangence courbe-courbe doit s'appliquer dans le plan de travail." -#: constraint.cpp:686 +#: 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 line segments, arcs, or beziers, that share an endpoint " -"(tangent)\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" - -#: constraint.cpp:704 +" – 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: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:719 +#: constraint.cpp:931 msgid "" "Bad selection for lock point where dragged constraint. This constraint can " "apply to:\n" @@ -547,24 +648,28 @@ 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:730 +#: constraint.cpp:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "NOUVEAU COMMENTAIRE — DOUBLE-CLIQUEZ POUR EDITER" + +#: constraint.cpp:952 msgid "click center of comment text" msgstr "cliquez le centre du texte de commentaire" -#: export.cpp:18 +#: 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 "" -"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:60 +#: export.cpp:61 msgid "" "Bad selection for export section. Please select:\n" "\n" @@ -573,41 +678,41 @@ 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:805 +#: 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:590 +#: 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:807 +#: 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:95 +#: file.cpp:44 group.cpp:91 msgctxt "group-name" msgid "sketch-in-plane" msgstr "dessin-dans-plan" @@ -617,439 +722,527 @@ msgctxt "group-name" msgid "#references" msgstr "#références" -#: file.cpp:539 +#: file.cpp:555 +msgid "The file is empty. It may be corrupt." +msgstr "Le fichier est vide. Il est possible qu'il soit corrompu." + +#: 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:876 +msgctxt "title" +msgid "Missing File" +msgstr "Fichier manquant" + +#: file.cpp:877 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "Le fichier lié « %s » n'est pas présent." + +#: 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 "" +"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:882 +msgctxt "button" +msgid "&Yes" +msgstr "&Oui" + +#: file.cpp:884 +msgctxt "button" +msgid "&No" +msgstr "&Non" + +#: file.cpp:886 solvespace.cpp:652 +msgctxt "button" +msgid "&Cancel" +msgstr "&Annuler" -#: graphicswin.cpp:29 +#: graphicswin.cpp:41 msgid "&File" msgstr "&Fichier" -#: graphicswin.cpp:30 +#: graphicswin.cpp:42 msgid "&New" msgstr "&Nouveau" -#: graphicswin.cpp:31 +#: graphicswin.cpp:43 msgid "&Open..." msgstr "&Ouvrir..." -#: graphicswin.cpp:32 +#: graphicswin.cpp:44 msgid "Open &Recent" msgstr "Ouvrir &Récent" -#: graphicswin.cpp:33 +#: graphicswin.cpp:45 msgid "&Save" msgstr "&Sauver" -#: graphicswin.cpp:34 +#: graphicswin.cpp:46 msgid "Save &As..." msgstr "Sauver &Comme..." -#: graphicswin.cpp:36 +#: graphicswin.cpp:48 msgid "Export &Image..." msgstr "Exporter &Image..." -#: graphicswin.cpp:37 +#: graphicswin.cpp:49 msgid "Export 2d &View..." msgstr "Exporter &vue 2D..." -#: graphicswin.cpp:38 +#: graphicswin.cpp:50 msgid "Export 2d &Section..." msgstr "Exporter &Section 2d..." -#: graphicswin.cpp:39 +#: graphicswin.cpp:51 msgid "Export 3d &Wireframe..." msgstr "Exporter &Fil de fer 3d..." -#: graphicswin.cpp:40 +#: graphicswin.cpp:52 msgid "Export Triangle &Mesh..." msgstr "Exporter Triangle &Maillage..." -#: graphicswin.cpp:41 +#: graphicswin.cpp:53 msgid "Export &Surfaces..." msgstr "Export &Surfaces..." -#: graphicswin.cpp:42 +#: graphicswin.cpp:54 msgid "Im&port..." msgstr "Im&porter..." -#: graphicswin.cpp:45 +#: graphicswin.cpp:57 msgid "E&xit" msgstr "&Quitter" -#: graphicswin.cpp:48 +#: graphicswin.cpp:60 msgid "&Edit" msgstr "&Editer" -#: graphicswin.cpp:49 +#: graphicswin.cpp:61 msgid "&Undo" msgstr "&Annuler" -#: graphicswin.cpp:50 +#: graphicswin.cpp:62 msgid "&Redo" msgstr "&Refaire" -#: graphicswin.cpp:51 +#: graphicswin.cpp:63 msgid "Re&generate All" msgstr "Re&générer Tout" -#: graphicswin.cpp:53 +#: graphicswin.cpp:65 msgid "Snap Selection to &Grid" msgstr "Accrocher la sélection à la &Grille" -#: graphicswin.cpp:54 +#: graphicswin.cpp:66 msgid "Rotate Imported &90°" -msgstr "Rotation importation &90°" +msgstr "Tourner l'import de &90°" -#: graphicswin.cpp:56 +#: graphicswin.cpp:68 msgid "Cu&t" msgstr "Co&uper" -#: graphicswin.cpp:57 +#: graphicswin.cpp:69 msgid "&Copy" msgstr "&Copier" -#: graphicswin.cpp:58 +#: graphicswin.cpp:70 msgid "&Paste" msgstr "Co&ller" -#: graphicswin.cpp:59 +#: graphicswin.cpp:71 msgid "Paste &Transformed..." msgstr "Coller &Transformer..." -#: graphicswin.cpp:60 +#: graphicswin.cpp:72 msgid "&Delete" msgstr "&Effacer" -#: graphicswin.cpp:62 +#: graphicswin.cpp:74 msgid "Select &Edge Chain" msgstr "Sélectionner une Chaîne d'&Arêtes" -#: graphicswin.cpp:63 +#: graphicswin.cpp:75 msgid "Select &All" msgstr "Sélectionner &Tout" -#: graphicswin.cpp:64 +#: graphicswin.cpp:76 msgid "&Unselect All" msgstr "&Désélectionner Tout" -#: graphicswin.cpp:66 +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "&Styles de Ligne..." + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "&Affichage Perspective..." + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "Con&figuration..." + +#: graphicswin.cpp:84 msgid "&View" msgstr "&Affichage" -#: graphicswin.cpp:67 +#: graphicswin.cpp:85 msgid "Zoom &In" msgstr "Zoom &Avant" -#: graphicswin.cpp:68 +#: graphicswin.cpp:86 msgid "Zoom &Out" msgstr "Zoom A&rrière" -#: graphicswin.cpp:69 +#: graphicswin.cpp:87 msgid "Zoom To &Fit" msgstr "Zoom A&justé" -#: graphicswin.cpp:71 +#: graphicswin.cpp:89 msgid "Align View to &Workplane" msgstr "Aligner la vue au &Plan de travail" -#: graphicswin.cpp:72 +#: graphicswin.cpp:90 msgid "Nearest &Ortho View" msgstr "Vue &Orthogonale la plus proche" -#: graphicswin.cpp:73 +#: graphicswin.cpp:91 msgid "Nearest &Isometric View" msgstr "Vue &Isométrique la plus proche" -#: graphicswin.cpp:74 +#: graphicswin.cpp:92 msgid "&Center View At Point" msgstr "&Centrer la Vue sur le Point" -#: graphicswin.cpp:76 +#: graphicswin.cpp:94 msgid "Show Snap &Grid" msgstr "Afficher la &grille d'accrochage" -#: graphicswin.cpp:77 +#: graphicswin.cpp:95 +msgid "Darken Inactive Solids" +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:78 +#: 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:79 -msgid "Dimensions in &Inches" -msgstr "Dimensions en &Pouces" - -#: graphicswin.cpp:80 +#: graphicswin.cpp:99 msgid "Dimensions in &Millimeters" msgstr "Dimensions en &Millimètres" -#: graphicswin.cpp:81 +#: graphicswin.cpp:100 msgid "Dimensions in M&eters" msgstr "Dimensions en &Mètres" -#: graphicswin.cpp:83 +#: 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:84 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" msgstr "Affichage du &Navigateur de Propriété" -#: graphicswin.cpp:86 +#: graphicswin.cpp:107 msgid "&Full Screen" msgstr "&Plein Ecran" -#: graphicswin.cpp:88 +#: graphicswin.cpp:109 msgid "&New Group" msgstr "&Nouveau Groupe" -#: graphicswin.cpp:89 +#: graphicswin.cpp:110 msgid "Sketch In &3d" msgstr "Dessin en &3d" -#: graphicswin.cpp:90 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" msgstr "Dessin dans un nouveau &Plan de travail" -#: graphicswin.cpp:92 +#: graphicswin.cpp:113 msgid "Step &Translating" -msgstr "Espacement &Linéaire" +msgstr "Répéter par &Translation" -#: graphicswin.cpp:93 +#: graphicswin.cpp:114 msgid "Step &Rotating" -msgstr "Espacement &Circulaire" +msgstr "Répéter par &Rotation" -#: graphicswin.cpp:95 +#: graphicswin.cpp:116 msgid "E&xtrude" msgstr "E&xtruder" -#: graphicswin.cpp:96 +#: graphicswin.cpp:117 +msgid "&Helix" +msgstr "&Hélice" + +#: graphicswin.cpp:118 msgid "&Lathe" -msgstr "&Révolution" +msgstr "&Tour (révolution complète)" -#: graphicswin.cpp:98 +#: graphicswin.cpp:119 +msgid "Re&volve" +msgstr "Ré&volution" + +#: graphicswin.cpp:121 msgid "Link / Assemble..." -msgstr "Lié / Assembler..." +msgstr "Lier / Assembler..." -#: graphicswin.cpp:99 +#: graphicswin.cpp:122 msgid "Link Recent" -msgstr "Lié Récent" +msgstr "Lier Récent" -#: graphicswin.cpp:101 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "&Dessin" -#: graphicswin.cpp:102 +#: graphicswin.cpp:125 msgid "In &Workplane" msgstr "Dans le &Plan de travail" -#: graphicswin.cpp:103 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" msgstr "N'importe où dans la &3d" -#: graphicswin.cpp:105 +#: graphicswin.cpp:128 msgid "Datum &Point" msgstr "&Point" -#: graphicswin.cpp:106 -msgid "&Workplane" +#: graphicswin.cpp:129 +msgid "Wor&kplane" msgstr "&Plan de travail" -#: graphicswin.cpp:108 +#: graphicswin.cpp:131 msgid "Line &Segment" msgstr "Ligne - &Polyligne" -#: graphicswin.cpp:109 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" msgstr "Ligne de C&onstruction" -#: graphicswin.cpp:110 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "&Rectangle" -#: graphicswin.cpp:111 +#: graphicswin.cpp:134 msgid "&Circle" msgstr "&Cercle" -#: graphicswin.cpp:112 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" msgstr "&Arc de Cercle" -#: graphicswin.cpp:113 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" -msgstr "Spline Cubique de &Beziers" +msgstr "Spline Cubique de &Bézier" -#: graphicswin.cpp:115 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" msgstr "&Texte en Police TrueType" -#: graphicswin.cpp:116 -msgid "&Image" -msgstr "&Image" +#: graphicswin.cpp:139 +msgid "I&mage" +msgstr "I&mage" -#: graphicswin.cpp:118 +#: graphicswin.cpp:141 msgid "To&ggle Construction" -msgstr "&Basculer en mode \"Construction\"" +msgstr "&Basculer en mode « Construction »" -#: graphicswin.cpp:119 -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:120 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" msgstr "Diviser les Courbes à l'&Intersection" -#: graphicswin.cpp:122 +#: graphicswin.cpp:145 msgid "&Constrain" -msgstr "&Constraintes" +msgstr "&Contraintes" -#: graphicswin.cpp:123 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "&Distance / Diamètre" -#: graphicswin.cpp:124 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" msgstr "Dimension Maîtresse / Indicative" -#: graphicswin.cpp:125 -msgid "A&ngle" -msgstr "A&ngle" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "A&ngle / Égalité Angle" -#: graphicswin.cpp:126 +#: graphicswin.cpp:149 msgid "Reference An&gle" msgstr "An&gle Maître / Indicatif" -#: graphicswin.cpp:127 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" msgstr "Autre angle S&upplémentaire" -#: graphicswin.cpp:128 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" msgstr "Basculer cote Maîtresse / cote Indicative" -#: graphicswin.cpp:130 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "&Horizontal" -#: graphicswin.cpp:131 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "&Vertical" -#: graphicswin.cpp:133 +#: graphicswin.cpp:156 msgid "&On Point / Curve / Plane" msgstr "&Sur Point / Courbe / Plan" -#: graphicswin.cpp:134 -msgid "E&qual Length / Radius / Angle" -msgstr "&Egale Longueur / Rayon / Angle" +#: graphicswin.cpp:157 +msgid "E&qual Length / Radius" +msgstr "É&galité Longueur / Rayon" -#: graphicswin.cpp:135 -msgid "Length Ra&tio" -msgstr "R&apport de Longueur" +#: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "Ratio Longueur / Arc" -#: graphicswin.cpp:136 -msgid "Length Diff&erence" -msgstr "D&ifférence de Longueur" +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Différence Longueur / Arc" -#: graphicswin.cpp:137 +#: graphicswin.cpp:160 msgid "At &Midpoint" msgstr "Au &Milieu" -#: graphicswin.cpp:138 +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "&Symétrique" -#: graphicswin.cpp:139 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "Para&llèle / Tangent" -#: graphicswin.cpp:140 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "&Perpendiculaire" -#: graphicswin.cpp:141 +#: graphicswin.cpp:164 msgid "Same Orient&ation" msgstr "Même Orient&ation" -#: graphicswin.cpp:142 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "Accrocher le point à l'&Emplacement" -#: graphicswin.cpp:144 +#: graphicswin.cpp:167 msgid "Comment" msgstr "Commentaire" -#: graphicswin.cpp:146 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "&Analyse" -#: graphicswin.cpp:147 +#: graphicswin.cpp:170 msgid "Measure &Volume" -msgstr "Mesure &Volume" +msgstr "Mesurer &Volume" -#: graphicswin.cpp:148 +#: graphicswin.cpp:171 msgid "Measure A&rea" -msgstr "Mesure &Aire" +msgstr "Mesurer &Aire" -#: graphicswin.cpp:149 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" -msgstr "Mesure &Périmètre" +msgstr "Mesurer &Périmètre" -#: graphicswin.cpp:150 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" msgstr "Montrer les Pièces &Interférentes" -#: graphicswin.cpp:151 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" msgstr "Montrer les Arêtes &Nues" -#: graphicswin.cpp:152 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" msgstr "Montrer le &Centre de Gravité" -#: graphicswin.cpp:154 -msgid "Show Degrees of &Freedom" -msgstr "Montrer les Degrés de &Liberté" +#: graphicswin.cpp:177 +msgid "Show &Underconstrained Points" +msgstr "Montrer les &sous-contraintes Points" -#: graphicswin.cpp:156 +#: graphicswin.cpp:179 msgid "&Trace Point" msgstr "&Tracer Point" -#: graphicswin.cpp:157 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." -msgstr "&Arrêt Tracé..." +msgstr "&Arrêter Tracé..." -#: graphicswin.cpp:158 +#: graphicswin.cpp:181 msgid "Step &Dimension..." -msgstr "Espacement &Dimension..." +msgstr "&Dimension pas-à-pas..." -#: graphicswin.cpp:160 +#: graphicswin.cpp:183 msgid "&Help" msgstr "&Aide" -#: graphicswin.cpp:161 +#: graphicswin.cpp:184 +msgid "&Language" +msgstr "&Langue" + +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "&Site web / Manuel" -#: graphicswin.cpp:162 -msgid "&Language" -msgstr "&Langue" +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "Voir le commit sur &GitHub" -#: graphicswin.cpp:164 +#: graphicswin.cpp:188 msgid "&About" msgstr "&A propos" -#: graphicswin.cpp:491 +#: graphicswin.cpp:362 +msgid "(no recent files)" +msgstr "(pas de fichier récent)" + +#: graphicswin.cpp:370 +#, c-format +msgid "File '%s' does not exist." +msgstr "Le fichier « %s » n'existe pas." + +#: 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:500 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel " "projection.\n" @@ -1063,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:581 +#: 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:862 +#: 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:882 +#: 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." @@ -1083,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:906 +#: 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:913 +#: 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:979 +#: 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:984 +#: 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-" @@ -1114,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:1008 +#: graphicswin.cpp:1350 msgid "" "Bad selection for tangent arc at point. Select a single point, or select " "nothing to set up arc parameters." @@ -1124,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:1019 +#: 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:1020 +#: graphicswin.cpp:1362 msgid "click to place datum point" msgstr "cliquez pour placer un point" -#: graphicswin.cpp:1021 +#: graphicswin.cpp:1363 msgid "click first point of line segment" msgstr "cliquez le premier point du segment de ligne" -#: graphicswin.cpp:1023 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" msgstr "cliquez le premier point de la ligne de construction" -#: graphicswin.cpp:1024 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" msgstr "cliquez le premier point du segment cubique" -#: graphicswin.cpp:1025 +#: graphicswin.cpp:1367 msgid "click center of circle" msgstr "cliquez pour placer le centre du cercle" -#: graphicswin.cpp:1026 +#: graphicswin.cpp:1368 msgid "click origin of workplane" msgstr "cliquez pour placer l'origine du plan de travail" -#: graphicswin.cpp:1027 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" msgstr "cliquez un coin du rectangle" -#: graphicswin.cpp:1028 +#: graphicswin.cpp:1370 msgid "click top left of text" msgstr "cliquez le haut à gauche du texte" -#: graphicswin.cpp:1034 +#: graphicswin.cpp:1376 msgid "click top left of image" msgstr "cliquez le haut à gauche de l'image" -#: graphicswin.cpp:1047 +#: graphicswin.cpp:1402 msgid "" "No entities are selected. Select entities before trying to toggle their " "construction state." @@ -1174,42 +1365,48 @@ msgstr "" "Aucune entité n'est sélectionnée. Sélectionnez les entités avant d'essayer " "de basculer leurs états de construction." -#: group.cpp:90 +#: group.cpp:86 msgctxt "group-name" msgid "sketch-in-3d" msgstr "dessin-en-3d" -#: group.cpp:146 +#: 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" - -#: group.cpp:158 +" – 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: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:167 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" msgstr "extruder" -#: group.cpp:179 +#: group.cpp:184 +msgid "Lathe operation can only be applied to planar sketches." +msgstr "L'opération tour ne peut être appliquée qu'à des dessins plans." + +#: group.cpp:195 msgid "" "Bad selection for new lathe group. This group can be created with:\n" "\n" @@ -1218,76 +1415,129 @@ 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 "tour" + +#: group.cpp:210 +msgid "Revolve operation can only be applied to planar sketches." +msgstr "L'opération révolution ne peut être appliquée qu'à des dessins plans." + +#: 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 "" +"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:233 +msgctxt "group-name" +msgid "revolve" msgstr "révolution" -#: group.cpp:202 +#: group.cpp:238 +msgid "Helix operation can only be applied to planar sketches." +msgstr "L'opération hélice ne peut être appliquée qu'à des dessins plans." + +#: 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 "" +"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: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, 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 "" -"Mauvaise sélection pour une nouvelle rotation. Ce groupe peut être créé avec:" -"\n" +"Mauvaise sélection pour une nouvelle rotation. Ce groupe peut être créé " +"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:215 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" msgstr "rotation" -#: group.cpp:226 +#: group.cpp:298 msgctxt "group-name" msgid "translate" msgstr "translation" -#: group.cpp:340 +#: group.cpp:422 msgid "(unnamed)" msgstr "(sans nom)" -#: groupmesh.cpp:626 +#: 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:639 +#: 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:641 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" -msgstr "le contour s'entrecroise!" +msgstr "le contour s'entrecroise !" -#: groupmesh.cpp:643 +#: groupmesh.cpp:727 msgid "zero-length edge!" -msgstr "arête de longueur nulle!" +msgstr "arête de longueur nulle !" -#: modify.cpp:237 +#: 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: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:284 +#: 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:371 +#: modify.cpp:386 msgid "" "Couldn't round this corner. Try a smaller radius, or try creating the " "desired geometry by hand with tangency constraints." @@ -1295,398 +1545,636 @@ 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:575 +#: 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:601 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." msgstr "Vous devez dessiner dans un plan de travail pour diviser." -#: modify.cpp:608 +#: 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:713 +#: 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:522 +#: mouse.cpp:558 +msgid "Assign to Style" +msgstr "Appliquer au style" + +#: mouse.cpp:574 msgid "No Style" msgstr "Pas de style" -#: mouse.cpp:523 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." msgstr "Style personnalisé nouvellement créé ..." -#: mouse.cpp:571 -msgid "Assign to Style" -msgstr "Appliquer au style" - -#: mouse.cpp:574 +#: mouse.cpp:584 msgid "Group Info" msgstr "Info Groupe" -#: mouse.cpp:577 +#: mouse.cpp:604 msgid "Style Info" msgstr "Info Style" -#: mouse.cpp:580 +#: mouse.cpp:624 msgid "Select Edge Chain" msgstr "Sélection Chaîne d'arêtes" -#: mouse.cpp:585 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" msgstr "Basculer cote maîtresse / cote indicative" -#: mouse.cpp:591 +#: mouse.cpp:636 msgid "Other Supplementary Angle" msgstr "Autre angle supplémentaire" -#: mouse.cpp:596 +#: mouse.cpp:641 msgid "Snap to Grid" msgstr "Accrocher à la grille" -#: mouse.cpp:604 +#: mouse.cpp:650 msgid "Remove Spline Point" msgstr "Effacer le point de la Spline" -#: mouse.cpp:615 +#: mouse.cpp:685 msgid "Add Spline Point" msgstr "Ajouter un point à la Spline" -#: mouse.cpp:619 +#: 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." + +#: mouse.cpp:714 msgid "Toggle Construction" -msgstr "Basculer en mode \"construction\"." +msgstr "Basculer en mode « construction »" -#: mouse.cpp:633 +#: mouse.cpp:730 msgid "Delete Point-Coincident Constraint" msgstr "Effacer la contraint Point-Coïncident" -#: mouse.cpp:639 +#: mouse.cpp:748 msgid "Cut" msgstr "Couper" -#: mouse.cpp:640 +#: mouse.cpp:750 msgid "Copy" msgstr "Copier" -#: mouse.cpp:643 +#: mouse.cpp:754 msgid "Select All" msgstr "Sélectionner tout" -#: mouse.cpp:647 +#: mouse.cpp:759 msgid "Paste" msgstr "Coller" -#: mouse.cpp:648 +#: mouse.cpp:761 msgid "Paste Transformed..." msgstr "Coller transformé..." -#: mouse.cpp:652 +#: mouse.cpp:766 msgid "Delete" msgstr "Effacer" -#: mouse.cpp:654 +#: mouse.cpp:769 msgid "Unselect All" msgstr "Désélectionner tout" -#: mouse.cpp:660 +#: mouse.cpp:776 msgid "Unselect Hovered" msgstr "Désélectionner survolé" -#: mouse.cpp:665 +#: mouse.cpp:785 msgid "Zoom to Fit" msgstr "Zoom pour ajuster" -#: mouse.cpp:805 -msgid "Cannot add spline point: maximum number of points reached." -msgstr "" -"Impossible d'ajouter le point spline: nombre maximum de points atteints." - -#: mouse.cpp:1009 mouse.cpp:1292 +#: 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:1015 +#: 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:1049 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" msgstr "cliquez pour placer un autre coin de rectangle" -#: mouse.cpp:1069 +#: mouse.cpp:1048 msgid "click to set radius" msgstr "cliquez pour ajuster le rayon" -#: mouse.cpp:1074 +#: mouse.cpp:1053 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:1093 +#: mouse.cpp:1072 msgid "click to place point" msgstr "cliquez pour placer un point" -#: mouse.cpp:1109 +#: mouse.cpp:1088 msgid "click next point of cubic, or press Esc" msgstr "cliquez le prochain point cubique ou appuyez sur Esc" -#: mouse.cpp:1114 +#: mouse.cpp:1093 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:1130 +#: mouse.cpp:1109 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:1146 -msgid "click to place bottom left of text" -msgstr "cliquez pour placer le bas gauche du texte" +#: mouse.cpp:1126 +msgid "click to place bottom right of text" +msgstr "cliquer pour placer le coin inférieur droit du texte" -#: mouse.cpp:1152 +#: 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\"." +"Impossible de dessiner l'image en 3d ; D'abord, activez un plan de travail " +"avec « Dessin -> Dans le plan de travail »." -#: mouse.cpp:1178 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" -msgstr "NOUVEAU COMMENTAIRE - DOUBLE-CLIQUE POUR EDITER" +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "modèles SolveSpace" + +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" +msgstr "TOUT" + +#: platform/gui.cpp:91 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "circuit imprimé IDF" + +#: platform/gui.cpp:92 +msgctxt "file-type" +msgid "STL triangle mesh" +msgstr "maillage de triangles STL" + +#: platform/gui.cpp:96 +msgctxt "file-type" +msgid "PNG image" +msgstr "image PNG" + +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "STL mesh" +msgstr "maillage STL" -#: platform/cocoamain.mm:481 platform/gtkmain.cpp:607 platform/w32main.cpp:451 -#: platform/w32main.cpp:1388 -msgctxt "title" -msgid "(new sketch)" -msgstr "(nouveau dessin)" +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Mesh Wavefront OBJ" -#: platform/cocoamain.mm:710 platform/gtkmain.cpp:912 platform/w32main.cpp:1307 -msgid "(no recent files)" -msgstr "(pas de fichier récent)" +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "Mesh compatible avec Three.js, avec visionneuse" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "Mesh compatible avec Three.js, mesh seulement" + +#: platform/gui.cpp:104 +msgctxt "file-type" +msgid "VRML text file" +msgstr "Fichier texte VRML" + +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "STEP file" +msgstr "fichier STEP" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "PDF file" +msgstr "fichier PDF" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "PostScript encapsulé" + +#: platform/gui.cpp:114 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "image vectorielle SVG" + +#: platform/gui.cpp:116 platform/gui.cpp:123 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "fichier DXF (AutoCAD 2007)" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "HPGL file" +msgstr "fichier 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 "fichiers AutoCAD DXF et DWG" + +#: platform/gui.cpp:131 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "CSV (Comma-separated values)" -#: platform/cocoamain.mm:828 platform/gtkmain.cpp:1020 +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" msgstr "sans nom" -#: platform/cocoamain.mm:860 -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?" +#: 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/cocoamain.mm:862 -msgid "Your changes will be lost if you don't save them." -msgstr "Vos modifications seront perdues si vous ne les enregistrez pas." +#: 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/cocoamain.mm:863 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" -msgid "Save" -msgstr "Sauver" +msgid "_Cancel" +msgstr "_Annuler" -#: platform/cocoamain.mm:864 platform/cocoamain.mm:905 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" -msgid "Cancel" -msgstr "Annuler" +msgid "_Save" +msgstr "_Sauver" -#: platform/cocoamain.mm:865 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" -msgid "Don't Save" -msgstr "Ne pas sauver" +msgid "_Open" +msgstr "_Ouvrir" -#: platform/cocoamain.mm:880 -msgid "An autosave file is available for this project." -msgstr "" +#: solvespace.cpp:175 +msgctxt "title" +msgid "Autosave Available" +msgstr "Sauvegarde automatique existante" + +#: solvespace.cpp:176 +msgctxt "dialog" +msgid "An autosave file is available for this sketch." +msgstr "Un fichier de sauvegarde automatique est disponible pour ce dessin." -#: platform/cocoamain.mm:882 +#: solvespace.cpp:177 +msgctxt "dialog" msgid "Do you want to load the autosave file instead?" -msgstr "Voulez-vous charger le fichier de sauvegarde à la place?" +msgstr "Voulez-vous charger la sauvegarde automatique à la place ?" -#: platform/cocoamain.mm:883 +#: solvespace.cpp:178 msgctxt "button" -msgid "Load" -msgstr "Charger" +msgid "&Load autosave" +msgstr "&Charger la sauvegarde automatique" -#: platform/cocoamain.mm:884 +#: solvespace.cpp:180 msgctxt "button" -msgid "Don't Load" -msgstr "Ne pas charger" +msgid "Do&n't Load" +msgstr "Ne &pas la charger" -#: platform/cocoamain.mm:900 -msgid "" -"Do you want to locate it manually?\n" -"If you select “No”, any geometry that depends on the missing file will be " -"removed." +#: solvespace.cpp:640 +msgctxt "title" +msgid "Modified File" +msgstr "Fichier modifié" + +#: solvespace.cpp:642 +#, c-format +msgctxt "dialog" +msgid "Do you want to save the changes you made to the sketch “%s”?" msgstr "" -"Voulez-vous le localiser manuellement?\n" -"Si vous sélectionnez \"Non\", toute géométrie qui dépend du fichier manquant " -"sera supprimée." +"Voulez-vous sauver les changements que vous avez fait au dessin « %s » ?" -#: platform/cocoamain.mm:903 -msgctxt "button" -msgid "Yes" -msgstr "Oui" +#: 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 ?" -#: platform/cocoamain.mm:906 +#: solvespace.cpp:648 +msgctxt "dialog" +msgid "Your changes will be lost if you don't save them." +msgstr "Vos changements seront perdus si vous ne les sauvez pas." + +#: solvespace.cpp:649 msgctxt "button" -msgid "No" -msgstr "Non" +msgid "&Save" +msgstr "&Sauver" -#: platform/cocoamain.mm:1126 platform/w32main.cpp:183 +#: solvespace.cpp:651 msgctxt "button" -msgid "OK" -msgstr "Valider" +msgid "Do&n't Save" +msgstr "&Ne pas sauver" + +#: solvespace.cpp:672 +msgctxt "title" +msgid "(new sketch)" +msgstr "(nouveau dessin)" -#: platform/cocoamain.mm:1211 platform/gtkmain.cpp:1382 platform/w32main.cpp:1410 -#: platform/w32main.cpp:1450 +#: solvespace.cpp:683 msgctxt "title" msgid "Property Browser" msgstr "Navigateur de propriété" -#: platform/gtkmain.cpp:968 -msgctxt "title" -msgid "Open File" -msgstr "Ouvrir Fichier" +#: 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." -#: platform/gtkmain.cpp:970 -msgid "_Cancel" -msgstr "_Annuler" +#: 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." -#: platform/gtkmain.cpp:971 -msgid "_Open" -msgstr "_Ouvrir" +#: 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." -#: platform/gtkmain.cpp:1010 -msgctxt "title" -msgid "Save File" -msgstr "Sauver fichier" +#: 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." -#: platform/gtkmain.cpp:1013 platform/gtkmain.cpp:1049 platform/gtkmain.cpp:1097 -msgctxt "button" -msgid "_Cancel" -msgstr "_Annuler" +#: solvespace.cpp:914 +msgid "The assembly does not interfere, good." +msgstr "Cet assemblage n'interfère pas, bien." -#: platform/gtkmain.cpp:1014 platform/gtkmain.cpp:1047 -msgctxt "button" -msgid "_Save" -msgstr "_Sauver" +#: 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" -#: platform/gtkmain.cpp:1042 platform/w32main.cpp:1167 +#: solvespace.cpp:939 +#, c-format msgid "" -"The file has changed since it was last saved.\n" "\n" -"Do you want to save the changes?" +"The volume of current group mesh is:\n" +"\n" +" %s" msgstr "" -"Le fichier a changé depuis sa dernière sauvegarde.\n" "\n" -"Voulez-vous enregistrer les modifications?" +"Le volume de ce groupe mesh est :\n" +"\n" +" %s" -#: platform/gtkmain.cpp:1046 platform/w32main.cpp:1169 -msgctxt "title" -msgid "Modified File" -msgstr "Fichier modifié" +#: 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." -#: platform/gtkmain.cpp:1048 -msgctxt "button" -msgid "Do_n't Save" -msgstr "_Ne pas sauver" +#: 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 "" +"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." -#: platform/gtkmain.cpp:1066 platform/w32main.cpp:1193 +#: solvespace.cpp:968 msgid "" -"An autosave file is available for this project.\n" +"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:980 +#, c-format +msgid "" +"The area of the region sketched in this group is:\n" +"\n" +" %s\n" "\n" -"Do you want to load the autosave file instead?" +"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." -#: platform/gtkmain.cpp:1070 platform/w32main.cpp:1195 -msgctxt "title" -msgid "Autosave Available" -msgstr "Sauvegarde automatique existante" +#: 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 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." -#: platform/gtkmain.cpp:1071 -msgctxt "button" -msgid "_Load autosave" -msgstr "_Charger la sauvegarde automatique" +#: 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." -#: platform/gtkmain.cpp:1072 -msgctxt "button" -msgid "Do_n't Load" -msgstr "_Ne pas charger" +#: solvespace.cpp:1022 +msgid "Bad selection for trace; select a single point." +msgstr "Mauvaise sélection pour le tracé ; sélectionnez un unique point." -#: platform/gtkmain.cpp:1093 platform/w32main.cpp:1223 -msgctxt "title" -msgid "Missing File" -msgstr "Fichier manquant" +#: solvespace.cpp:1049 +#, c-format +msgid "Couldn't write to '%s'" +msgstr "Impossible d'écrire « %s »" -#: platform/gtkmain.cpp:1094 -msgctxt "button" -msgid "_Yes" -msgstr "_Oui" +#: solvespace.cpp:1079 +msgid "The mesh is self-intersecting (NOT okay, invalid)." +msgstr "Le mesh s'intersecte (NON valide)." -#: platform/gtkmain.cpp:1095 -msgctxt "button" -msgid "_No" -msgstr "_Non" +#: solvespace.cpp:1080 +msgid "The mesh is not self-intersecting (okay, valid)." +msgstr "Le mesh ne s'intersecte pas (valide)." -#: platform/gtkmain.cpp:1306 platform/w32main.cpp:179 -msgctxt "title" -msgid "Error" -msgstr "Erreur" +#: solvespace.cpp:1082 +msgid "The mesh has naked edges (NOT okay, invalid)." +msgstr "Le mesh a des arêtes nues (NON valide)." -#: platform/gtkmain.cpp:1306 platform/w32main.cpp:179 -msgctxt "title" -msgid "Message" -msgstr "Message" +#: solvespace.cpp:1083 +msgid "The mesh is watertight (okay, valid)." +msgstr "Le mesh est étanche (valide)." + +#: 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:1090 +#, c-format +msgid "" +"%s\n" +"\n" +"%s\n" +"\n" +"Zero problematic edges, good.%s" +msgstr "" +"%s\n" +"\n" +"%s\n" +"\n" +"Pas d'arête problématique, 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 arêtes problématiques, pas bien.%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 "" +"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:160 +#: 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:659 +#: style.cpp:735 msgid "Style name cannot be empty" msgstr "Le nom d'un style ne peut pas être vide" -#: textscreens.cpp:662 +#: 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:666 +#: 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:695 +#: textscreens.cpp:866 msgid "Group name cannot be empty" msgstr "Un nom de groupe ne peut pas être vide" -#: textscreens.cpp:739 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." msgstr "L'opacité doit être entre 0 et 1." -#: textscreens.cpp:778 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "Le rayon ne peut pas être zéro ou négatif." @@ -1720,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" @@ -1728,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" @@ -1788,102 +2276,366 @@ 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 "Nouveau groupe hélice du dessin actif" + +#: toolbar.cpp:74 +msgid "New group revolve active sketch" +msgstr "Nouveau groupe de révolution partielle du dessin actif" + +#: toolbar.cpp:76 msgid "New group step and repeat rotating" msgstr "Nouveau groupe de répétition circulaire" -#: toolbar.cpp:74 +#: toolbar.cpp:78 msgid "New group step and repeat translating" msgstr "Nouveau groupe de répétition linéaire" -#: toolbar.cpp:76 +#: toolbar.cpp:80 msgid "New group in new workplane (thru given entities)" msgstr "" "Nouveau groupe dans un nouveau plan de travail (Par des entités données)" -#: toolbar.cpp:78 +#: toolbar.cpp:82 msgid "New group in 3d" msgstr "Nouveau groupe en 3d" -#: toolbar.cpp:80 +#: toolbar.cpp:84 msgid "New group linking / assembling file" msgstr "Nouveau groupe lié / assemblage" -#: toolbar.cpp:84 +#: toolbar.cpp:88 msgid "Nearest isometric view" msgstr "Vue isométrique la plus proche" -#: toolbar.cpp:86 +#: toolbar.cpp:90 msgid "Align view to active workplane" msgstr "Aligner la vue sur le plan de travail actif" -#: ui.h:69 -msgid "SolveSpace models" -msgstr "Modèles SolveSpace" +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "Erreur" -#: ui.h:74 -msgid "PNG file" -msgstr "Fichier PNG" +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "Message" -#: ui.h:79 -msgid "STL mesh" -msgstr "Maillage STL" +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "&Ok" -#: ui.h:80 -msgid "Wavefront OBJ mesh" -msgstr "Maillage Wavefront OBJ" +#: view.cpp:127 +msgid "Scale cannot be zero or negative." +msgstr "L'échelle ne peut pas être zéro ou négative." -#: ui.h:81 -msgid "Three.js-compatible mesh, with viewer" -msgstr "Maillage Three-js-compatible, avec visualisateur" +#: view.cpp:139 view.cpp:148 +msgid "Bad format: specify 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." + +#~ msgid "Show Degrees of &Freedom" +#~ msgstr "Montrer les Degrés de &Liberté" + +#~ msgid "click to place bottom left of text" +#~ msgstr "cliquez pour placer le bas gauche du texte" + +#~ 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 ?" + +#~ msgid "Your changes will be lost if you don't save them." +#~ msgstr "Vos modifications seront perdues si vous ne les enregistrez pas." + +#~ msgctxt "button" +#~ msgid "Save" +#~ msgstr "Sauver" + +#~ msgctxt "button" +#~ msgid "Cancel" +#~ msgstr "Annuler" + +#~ msgctxt "button" +#~ msgid "Don't Save" +#~ msgstr "Ne pas sauver" + +#~ msgid "Do you want to load the autosave file instead?" +#~ msgstr "Voulez-vous charger le fichier de sauvegarde à la place ?" + +#~ msgctxt "button" +#~ msgid "Load" +#~ msgstr "Charger" + +#~ msgctxt "button" +#~ msgid "Don't Load" +#~ msgstr "Ne pas charger" + +#~ msgid "" +#~ "Do you want to locate it manually?\n" +#~ "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 " +#~ "manquant sera supprimée." + +#~ msgctxt "button" +#~ msgid "Yes" +#~ msgstr "Oui" + +#~ msgctxt "button" +#~ msgid "No" +#~ msgstr "Non" + +#~ msgctxt "button" +#~ msgid "OK" +#~ msgstr "Valider" + +#~ msgid "_Cancel" +#~ msgstr "_Annuler" + +#~ msgid "_Open" +#~ msgstr "_Ouvrir" + +#~ msgid "" +#~ "The file has changed since it was last saved.\n" +#~ "\n" +#~ "Do you want to save the changes?" +#~ msgstr "" +#~ "Le fichier a changé depuis sa dernière sauvegarde.\n" +#~ "\n" +#~ "Voulez-vous enregistrer les modifications ?" -#: ui.h:82 -msgid "Three.js-compatible mesh, mesh only" -msgstr "Maillage Three-js-compatible, maillage seul" +#~ msgctxt "button" +#~ msgid "Do_n't Save" +#~ msgstr "_Ne pas sauver" -#: ui.h:87 ui.h:95 ui.h:103 -msgid "STEP file" -msgstr "Fichier STEP" +#~ msgctxt "button" +#~ msgid "_Load autosave" +#~ msgstr "_Charger la sauvegarde automatique" + +#~ msgctxt "button" +#~ msgid "Do_n't Load" +#~ msgstr "_Ne pas charger" -#: ui.h:92 -msgid "PDF file" -msgstr "Fichier PDF" +#~ msgctxt "button" +#~ msgid "_Yes" +#~ msgstr "_Oui" -#: ui.h:93 -msgid "Encapsulated PostScript" -msgstr "Encapsulated PostScript" +#~ msgctxt "button" +#~ msgid "_No" +#~ msgstr "_Non" -#: ui.h:94 -msgid "Scalable Vector Graphics" -msgstr "Scalable Vector Graphics" +#~ msgid "SolveSpace models" +#~ msgstr "Modèles SolveSpace" -#: ui.h:96 ui.h:104 -msgid "DXF file (AutoCAD 2007)" -msgstr "Fichier DXF (AutoCAD 2007)" +#~ msgid "PNG file" +#~ msgstr "Fichier PNG" -#: ui.h:97 -msgid "HPGL file" -msgstr "Fichier HPGL" +#~ msgid "STL mesh" +#~ msgstr "Maillage STL" -#: ui.h:98 -msgid "G Code" -msgstr "G Code" +#~ msgid "Wavefront OBJ mesh" +#~ msgstr "Maillage Wavefront OBJ" -#: ui.h:109 -msgid "AutoCAD DXF and DWG files" -msgstr "Fichiers AutoCAD DXF et DWG" +#~ msgid "Three.js-compatible mesh, with viewer" +#~ msgstr "Maillage Three-js-compatible, avec visualisateur" -#: ui.h:114 -msgid "Comma-separated values" -msgstr "Valeurs CSV séparées par des virgules" +#~ msgid "Three.js-compatible mesh, mesh only" +#~ msgstr "Maillage Three-js-compatible, maillage seul" -#: view.cpp:78 -msgid "Scale cannot be zero or negative." -msgstr "L'échelle ne peut pas être zéro ou négative." +#~ msgid "STEP file" +#~ msgstr "Fichier STEP" -#: view.cpp:90 view.cpp:99 -msgid "Bad format: specify x, y, z" -msgstr "Mauvais format: Spécifiez x, y, z" +#~ msgid "PDF file" +#~ msgstr "Fichier PDF" + +#~ msgid "Encapsulated PostScript" +#~ msgstr "Encapsulated PostScript" + +#~ msgid "Scalable Vector Graphics" +#~ msgstr "Scalable Vector Graphics" + +#~ msgid "DXF file (AutoCAD 2007)" +#~ msgstr "Fichier DXF (AutoCAD 2007)" + +#~ msgid "HPGL file" +#~ msgstr "Fichier HPGL" + +#~ msgid "G Code" +#~ msgstr "G Code" + +#~ msgid "AutoCAD DXF and DWG files" +#~ msgstr "Fichiers AutoCAD DXF et DWG" +#~ msgid "Comma-separated values" +#~ msgstr "Valeurs CSV séparées par des virgules" 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 661f6fae2..36f1d2e1e 100644 --- a/res/locales/ru_RU.po +++ b/res/locales/ru_RU.po @@ -2,76 +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: 2018-07-12 22:40+0000\n" -"PO-Revision-Date: 2017-04-21 10:29+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.0.1\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:274 +#: 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:291 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." msgstr "Буфер обмена пуст; нечего вставлять." -#: clipboard.cpp:338 +#: clipboard.cpp:378 msgid "Number of copies to paste must be at least one." msgstr "Укажите в поле 'количество' хотя бы одну копию для вставки." -#: clipboard.cpp:354 textscreens.cpp:709 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "Масштабный коэффициент не может быть нулевым." -#: clipboard.cpp:392 +#: clipboard.cpp:436 msgid "Select one point to define origin of rotation." msgstr "Выберите одну точку в качестве центра вращения." -#: clipboard.cpp:404 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." msgstr "Выберите две точки, чтобы задать вектор смещения." -#: clipboard.cpp:414 +#: clipboard.cpp:458 msgid "" "Transformation is identity. So all copies will be exactly on top of each " "other." msgstr "" "Трансформация не задана. Все копии будут расположены в одном и том же месте." -#: clipboard.cpp:418 +#: clipboard.cpp:462 msgid "Too many items to paste; split this into smaller pastes." msgstr "Слишком много элементов для вставки; разбейте на несколько частей." -#: clipboard.cpp:423 +#: clipboard.cpp:467 msgid "No workplane active." -msgstr "Рабочая плоскость не активна" +msgstr "Нет активной рабочей плоскости." -#: confscreen.cpp:336 +#: confscreen.cpp:410 msgid "Bad format: specify coordinates as x, y, z" msgstr "Неверный формат: введите координаты как x, y, z" -#: confscreen.cpp:347 style.cpp:653 textscreens.cpp:730 +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 msgid "Bad format: specify color as r, g, b" msgstr "Неверный формат: введите цвет как r, g, b" -#: confscreen.cpp:372 +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." @@ -79,24 +83,25 @@ msgstr "" "Коэффициент перспективы не будет иметь эффект, пока вы не включите Вид-" ">Перспективная Проекция." -#: confscreen.cpp:386 -msgid "Specify between 0 and 8 digits after the decimal." -msgstr "Введите число от 0 до 8." +#: confscreen.cpp:464 confscreen.cpp:474 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "Введите число от 0 до %d." -#: confscreen.cpp:398 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" msgstr "Масштабный коэффициент не может быть нулевым!" -#: confscreen.cpp:410 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" msgstr "Радиус режущего инструмента не может быть отрицательным!" -#: confscreen.cpp:464 +#: confscreen.cpp:557 msgid "Bad value: autosave interval should be positive" msgstr "" "Неверное значение: интервал автосохранения должен быть положительным числом" -#: confscreen.cpp:467 +#: confscreen.cpp:560 msgid "Bad format: specify interval in integral minutes" msgstr "" "Неверный формат: введите целое число, чтобы задать интервал автосохранения" @@ -168,115 +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:160 +#: 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 "" +"Выбранные вами точки не являются конечными точками двух кривых. У кривых нет " +"общей конечной точки.\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" @@ -301,78 +413,82 @@ msgstr "" " * грань и точку (расстояние от точки до плоскости грани)\n" " * окружность или дугу (диаметр / радиус)\n" -#: constraint.cpp:213 +#: 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:275 +#: 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:314 +#: 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:331 +#: 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:357 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" @@ -385,7 +501,7 @@ msgstr "" " * точку и отрезок (точка на середине отрезка)\n" " * отрезок и рабочую плоскость (середина отрезка на плоскости)\n" -#: constraint.cpp:415 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -405,7 +521,7 @@ msgstr "" " * рабочую плоскость и две точки / отрезок (симметричность относительно " "рабочей плоскости\n" -#: constraint.cpp:429 +#: constraint.cpp:623 msgid "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." @@ -413,7 +529,7 @@ msgstr "" "Рабочая плоскость должна быть активна для того, чтобы создать\n" "ограничение симметричности без явного указания плоскости симметрии." -#: constraint.cpp:459 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." @@ -421,113 +537,104 @@ msgstr "" "Рабочая плоскость должна быть активирована (Эскиз -> В рабочей плоскости)\n" "перед тем, как накладывать ограничения горизонтальности / вертикальности." -#: constraint.cpp:472 +#: 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:493 +#: 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:545 +#: constraint.cpp:748 msgid "Must select an angle constraint." msgstr "" "Переключатся между смежными углами можно только выбрав ограничение угла." -#: constraint.cpp:557 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." msgstr "" "Переключать режим 'размера для справок' возможно только для ограничений, " "имеющих размерное значение." -#: constraint.cpp:568 +#: 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:625 -msgid "" -"The tangent arc and line segment must share an endpoint. Constrain them with " -"Constrain -> On Point before constraining tangent." -msgstr "" -"Дуга и отрезок должны быть соединены. Соедините их крайние точки с помощью " -"'Ограничения -> Точка на Примитиве' перед тем, как применять ограничение " -"касательности." - -#: constraint.cpp:649 -msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them " -"with Constrain -> On Point before constraining tangent." -msgstr "" -"Сплайн и отрезок должны быть соединены. Соедините их крайние точки с помощью " -"'Ограничения -> Точка на Примитиве' перед тем, как применять ограничение " -"касательности." - -#: constraint.cpp:659 +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." msgstr "" "Ограничение касательности может быть наложено только в рабочей плоскости." -#: constraint.cpp:677 -msgid "" -"The curves must share an endpoint. Constrain them with Constrain -> On Point " -"before constraining tangent." -msgstr "" -"Кривые должны быть соединены. Соедините их крайние точки с помощью " -"'Ограничения -> Точка на Примитиве' перед тем, как применять ограничение " -"касательности." - -#: constraint.cpp:686 +#: 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:704 +#: 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,33 +642,39 @@ msgstr "" "Неправильное выделение для ограничения перпендикулярности.\n" "Ограничение может принимать в качестве выделения следующие примитивы:\n" "\n" +" * две граней\n" " * два отрезка\n" " * отрезок и координатный базис (нормаль)\n" " * два координатных базиса (нормали)\n" -#: constraint.cpp:719 +#: 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:730 +#: 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:18 +#: 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:60 +#: export.cpp:61 msgid "" "Bad selection for export section. Please select:\n" "\n" @@ -570,34 +683,41 @@ msgid "" " * a point and two line segments (plane through point and parallel to " "lines)\n" msgstr "" +"Неправильное выделение для экспорта сечения. Необходимо выделить:\n" +"\n" +" * Ничего, но с активированной рабочей плоскостью (рабочая плоскость " +"будет плоскостью сечения)\n" +" * грань (сечение через грань)\n" +" * точку и два отрезка (сечение плоскостью, заданной двумя отрезками, " +"построенной через указанную точку)\n" -#: export.cpp:805 +#: 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:590 +#: exportvector.cpp:592 msgid "" "Some aspects of the drawing have no DXF equivalent and were not exported:\n" msgstr "" "Некоторые элементы чертежа не имеют аналогов в DXF-представлении и не были " "экспортированы:\n" -#: exportvector.cpp:807 +#: exportvector.cpp:838 msgid "" "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." msgstr "" "Размер страницы PDF превышает 200x200 дюймов; некоторые программы просмотра " "не смогут прочитать такой файл." -#: file.cpp:44 group.cpp:95 +#: file.cpp:44 group.cpp:91 msgctxt "group-name" msgid "sketch-in-plane" msgstr "эскиз-в-плоскости" @@ -607,439 +727,526 @@ msgctxt "group-name" msgid "#references" msgstr "система-координат" -#: file.cpp:539 +#: 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 "Файл Отсутствует" -#: graphicswin.cpp:29 +#: 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" +"Если вы ответите \"Нет\", то вся геометрия, которая зависит от " +"отсутствующего файла будет удалена." + +#: file.cpp:882 +msgctxt "button" +msgid "&Yes" +msgstr "Да" + +#: file.cpp:884 +msgctxt "button" +msgid "&No" +msgstr "Нет" + +#: file.cpp:886 solvespace.cpp:652 +msgctxt "button" +msgid "&Cancel" +msgstr "Отменить" + +#: graphicswin.cpp:41 msgid "&File" msgstr "&Файл" -#: graphicswin.cpp:30 +#: graphicswin.cpp:42 msgid "&New" -msgstr "&Новый" +msgstr "Соз&дать" -#: graphicswin.cpp:31 +#: graphicswin.cpp:43 msgid "&Open..." msgstr "&Открыть..." -#: graphicswin.cpp:32 +#: graphicswin.cpp:44 msgid "Open &Recent" -msgstr "Открыть Н&едавний" +msgstr "Открыть н&едавние файлы" -#: graphicswin.cpp:33 +#: graphicswin.cpp:45 msgid "&Save" msgstr "&Сохранить" -#: graphicswin.cpp:34 +#: graphicswin.cpp:46 msgid "Save &As..." -msgstr "Сохранить &Как..." +msgstr "Сохранить &Как…" -#: graphicswin.cpp:36 +#: graphicswin.cpp:48 msgid "Export &Image..." -msgstr "Экспортировать И&зображение..." +msgstr "Экспортировать и&зображение…" -#: graphicswin.cpp:37 +#: graphicswin.cpp:49 msgid "Export 2d &View..." -msgstr "Экспортировать &2d Вид..." +msgstr "Экспортировать &2d-вид…" -#: graphicswin.cpp:38 +#: graphicswin.cpp:50 msgid "Export 2d &Section..." -msgstr "Экспортировать 2d Се&чение..." +msgstr "Экспортировать 2d-се&чение…" -#: graphicswin.cpp:39 +#: graphicswin.cpp:51 msgid "Export 3d &Wireframe..." -msgstr "Экспортировать &3d Каркас..." +msgstr "Экспортировать &3d-каркас…" -#: graphicswin.cpp:40 +#: graphicswin.cpp:52 msgid "Export Triangle &Mesh..." -msgstr "Экспортировать &Полигональную сетку..." +msgstr "Экспортировать &полигональную сетку…" -#: graphicswin.cpp:41 +#: graphicswin.cpp:53 msgid "Export &Surfaces..." -msgstr "Экспортировать Повер&хности..." +msgstr "Экспортировать повер&хности…" -#: graphicswin.cpp:42 +#: graphicswin.cpp:54 msgid "Im&port..." msgstr "&Импортировать..." -#: graphicswin.cpp:45 +#: graphicswin.cpp:57 msgid "E&xit" msgstr "&Выход" -#: graphicswin.cpp:48 +#: graphicswin.cpp:60 msgid "&Edit" msgstr "&Правка" -#: graphicswin.cpp:49 +#: graphicswin.cpp:61 msgid "&Undo" msgstr "&Отменить" -#: graphicswin.cpp:50 +#: graphicswin.cpp:62 msgid "&Redo" msgstr "&Вернуть" -#: graphicswin.cpp:51 +#: graphicswin.cpp:63 msgid "Re&generate All" -msgstr "&Перегенерировать Все" +msgstr "&Перегенерировать все" -#: graphicswin.cpp:53 +#: graphicswin.cpp:65 msgid "Snap Selection to &Grid" -msgstr "Привязать Выделение к &Сетке" +msgstr "Привязать выделение к &сетке" -#: graphicswin.cpp:54 +#: graphicswin.cpp:66 msgid "Rotate Imported &90°" -msgstr "Повернуть Импортированное на &90°" +msgstr "Повернуть импортированное на &90°" -#: graphicswin.cpp:56 +#: graphicswin.cpp:68 msgid "Cu&t" msgstr "Вы&резать" -#: graphicswin.cpp:57 +#: graphicswin.cpp:69 msgid "&Copy" msgstr "&Копировать" -#: graphicswin.cpp:58 +#: graphicswin.cpp:70 msgid "&Paste" msgstr "В&ставить" -#: graphicswin.cpp:59 +#: graphicswin.cpp:71 msgid "Paste &Transformed..." -msgstr "Вставить с &Трансформацией..." +msgstr "Вставить с &трансформацией…" -#: graphicswin.cpp:60 +#: graphicswin.cpp:72 msgid "&Delete" msgstr "&Удалить" -#: graphicswin.cpp:62 +#: graphicswin.cpp:74 msgid "Select &Edge Chain" -msgstr "Вы&делить Последовательность Ребер" +msgstr "Вы&делить последовательность ребер" -#: graphicswin.cpp:63 +#: graphicswin.cpp:75 msgid "Select &All" -msgstr "В&ыделить Все" +msgstr "В&ыделить всё" -#: graphicswin.cpp:64 +#: graphicswin.cpp:76 msgid "&Unselect All" -msgstr "С&бросить Выделение" +msgstr "С&бросить выделение" -#: graphicswin.cpp:66 +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "Стили линий…" + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "&Проекция вида..." + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "Настройки..." + +#: graphicswin.cpp:84 msgid "&View" msgstr "&Вид" -#: graphicswin.cpp:67 +#: graphicswin.cpp:85 msgid "Zoom &In" msgstr "&Приблизить" -#: graphicswin.cpp:68 +#: graphicswin.cpp:86 msgid "Zoom &Out" msgstr "От&далить" -#: graphicswin.cpp:69 +#: graphicswin.cpp:87 msgid "Zoom To &Fit" -msgstr "Показать &Все / Выделенное" +msgstr "Показать &всё / выделенное" -#: graphicswin.cpp:71 +#: graphicswin.cpp:89 msgid "Align View to &Workplane" -msgstr "Выровнять Вид на &Рабочую Плоскость" +msgstr "Выровнять вид по &рабочей плоскости" -#: graphicswin.cpp:72 +#: graphicswin.cpp:90 msgid "Nearest &Ortho View" -msgstr "Ближайший &Ортогональный Вид" +msgstr "Ближайший &ортогональный вид" -#: graphicswin.cpp:73 +#: graphicswin.cpp:91 msgid "Nearest &Isometric View" -msgstr "Ближайший &Изометрический Вид" +msgstr "Ближайший &изометрический вид" -#: graphicswin.cpp:74 +#: graphicswin.cpp:92 msgid "&Center View At Point" -msgstr "&Центрировать Вид на Точке" +msgstr "&Центрировать вид на точке" -#: graphicswin.cpp:76 +#: graphicswin.cpp:94 msgid "Show Snap &Grid" -msgstr "Показать &Сетку" +msgstr "Показать &сетку" + +#: graphicswin.cpp:95 +msgid "Darken Inactive Solids" +msgstr "Затемнять неактивные тела" -#: graphicswin.cpp:77 +#: graphicswin.cpp:96 msgid "Use &Perspective Projection" -msgstr "Перспективная Прое&кция" +msgstr "Перспективная прое&кция" -#: graphicswin.cpp:78 -msgid "Dimension &Units" -msgstr "" +#: graphicswin.cpp:97 +msgid "Show E&xploded View" +msgstr "Показать &развернутый вид" -#: graphicswin.cpp:79 -msgid "Dimensions in &Inches" -msgstr "Размеры в Дю&ймах" +#: graphicswin.cpp:98 +msgid "Dimension &Units" +msgstr "Единицы &измерения" -#: graphicswin.cpp:80 +#: graphicswin.cpp:99 msgid "Dimensions in &Millimeters" -msgstr "Размеры в Ми&ллиметрах" +msgstr "Размеры в ми&ллиметрах" -#: graphicswin.cpp:81 +#: graphicswin.cpp:100 msgid "Dimensions in M&eters" -msgstr "" +msgstr "Размеры в метрах" + +#: graphicswin.cpp:101 +msgid "Dimensions in &Inches" +msgstr "Размеры в дю&ймах" + +#: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "Размеры в &футах и дюймах" -#: graphicswin.cpp:83 +#: graphicswin.cpp:104 msgid "Show &Toolbar" -msgstr "Показывать Па&нель Инструментов" +msgstr "Показывать па&нель инструментов" -#: graphicswin.cpp:84 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" -msgstr "Показывать Брау&зер" +msgstr "Показывать брау&зер" -#: graphicswin.cpp:86 +#: graphicswin.cpp:107 msgid "&Full Screen" -msgstr "Полно&экранный Режим" +msgstr "Полно&экранный режим" -#: graphicswin.cpp:88 +#: graphicswin.cpp:109 msgid "&New Group" -msgstr "&Группа" +msgstr "&Новая группа" -#: graphicswin.cpp:89 +#: graphicswin.cpp:110 msgid "Sketch In &3d" -msgstr "Создать Эскиз в &3d" +msgstr "Создать эскиз в &3d" -#: graphicswin.cpp:90 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" -msgstr "Создать Эскиз в Новой &Рабочей Плоскости" +msgstr "Создать эскиз в новой &рабочей плоскости" -#: graphicswin.cpp:92 +#: graphicswin.cpp:113 msgid "Step &Translating" -msgstr "&Линейный Массив" +msgstr "&Линейный массив" -#: graphicswin.cpp:93 +#: graphicswin.cpp:114 msgid "Step &Rotating" -msgstr "&Круговой Массив" +msgstr "&Круговой массив" -#: graphicswin.cpp:95 +#: graphicswin.cpp:116 msgid "E&xtrude" -msgstr "Тело &Выдавливания" +msgstr "Тело &выдавливания" -#: graphicswin.cpp:96 +#: graphicswin.cpp:117 +msgid "&Helix" +msgstr "Тело в&интовое" + +#: graphicswin.cpp:118 msgid "&Lathe" -msgstr "Тело В&ращения" +msgstr "Тело в&ращения" -#: graphicswin.cpp:98 +#: graphicswin.cpp:119 +msgid "Re&volve" +msgstr "Тело в&ращения на угол" + +#: graphicswin.cpp:121 msgid "Link / Assemble..." -msgstr "&Импорт Детали / Сборка..." +msgstr "&Импортировать деталь/сборку…" -#: graphicswin.cpp:99 +#: graphicswin.cpp:122 msgid "Link Recent" -msgstr "Последние &Детали" +msgstr "Импортировать недавний файл" -#: graphicswin.cpp:101 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "&Эскиз" -#: graphicswin.cpp:102 +#: graphicswin.cpp:125 msgid "In &Workplane" -msgstr "В &Рабочей Плоскости" +msgstr "В &рабочей плоскости" -#: graphicswin.cpp:103 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" msgstr "Режим &3d" -#: graphicswin.cpp:105 +#: graphicswin.cpp:128 msgid "Datum &Point" -msgstr "Опорная &Точка" +msgstr "Опорная &точка" -#: graphicswin.cpp:106 -msgid "&Workplane" -msgstr "Рабочая &Плоскость" +#: graphicswin.cpp:129 +msgid "Wor&kplane" +msgstr "Рабочая &плоскость" -#: graphicswin.cpp:108 +#: graphicswin.cpp:131 msgid "Line &Segment" msgstr "&Отрезок" -#: graphicswin.cpp:109 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" -msgstr "&Вспомогательный Отрезок" +msgstr "&Вспомогательный отрезок" -#: graphicswin.cpp:110 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "Прямоу&гольник" -#: graphicswin.cpp:111 +#: graphicswin.cpp:134 msgid "&Circle" msgstr "О&кружность" -#: graphicswin.cpp:112 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" -msgstr "Д&уга Окружности" +msgstr "Д&уга окружности" -#: graphicswin.cpp:113 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" -msgstr "Кубический &Сплайн Безье" +msgstr "Кубический &сплайн Безье" -#: graphicswin.cpp:115 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" msgstr "Т&екст TrueType" -#: graphicswin.cpp:116 -msgid "&Image" -msgstr "" +#: graphicswin.cpp:139 +msgid "I&mage" +msgstr "И&зображение" -#: graphicswin.cpp:118 +#: graphicswin.cpp:141 msgid "To&ggle Construction" -msgstr "Переключить Режим Вс&помогательных Построений" +msgstr "Переключить режим вс&помогательных построений" -#: graphicswin.cpp:119 -msgid "Tangent &Arc at Point" -msgstr "Кас&ательная в Точке" +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" +msgstr "Кас&ательная в точке" -#: graphicswin.cpp:120 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" -msgstr "Ра&збить Кривые Пересечением" +msgstr "Ра&збить кривые пересечением" -#: graphicswin.cpp:122 +#: graphicswin.cpp:145 msgid "&Constrain" -msgstr "&Ограничения" +msgstr "&Ограничение" -#: graphicswin.cpp:123 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "&Расстояние / Диаметр" -#: graphicswin.cpp:124 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" -msgstr "&Справочный Размер" +msgstr "Справочный ра&змер" -#: graphicswin.cpp:125 -msgid "A&ngle" -msgstr "&Угол" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "&Угол / Равенство углов" -#: graphicswin.cpp:126 +#: graphicswin.cpp:149 msgid "Reference An&gle" -msgstr "С&правочный Угол" +msgstr "Справочный &угол" -#: graphicswin.cpp:127 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" -msgstr "Переключить Сме&жный Угол" +msgstr "Переключить сме&жный угол" -#: graphicswin.cpp:128 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" -msgstr "Переключить Режим Размера Для Спра&вок" +msgstr "Переключить &режим справочного размера" -#: graphicswin.cpp:130 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "&Горизонтальность" -#: graphicswin.cpp:131 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "&Вертикальность" -#: graphicswin.cpp:133 +#: graphicswin.cpp:156 msgid "&On Point / Curve / Plane" -msgstr "&Точка на Примитиве" +msgstr "&Точка на примитиве" -#: graphicswin.cpp:134 -msgid "E&qual Length / Radius / Angle" -msgstr "&Равенство Длин / Радиусов / Углов" +#: graphicswin.cpp:157 +msgid "E&qual Length / Radius" +msgstr "&Равенство длин / радиусов" -#: graphicswin.cpp:135 -msgid "Length Ra&tio" -msgstr "Отно&шение Длин" +#: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "&Отношение длин (дуга)" -#: graphicswin.cpp:136 -msgid "Length Diff&erence" -msgstr "Ра&зница Длин" +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Р&азность длин (дуга)" -#: graphicswin.cpp:137 +#: graphicswin.cpp:160 msgid "At &Midpoint" -msgstr "&На Середине" +msgstr "&На середине" -#: graphicswin.cpp:138 +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "С&имметричность" -#: graphicswin.cpp:139 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "Пара&ллельность / Касательность" -#: graphicswin.cpp:140 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "Перпендикул&ярность" -#: graphicswin.cpp:141 +#: graphicswin.cpp:164 msgid "Same Orient&ation" -msgstr "Идентичная &Ориентация" +msgstr "Идентичная &ориентация" -#: graphicswin.cpp:142 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "За&фиксировать" -#: graphicswin.cpp:144 +#: graphicswin.cpp:167 msgid "Comment" -msgstr "Текстовый &Комментарий" +msgstr "Комментарий" -#: graphicswin.cpp:146 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "&Анализ" -#: graphicswin.cpp:147 +#: graphicswin.cpp:170 msgid "Measure &Volume" -msgstr "Измерить &Объем" +msgstr "Измерить &объем" -#: graphicswin.cpp:148 +#: graphicswin.cpp:171 msgid "Measure A&rea" -msgstr "Измерить П&лощадь" +msgstr "Измерить п&лощадь" -#: graphicswin.cpp:149 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" -msgstr "Измерить П&ериметр" +msgstr "Измерить п&ериметр" -#: graphicswin.cpp:150 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" -msgstr "Показать Пе&ресекающиеся Детали" +msgstr "Показать пе&ресекающиеся детали" -#: graphicswin.cpp:151 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" -msgstr "Показать Про&блемные Ребра" +msgstr "Показать про&блемные ребра" -#: graphicswin.cpp:152 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" -msgstr "" +msgstr "Показать &центр массы" -#: graphicswin.cpp:154 -msgid "Show Degrees of &Freedom" -msgstr "Показать Степени С&вободы" +#: graphicswin.cpp:177 +msgid "Show &Underconstrained Points" +msgstr "Показать c&вободные точки" -#: graphicswin.cpp:156 +#: graphicswin.cpp:179 msgid "&Trace Point" -msgstr "Включить &Трассировку Точки" +msgstr "Включить &трассировку точки" -#: graphicswin.cpp:157 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." -msgstr "Остановить Тра&ссировку..." +msgstr "Остановить тра&ссировку..." -#: graphicswin.cpp:158 +#: graphicswin.cpp:181 msgid "Step &Dimension..." -msgstr "Плавное Из&менение Размера..." +msgstr "Плавное из&менение размера…" -#: graphicswin.cpp:160 +#: graphicswin.cpp:183 msgid "&Help" -msgstr "&Помощь" +msgstr "&Справка" -#: graphicswin.cpp:161 +#: graphicswin.cpp:184 +msgid "&Language" +msgstr "&Язык" + +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "Вебсайт / &Справка" -#: graphicswin.cpp:162 -msgid "&Language" -msgstr "&Язык" +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "Пере&йти к коммиту на GitHub" -#: graphicswin.cpp:164 +#: graphicswin.cpp:188 msgid "&About" msgstr "О &Программе" -#: graphicswin.cpp:491 +#: 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:500 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel " "projection.\n" @@ -1053,25 +1260,25 @@ msgstr "" "перспективы на конфигурационной странице браузера.\n" "Значение по умолчанию 0.3." -#: graphicswin.cpp:581 +#: graphicswin.cpp:884 msgid "" "Select a point; this point will become the center of the view on screen." msgstr "Выделите точку. Вид будет отцентрован по этой точке." -#: graphicswin.cpp:862 +#: graphicswin.cpp:1193 msgid "No additional entities share endpoints with the selected entities." msgstr "Нет дополнительных объектов, соединенных с выбранными примитивами." -#: graphicswin.cpp:882 +#: 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:906 +#: graphicswin.cpp:1234 msgid "" "No workplane is active. Activate a workplane (with Sketch -> In Workplane) " "to define the plane for the snap grid." @@ -1079,7 +1286,7 @@ msgstr "" "Рабочая плоскость не активна. Активируйте ее через Эскиз -> В Рабочей " "Плоскости чтобы определить плоскость для сетки." -#: graphicswin.cpp:913 +#: 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." @@ -1088,13 +1295,13 @@ msgstr "" "текстовые комментарии или ограничения с размерными значениями. Чтобы " "привязать отрезок или другой примитив, выбирайте его точки." -#: graphicswin.cpp:979 +#: graphicswin.cpp:1326 msgid "No workplane selected. Activating default workplane for this group." msgstr "" "Рабочая плоскость не активна. Активирована рабочая плоскость по умолчанию " "для данной группы." -#: graphicswin.cpp:984 +#: 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-" @@ -1104,7 +1311,7 @@ msgstr "" "по умолчанию. Попробуйте выделить рабочую плоскость или создать новую с " "помощью Группа -> Создать Эскиз в Новой Рабочей Плоскости." -#: graphicswin.cpp:1008 +#: graphicswin.cpp:1350 msgid "" "Bad selection for tangent arc at point. Select a single point, or select " "nothing to set up arc parameters." @@ -1113,53 +1320,54 @@ msgstr "" "точку, либо запустите команду без выделения, чтобы перейти к окну настроек " "этой команды." -#: graphicswin.cpp:1019 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" msgstr "" "кликните мышью там, где хотите создать дугу окружности (дуга будет " "нарисована против часовой стрелки)" -#: graphicswin.cpp:1020 +#: graphicswin.cpp:1362 msgid "click to place datum point" msgstr "кликните мышью там, где хотите создать опорную точку" -#: graphicswin.cpp:1021 +#: graphicswin.cpp:1363 msgid "click first point of line segment" msgstr "кликните мышью там, где хотите создать первую точку отрезка" -#: graphicswin.cpp:1023 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" msgstr "" "кликните мышью там, где хотите создать первую точку вспомогательного отрезка" -#: graphicswin.cpp:1024 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" msgstr "" "кликните мышью там, где хотите создать первую точку кубического сплайна Безье" -#: graphicswin.cpp:1025 +#: graphicswin.cpp:1367 msgid "click center of circle" msgstr "кликните мышью там, где будет находиться центр окружности" -#: graphicswin.cpp:1026 +#: graphicswin.cpp:1368 msgid "click origin of workplane" msgstr "" "кликните мышью там, где будет находиться точка, через которую будет " "построена рабочая плоскость" -#: graphicswin.cpp:1027 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" msgstr "кликните мышью там, где будет находиться один из углов прямоугольника" -#: graphicswin.cpp:1028 +#: graphicswin.cpp:1370 msgid "click top left of text" msgstr "кликните мышью там, где хотите создать текст" -#: graphicswin.cpp:1034 +#: graphicswin.cpp:1376 msgid "click top left of image" msgstr "" +"кликните мышью там, где будет расположен левый верхний угол изображения" -#: graphicswin.cpp:1047 +#: graphicswin.cpp:1402 msgid "" "No entities are selected. Select entities before trying to toggle their " "construction state." @@ -1167,31 +1375,30 @@ msgstr "" "Не выбран ни один примитив. Выберите примитивы перед тем, как переключать их " "режим дополнительных построений." -#: group.cpp:90 +#: group.cpp:86 msgctxt "group-name" msgid "sketch-in-3d" msgstr "эскиз-в-3d" -#: group.cpp:146 +#: 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:158 +#: group.cpp:170 msgid "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " "will be extruded normal to the workplane." @@ -1199,12 +1406,18 @@ msgstr "" "Выберите рабочую плоскость (Эскиз -> В Рабочей Плоскости) перед созданием " "группы выдавливания. Эскиз будет выдавлен по нормали к рабочей плоскости." -#: group.cpp:167 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" msgstr "тело-выдавливания" -#: group.cpp:179 +#: 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" @@ -1216,17 +1429,73 @@ msgstr "" "Группа может быть создана, используя в качестве выделения следующие " "примитивы:\n" "\n" -" * точку и отрезок / координатных базис (нормаль) (тело вращения вокруг " -"оси, проходящей через точку и параллельной отрезку / нормали)\n" -" * отрезок (тело вращения вокруг оси, проходящей через отрезок)\n" +" * точку и отрезок / координатный базис (нормаль) (вращение вокруг оси, " +"проходящей через точку и параллельной отрезку / нормали)\n" +" * отрезок (вращение вокруг оси, проходящей через отрезок)\n" "\n" -#: group.cpp:189 +#: group.cpp:205 msgctxt "group-name" msgid "lathe" msgstr "тело-вращения" -#: group.cpp:202 +#: 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" +" * отрезок (вращение вокруг оси, проходящей через отрезок)\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" +" * отрезок (вращение вокруг отрезка)\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" @@ -1241,47 +1510,51 @@ msgstr "" "\n" " * точку при активной рабочей плоскости (вращение в плоскости вокруг " "выбранной точки)\n" -" * точку и отрезок / координатных базис (нормаль) (вращение вокруг оси, " +" * точку и отрезок / координатный базис (нормаль) (вращение вокруг оси, " "проходящей через точку и параллельной отрезку / нормали)\n" "\n" -#: group.cpp:215 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" msgstr "круговой-массив" -#: group.cpp:226 +#: group.cpp:298 msgctxt "group-name" msgid "translate" msgstr "линейный-массив" -#: group.cpp:340 +#: group.cpp:422 msgid "(unnamed)" msgstr "(без имени)" -#: groupmesh.cpp:626 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" msgstr "незамкнутый контур или несовпадение стилей!" -#: groupmesh.cpp:639 +#: groupmesh.cpp:723 msgid "points not all coplanar!" msgstr "не все точки лежат в одной плоскости!" -#: groupmesh.cpp:641 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" msgstr "контур имеет самопересечения!" -#: groupmesh.cpp:643 +#: groupmesh.cpp:727 msgid "zero-length edge!" -msgstr "вырожденный отрезок!" +msgstr "ребро нулевой длины!" -#: modify.cpp:237 +#: 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:284 +#: 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." @@ -1289,7 +1562,7 @@ msgstr "" "Чтобы создать скругление эскиза, выберите точку, где соединяются два " "примитива, не принадлежащих к вспомогательной геометрии." -#: modify.cpp:371 +#: modify.cpp:386 msgid "" "Couldn't round this corner. Try a smaller radius, or try creating the " "desired geometry by hand with tangency constraints." @@ -1297,369 +1570,603 @@ msgstr "" "Невозможно скруглить угол. Попробуйте радиус поменьше или создайте требуемую " "геометрию с помощью Ограничения -> Параллельность / Касательность." -#: modify.cpp:575 +#: modify.cpp:595 msgid "Couldn't split this entity; lines, circles, or cubics only." msgstr "" "Невозможно разделить такие примитивы. Выберите линии, окружности или " "кубические сплайны." -#: modify.cpp:601 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." msgstr "" "Пересечение примитивов работает только когда рабочая плоскость активна." -#: modify.cpp:608 +#: 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:713 +#: modify.cpp:734 msgid "Can't split; no intersection found." msgstr "Невозможно разделить пересекаемые примитивы: пересечений нет." -#: mouse.cpp:522 +#: mouse.cpp:558 +msgid "Assign to Style" +msgstr "Назначить стилю" + +#: mouse.cpp:574 msgid "No Style" -msgstr "Стиль по Умолчанию" +msgstr "Без стиля" -#: mouse.cpp:523 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." -msgstr "Создать Новый Стиль..." - -#: mouse.cpp:571 -msgid "Assign to Style" -msgstr "Применить Стиль" +msgstr "Создать новый стиль…" -#: mouse.cpp:574 +#: mouse.cpp:584 msgid "Group Info" -msgstr "Настройки Группы" +msgstr "Настройки группы" -#: mouse.cpp:577 +#: mouse.cpp:604 msgid "Style Info" -msgstr "Настройки Стиля" +msgstr "Настройки стиля" -#: mouse.cpp:580 +#: mouse.cpp:624 msgid "Select Edge Chain" -msgstr "Выделить Последовательность Примитивов" +msgstr "Выделить последовательность ребер" -#: mouse.cpp:585 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" -msgstr "Переключить Режим Размера Для Справок" +msgstr "Переключить справочный размер" -#: mouse.cpp:591 +#: mouse.cpp:636 msgid "Other Supplementary Angle" -msgstr "Переключить Смежный Угол" +msgstr "Переключить смежный угол" -#: mouse.cpp:596 +#: mouse.cpp:641 msgid "Snap to Grid" -msgstr "Привязать к Сетке" +msgstr "Привязать к сетке" -#: mouse.cpp:604 +#: mouse.cpp:650 msgid "Remove Spline Point" -msgstr "Удалить Точку Сплайна" +msgstr "Удалить точку сплайна" -#: mouse.cpp:615 +#: mouse.cpp:685 msgid "Add Spline Point" -msgstr "Добавить Точку Сплайна" +msgstr "Добавить точку сплайна" -#: mouse.cpp:619 +#: mouse.cpp:689 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "" +"Невозможно добавить точку сплайна: достигнуто ограничение максимального " +"количества точек для сплайна." + +#: mouse.cpp:714 msgid "Toggle Construction" -msgstr "Переключить Режим 'Дополнительные Построения'." +msgstr "Переключить режим «Дополнительные построения»" -#: mouse.cpp:633 +#: mouse.cpp:730 msgid "Delete Point-Coincident Constraint" -msgstr "Удалить Ограничение Совпадения Точек" +msgstr "Удалить ограничение совпадения точек" -#: mouse.cpp:639 +#: mouse.cpp:748 msgid "Cut" msgstr "Вырезать" -#: mouse.cpp:640 +#: mouse.cpp:750 msgid "Copy" msgstr "Копировать" -#: mouse.cpp:643 +#: mouse.cpp:754 msgid "Select All" -msgstr "Выделить Все" +msgstr "Выделить всё" -#: mouse.cpp:647 +#: mouse.cpp:759 msgid "Paste" msgstr "Вставить" -#: mouse.cpp:648 +#: mouse.cpp:761 msgid "Paste Transformed..." -msgstr "Вставить с Трансформацией..." +msgstr "Вставить с трансформацией…" -#: mouse.cpp:652 +#: mouse.cpp:766 msgid "Delete" msgstr "Удалить" -#: mouse.cpp:654 +#: mouse.cpp:769 msgid "Unselect All" -msgstr "Сбросить Выделение" +msgstr "Сбросить выделение" -#: mouse.cpp:660 +#: mouse.cpp:776 msgid "Unselect Hovered" -msgstr "Снять Выделение с Выбранного" +msgstr "Снять выделение с выбранного" -#: mouse.cpp:665 +#: mouse.cpp:785 msgid "Zoom to Fit" -msgstr "Показать Все" - -#: mouse.cpp:805 -msgid "Cannot add spline point: maximum number of points reached." -msgstr "" -"Невозможно добавить точку сплайна: достигнуто ограничение максимального " -"количества точек для сплайна." +msgstr "Показать всё" -#: mouse.cpp:1009 mouse.cpp:1292 +#: mouse.cpp:987 mouse.cpp:1276 msgid "click next point of line, or press Esc" msgstr "кликните мышью там, где хотите расположить следующую точку" -#: mouse.cpp:1015 +#: mouse.cpp:993 msgid "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" "Невозможно начертить прямоугольник, когда рабочая плоскость не активна." -#: mouse.cpp:1049 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" msgstr "кликните мышью там, где хотите расположить другой угол прямоугольника" -#: mouse.cpp:1069 +#: mouse.cpp:1048 msgid "click to set radius" msgstr "кликните, чтобы задать радиус" -#: mouse.cpp:1074 +#: mouse.cpp:1053 msgid "" "Can't draw arc in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "Невозможно создать дугу, когда нет активной рабочей плоскости." -#: mouse.cpp:1093 +#: mouse.cpp:1072 msgid "click to place point" msgstr "кликните мышью там, где хотите создать точку" -#: mouse.cpp:1109 +#: mouse.cpp:1088 msgid "click next point of cubic, or press Esc" -msgstr "" -"кликните мышью там, где хотите создать следующую точку сплайна или нажмите " -"Esc для завершения операции." +msgstr "кликните следующую точку сплайна или нажмите Esc" -#: mouse.cpp:1114 +#: mouse.cpp:1093 msgid "" "Sketching in a workplane already; sketch in 3d before creating new workplane." msgstr "" "Рабочая плоскость уже активна. Перейдите в режим 3d перед созданием новой " "рабочей плоскости." -#: mouse.cpp:1130 +#: mouse.cpp:1109 msgid "" "Can't draw text in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "Невозможно создать текст, когда нет активной рабочей плоскости." -#: mouse.cpp:1146 -msgid "click to place bottom left of text" -msgstr "кликните мышью, чтобы расположить текст" +#: mouse.cpp:1126 +msgid "click to place bottom right of text" +msgstr "кликните, чтобы расположить правый нижний угол текста" -#: mouse.cpp:1152 +#: mouse.cpp:1132 msgid "" "Can't draw image in 3d; first, activate a workplane with Sketch -> In " "Workplane." -msgstr "" +msgstr "Невозможно создать изображение. Активируйте рабочую плоскость." -#: mouse.cpp:1178 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" -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/cocoamain.mm:481 platform/gtkmain.cpp:607 platform/w32main.cpp:451 -#: platform/w32main.cpp:1388 -msgctxt "title" -msgid "(new sketch)" -msgstr "(новый проект)" +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "STL mesh" +msgstr "STL полигональная сетка" -#: platform/cocoamain.mm:710 platform/gtkmain.cpp:912 platform/w32main.cpp:1307 -msgid "(no recent files)" -msgstr "(пусто)" +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Wavefront OBJ полигональная сетка" -#: platform/cocoamain.mm:828 platform/gtkmain.cpp:1020 +#: 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 "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 файл (AutoCAD 2007)" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "HPGL file" +msgstr "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 "AutoCAD DXF и DWG файлы" + +#: platform/gui.cpp:131 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "CSV файлы (значения, разделенные запятой)" + +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" msgstr "без имени" -#: platform/cocoamain.mm:860 -msgid "Do you want to save the changes you made to the new sketch?" -msgstr "Хотите сохранить ваши изменения?" +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 +msgctxt "title" +msgid "Save File" +msgstr "Сохранить файл" -#: platform/cocoamain.mm:862 -msgid "Your changes will be lost if you don't save them." -msgstr "Ваши изменения будут утеряны, если вы их не сохраните." +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 +msgctxt "title" +msgid "Open File" +msgstr "Открыть файл" -#: platform/cocoamain.mm:863 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" -msgid "Save" -msgstr "Сохранить" +msgid "_Cancel" +msgstr "О_тменить" -#: platform/cocoamain.mm:864 platform/cocoamain.mm:905 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" -msgid "Cancel" -msgstr "Отменить" +msgid "_Save" +msgstr "_Сохранить" -#: platform/cocoamain.mm:865 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" -msgid "Don't Save" -msgstr "Не Сохранять" +msgid "_Open" +msgstr "_Открыть" + +#: solvespace.cpp:175 +msgctxt "title" +msgid "Autosave Available" +msgstr "Доступно автосохранение" -#: platform/cocoamain.mm:880 -msgid "An autosave file is available for this project." -msgstr "Файлы автосохранения доступны для данного проекта." +#: solvespace.cpp:176 +msgctxt "dialog" +msgid "An autosave file is available for this sketch." +msgstr "Автоматически сохраненный файл доступен для данного проекта." -#: platform/cocoamain.mm:882 +#: solvespace.cpp:177 +msgctxt "dialog" msgid "Do you want to load the autosave file instead?" -msgstr "Хотите загрузить их вместо открываемого файла?" +msgstr "Хотите загрузить автосохраненный файл вместо исходного?" -#: platform/cocoamain.mm:883 +#: solvespace.cpp:178 msgctxt "button" -msgid "Load" -msgstr "Загрузить Автосохранение" +msgid "&Load autosave" +msgstr "&Загрузить автосохранение" -#: platform/cocoamain.mm:884 +#: solvespace.cpp:180 msgctxt "button" -msgid "Don't Load" -msgstr "Просто Открыть Файл" +msgid "Do&n't Load" +msgstr "&Не загружать" -#: platform/cocoamain.mm:900 -msgid "" -"Do you want to locate it manually?\n" -"If you select “No”, any geometry that depends on the missing file will be " -"removed." -msgstr "" -"Хотите найти их вручную?\n" -"Если вы ответите \"Нет\", то вся геометрия, которая зависит от " -"отсутствующего файла будет удалена." +#: solvespace.cpp:640 +msgctxt "title" +msgid "Modified File" +msgstr "Измененный файл" -#: platform/cocoamain.mm:903 -msgctxt "button" -msgid "Yes" -msgstr "Да" +#: solvespace.cpp:642 +#, c-format +msgctxt "dialog" +msgid "Do you want to save the changes you made to the sketch “%s”?" +msgstr "Сохранить изменения, сделанные в файле “%s”?" -#: platform/cocoamain.mm:906 +#: 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 "No" -msgstr "Нет" +msgid "&Save" +msgstr "&Сохранить" -#: platform/cocoamain.mm:1126 platform/w32main.cpp:183 +#: solvespace.cpp:651 msgctxt "button" -msgid "OK" -msgstr "ХОРОШО" +msgid "Do&n't Save" +msgstr "&Не сохранять" -#: platform/cocoamain.mm:1211 platform/gtkmain.cpp:1382 -#: platform/w32main.cpp:1410 platform/w32main.cpp:1450 +#: solvespace.cpp:672 +msgctxt "title" +msgid "(new sketch)" +msgstr "(новый проект)" + +#: solvespace.cpp:683 msgctxt "title" msgid "Property Browser" msgstr "Браузер" -#: platform/gtkmain.cpp:968 -msgctxt "title" -msgid "Open File" -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 "" +"Ограничения отображаются, и будут экспортированы в файл. Предположительно, " +"это не то, что требуется, если так, необходимо спрятать их, нажав ссылку " +"вверху окна Браузера." -#: platform/gtkmain.cpp:970 -msgid "_Cancel" -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." -#: platform/gtkmain.cpp:971 -msgid "_Open" -msgstr "Открыть" +#: solvespace.cpp:886 +msgid "Constraint must have a label, and must not be a reference dimension." +msgstr "У ограничения должно быть значение и оно не должно быть справочным." -#: platform/gtkmain.cpp:1010 -msgctxt "title" -msgid "Save File" -msgstr "Сохранить Файл" +#: solvespace.cpp:890 +msgid "Bad selection for step dimension; select a constraint." +msgstr "" +"Неправильное выделение для операции изменения значения с заданным шагом; " +"необходимо выбрать ограничение со значением." -#: platform/gtkmain.cpp:1013 platform/gtkmain.cpp:1049 -#: platform/gtkmain.cpp:1097 -msgctxt "button" -msgid "_Cancel" -msgstr "Отменить" +#: solvespace.cpp:914 +msgid "The assembly does not interfere, good." +msgstr "Сборка не содержит пересечения деталей - это хорошо." -#: platform/gtkmain.cpp:1014 platform/gtkmain.cpp:1047 -msgctxt "button" -msgid "_Save" -msgstr "Сохранить" +#: solvespace.cpp:930 +#, c-format +msgid "" +"The volume of the solid model is:\n" +"\n" +" %s" +msgstr "" +"Объем тел:\n" +"\n" +" %s" -#: platform/gtkmain.cpp:1042 platform/w32main.cpp:1167 +#: solvespace.cpp:939 +#, c-format msgid "" -"The file has changed since it was last saved.\n" "\n" -"Do you want to save the changes?" +"The volume of current group mesh is:\n" +"\n" +" %s" msgstr "" -"Файл имеет несохраненные изменения.\n" "\n" -"Хотите сохранить их?" +"Объем тел текущей группы:\n" +"\n" +" %s" -#: platform/gtkmain.cpp:1046 platform/w32main.cpp:1169 -msgctxt "title" -msgid "Modified File" -msgstr "Измененный Файл" +#: 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%." -#: platform/gtkmain.cpp:1048 -msgctxt "button" -msgid "Do_n't Save" -msgstr "Не Сохранять" +#: 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 "" +"Эта группа не содержит замкнутых областей. В ней нет замкнутых контуров, " +"примитивы не лежат в одной плоскости или самопересекаются." -#: platform/gtkmain.cpp:1066 platform/w32main.cpp:1193 +#: solvespace.cpp:980 +#, c-format msgid "" -"An autosave file is available for this project.\n" +"The area of the region sketched in this group is:\n" "\n" -"Do you want to load the autosave file instead?" +" %s\n" +"\n" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." msgstr "" -"Файлы автосохранения доступны для данного проекта.\n" +"Площадь замкнутой области текущей группы:\n" +"\n" +" %s\n" "\n" -"Хотите загрузить их вместо открываемого файла?" +"Кривые аппроксимированы кусочно-линейными функциями.\n" +"Это приводит к ошибке в расчетах, обычно в пределах 1%%." -#: platform/gtkmain.cpp:1070 platform/w32main.cpp:1195 -msgctxt "title" -msgid "Autosave Available" -msgstr "Автосохранение Доступно" +#: 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%%." -#: platform/gtkmain.cpp:1071 -msgctxt "button" -msgid "_Load autosave" -msgstr "Загрузить Автосохранение" +#: solvespace.cpp:1006 +msgid "Bad selection for perimeter; select line segments, arcs, and curves." +msgstr "" +"Неправильное выделение для расчета периметра; необходимо выбирать только " +"отрезки, дуги и кривые." -#: platform/gtkmain.cpp:1072 -msgctxt "button" -msgid "Do_n't Load" -msgstr "Не Загружать" +#: solvespace.cpp:1022 +msgid "Bad selection for trace; select a single point." +msgstr "Неправильное выделение для трассировки; необходимо выбрать одну точку." -#: platform/gtkmain.cpp:1093 platform/w32main.cpp:1223 -msgctxt "title" -msgid "Missing File" -msgstr "Файл Отсутствует" +#: solvespace.cpp:1049 +#, c-format +msgid "Couldn't write to '%s'" +msgstr "Невозможно записать в '%s'" -#: platform/gtkmain.cpp:1094 -msgctxt "button" -msgid "_Yes" -msgstr "Да" +#: solvespace.cpp:1079 +msgid "The mesh is self-intersecting (NOT okay, invalid)." +msgstr "Полигональная сетка содержит самопересечения (это плохо)" -#: platform/gtkmain.cpp:1095 -msgctxt "button" -msgid "_No" -msgstr "Нет" +#: solvespace.cpp:1080 +msgid "The mesh is not self-intersecting (okay, valid)." +msgstr "Полигональная сетка не содержит самопересечений (это хорошо)" -#: platform/gtkmain.cpp:1306 platform/w32main.cpp:179 -msgctxt "title" -msgid "Error" -msgstr "Ошибка" +#: solvespace.cpp:1082 +msgid "The mesh has naked edges (NOT okay, invalid)." +msgstr "Полигональная сетка содержит \"оголенные\" ребра (это плохо)" -#: platform/gtkmain.cpp:1306 platform/w32main.cpp:179 -msgctxt "title" -msgid "Message" -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" -#: style.cpp:160 +#: 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 "" +"Это SolveSpace версии %s.\n" +"\n" +"Для дополнительной информации посетите сайт: http://solvespace.com/\n" +"\n" +"SolveSpace — свободное программное обеспечение: поощряется изменение, " +"улучшение\n" +"распространение программы по условиям лицензии GNU\n" +"General Public License (GPL) версии 3 или новее.\n" +"\n" +"НИКАКИХ ГАРАНТИЙ за пределами прав, \n" +"предусмотренных законом. Дополнительная информация на сайте: http://gnu.org/" +"licenses/\n" +"\n" +"© 2008-%d Джонатан Уэстхьюс и другие авторы.\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." @@ -1667,27 +2174,27 @@ msgstr "" "Невозможно применить стиль к примитиву, который произошел от другого " "примитива. Попробуйте применить стиль к исходному примитиву." -#: style.cpp:659 +#: style.cpp:735 msgid "Style name cannot be empty" -msgstr "Имя стиля не может быть пустым." +msgstr "Имя стиля не может быть пустым" -#: textscreens.cpp:662 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." msgstr "Невозможно сделать повторение меньше, чем 1 раз." -#: textscreens.cpp:666 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." msgstr "Невозможно сделать повтор больше, чем 999 раз." -#: textscreens.cpp:695 +#: textscreens.cpp:866 msgid "Group name cannot be empty" -msgstr "Имя группы не может быть пустым." +msgstr "Имя группы не может быть пустым" -#: textscreens.cpp:739 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." msgstr "Прозрачность должна быть числом от нуля до единицы." -#: textscreens.cpp:778 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "Радиус не может быть нулевым или отрицательным." @@ -1713,7 +2220,7 @@ msgstr "Начертить текст TrueType" #: toolbar.cpp:28 msgid "Sketch image from a file" -msgstr "" +msgstr "Импортировать изображение из файла" #: toolbar.cpp:30 msgid "Create tangent arc at selected point" @@ -1789,103 +2296,332 @@ msgstr "Создать группу выдавливания текущего э #: toolbar.cpp:70 msgid "New group rotating active sketch" -msgstr "Создать группу вращения текущего эскиза" +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:74 +#: toolbar.cpp:78 msgid "New group step and repeat translating" msgstr "Создать группу линейного массива" -#: toolbar.cpp:76 +#: toolbar.cpp:80 msgid "New group in new workplane (thru given entities)" msgstr "Создать группу в новой рабочей плоскости (через выбранные примитивы)" -#: toolbar.cpp:78 +#: toolbar.cpp:82 msgid "New group in 3d" msgstr "Новая группа в 3d" -#: toolbar.cpp:80 +#: toolbar.cpp:84 msgid "New group linking / assembling file" msgstr "Новая группа импорта детали / сборки" -#: toolbar.cpp:84 +#: toolbar.cpp:88 msgid "Nearest isometric view" msgstr "Ближайший изометрический вид" -#: toolbar.cpp:86 +#: toolbar.cpp:90 msgid "Align view to active workplane" -msgstr "Выровнять вид на рабочую плоскость" +msgstr "Выровнять вид по рабочей плоскости" -#: ui.h:69 -msgid "SolveSpace models" -msgstr "проекты SolveSpace" +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "Ошибка" -#: ui.h:74 -msgid "PNG file" -msgstr "PNG изображение" +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "Сообщение" -#: ui.h:79 -msgid "STL mesh" -msgstr "STL полигональная сетка" +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "&OK" -#: ui.h:80 -msgid "Wavefront OBJ mesh" -msgstr "Wavefront OBJ полигональная сетка" +#: view.cpp:127 +msgid "Scale cannot be zero or negative." +msgstr "Масштабный коэффициент не может быть нулевым или отрицательным." -#: ui.h:81 -msgid "Three.js-compatible mesh, with viewer" -msgstr "Three.js-совместимая полигональная сетка с просмторщиком" +#: view.cpp:139 view.cpp:148 +msgid "Bad format: specify x, y, z" +msgstr "Неверный формат: введите данные как x, y, z" -#: ui.h:82 -msgid "Three.js-compatible mesh, mesh only" -msgstr "Three.js-совместимая полигональная сетка" +#~ 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" -#: ui.h:87 ui.h:95 ui.h:103 -msgid "STEP file" -msgstr "STEP файл" +#~ 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" -#: ui.h:92 -msgid "PDF file" -msgstr "PDF документ" +#~ 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" -#: ui.h:93 -msgid "Encapsulated PostScript" -msgstr "Encapsulated PostScript" +#~ 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" -#: ui.h:94 -msgid "Scalable Vector Graphics" -msgstr "SVG изображение" +#~ 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" -#: ui.h:96 ui.h:104 -msgid "DXF file (AutoCAD 2007)" -msgstr "DXF файл (AutoCAD 2007)" +#~ 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" -#: ui.h:97 -msgid "HPGL file" -msgstr "HPGL файл" +#~ msgid "A&ngle" +#~ msgstr "&Угол" -#: ui.h:98 -msgid "G Code" -msgstr "G Code" +#~ msgid "E&qual Length / Radius / Angle" +#~ msgstr "&Равенство Длин / Радиусов / Углов" -#: ui.h:109 -msgid "AutoCAD DXF and DWG files" -msgstr "AutoCAD DXF и DWG файлы" +#~ msgid "" +#~ "Bad selection for length ratio constraint. This constraint can apply to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ msgstr "" +#~ "Неправильное выделение для ограничения 'отношение длин'.\n" +#~ "Ограничение может принимать в качестве выделения следующие примитивы:\n" +#~ "\n" +#~ " * два отрезка\n" -#: ui.h:114 -msgid "Comma-separated values" -msgstr "CSV файлы (значения, разделенные запятой)" +#~ msgid "" +#~ "Bad selection for length difference constraint. This constraint can apply " +#~ "to:\n" +#~ "\n" +#~ " * two line segments\n" +#~ msgstr "" +#~ "Неправильное выделение для ограничения 'разница длин'.\n" +#~ "Ограничение может принимать в качестве выделения следующие примитивы:\n" +#~ "\n" +#~ " * два отрезка\n" -#: view.cpp:78 -msgid "Scale cannot be zero or negative." -msgstr "Масштабный коэффициент не может быть нулевым или отрицательным." +#~ msgid "Length Ra&tio" +#~ msgstr "Отно&шение Длин" -#: view.cpp:90 view.cpp:99 -msgid "Bad format: specify x, y, z" -msgstr "Неверный формат: введите данные как x, y, z" +#~ 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." + +#~ msgid "Show Degrees of &Freedom" +#~ msgstr "Показать Степени С&вободы" + +#~ msgid "click to place bottom left of text" +#~ msgstr "кликните мышью, чтобы расположить текст" + +#~ msgid "Do you want to save the changes you made to the new sketch?" +#~ msgstr "Хотите сохранить ваши изменения?" + +#~ msgid "Your changes will be lost if you don't save them." +#~ msgstr "Ваши изменения будут утеряны, если вы их не сохраните." + +#~ msgctxt "button" +#~ msgid "Save" +#~ msgstr "Сохранить" + +#~ msgctxt "button" +#~ msgid "Cancel" +#~ msgstr "Отменить" + +#~ msgctxt "button" +#~ msgid "Don't Save" +#~ msgstr "Не Сохранять" + +#~ msgid "An autosave file is available for this project." +#~ msgstr "Файлы автосохранения доступны для данного проекта." + +#~ msgid "Do you want to load the autosave file instead?" +#~ msgstr "Хотите загрузить их вместо открываемого файла?" + +#~ msgctxt "button" +#~ msgid "Load" +#~ msgstr "Загрузить Автосохранение" + +#~ msgctxt "button" +#~ msgid "Don't Load" +#~ msgstr "Просто Открыть Файл" + +#~ msgid "" +#~ "Do you want to locate it manually?\n" +#~ "If you select “No”, any geometry that depends on the missing file will be " +#~ "removed." +#~ msgstr "" +#~ "Хотите найти их вручную?\n" +#~ "Если вы ответите \"Нет\", то вся геометрия, которая зависит от " +#~ "отсутствующего файла будет удалена." + +#~ msgctxt "button" +#~ msgid "Yes" +#~ msgstr "Да" + +#~ msgctxt "button" +#~ msgid "No" +#~ msgstr "Нет" + +#~ msgctxt "button" +#~ msgid "OK" +#~ msgstr "ХОРОШО" + +#~ msgid "_Cancel" +#~ msgstr "Отменить" + +#~ msgid "_Open" +#~ msgstr "Открыть" + +#~ msgid "" +#~ "The file has changed since it was last saved.\n" +#~ "\n" +#~ "Do you want to save the changes?" +#~ msgstr "" +#~ "Файл имеет несохраненные изменения.\n" +#~ "\n" +#~ "Хотите сохранить их?" + +#~ msgctxt "button" +#~ msgid "Do_n't Save" +#~ msgstr "Не Сохранять" + +#~ msgid "" +#~ "An autosave file is available for this project.\n" +#~ "\n" +#~ "Do you want to load the autosave file instead?" +#~ msgstr "" +#~ "Файлы автосохранения доступны для данного проекта.\n" +#~ "\n" +#~ "Хотите загрузить их вместо открываемого файла?" + +#~ msgctxt "button" +#~ msgid "_Load autosave" +#~ msgstr "Загрузить Автосохранение" + +#~ msgctxt "button" +#~ msgid "Do_n't Load" +#~ msgstr "Не Загружать" + +#~ msgctxt "button" +#~ msgid "_Yes" +#~ msgstr "Да" + +#~ msgctxt "button" +#~ msgid "_No" +#~ msgstr "Нет" #~ msgid "" #~ "Select two entities that intersect each other (e.g. two lines or two " diff --git a/res/locales/tr_TR.po b/res/locales/tr_TR.po new file mode 100644 index 000000000..097e14afd --- /dev/null +++ b/res/locales/tr_TR.po @@ -0,0 +1,2330 @@ +# Turkish translations for SolveSpace package. +# Copyright (C) 2017 the SolveSpace authors +# This file is distributed under the same license as the SolveSpace package. +# SPDX-FileCopyrightText: 2022, 2025 Mustafa Halil GÖRENTAŞ +# +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: 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: Lokalize 23.08.5\n" + +#: clipboard.cpp:314 +#, fuzzy +msgid "" +"Cut, paste, and copy work only in a workplane.\n" +"\n" +"Activate one with Sketch -> In Workplane." +msgstr "" +"Ç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:331 +msgid "Clipboard is empty; nothing to paste." +msgstr "Pano boş; yapıştırılacak bir şey yok." + +#: 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:394 textscreens.cpp:879 +msgid "Scale cannot be zero." +msgstr "Ölçek sıfır olamaz." + +#: clipboard.cpp:436 +msgid "Select one point to define origin of rotation." +msgstr "Çevirme merkezini tanımlamak için bir nokta seçin." + +#: clipboard.cpp:448 +msgid "Select two points to define translation vector." +msgstr "Doğrusal kopyalama vektörünü tanımlamak için iki nokta seçin." + +#: 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: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ıma şeklinde bölün." + +#: clipboard.cpp:467 +msgid "No workplane active." +msgstr "Etkin çalışma düzlemi yok." + +#: confscreen.cpp:410 +msgid "Bad format: specify coordinates as x, y, z" +msgstr "Hatalı biçim: koordinatları x, y, z olarak belirtin" + +#: 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: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 " +"katsayısının hiçbir etkisi olmayacaktır." + +#: confscreen.cpp:464 confscreen.cpp:474 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "Ondalık basamak sonrası 0 ile %d arasında rakam belirtin." + +#: 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:498 +msgid "Cutter radius offset must not be negative!" +msgstr "Kesici yarıçap ofseti negatif olmamalıdır!" + +#: 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: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 "çakışık-nktlar" + +#: constraint.cpp:13 +msgctxt "constr-name" +msgid "pt-pt-distance" +msgstr "nkt-nkt-mesafe" + +#: constraint.cpp:14 +msgctxt "constr-name" +msgid "pt-line-distance" +msgstr "nkt-çizgi-mesafesi" + +#: constraint.cpp:15 +msgctxt "constr-name" +msgid "pt-plane-distance" +msgstr "nkt-düzlem-mesafesi" + +#: constraint.cpp:16 +msgctxt "constr-name" +msgid "pt-face-distance" +msgstr "nkt-yüzey-mesafesi" + +#: constraint.cpp:17 +msgctxt "constr-name" +msgid "proj-pt-pt-distance" +msgstr "proj-nkt-nkt-mesafesi" + +#: constraint.cpp:18 +msgctxt "constr-name" +msgid "pt-in-plane" +msgstr "düzlemde-nkt" + +#: constraint.cpp:19 +msgctxt "constr-name" +msgid "pt-on-line" +msgstr "çizgide-nkt" + +#: constraint.cpp:20 +msgctxt "constr-name" +msgid "pt-on-face" +msgstr "yüzeyde-nkt" + +#: constraint.cpp:21 +msgctxt "constr-name" +msgid "eq-length" +msgstr "eşit-uzunluk" + +#: constraint.cpp:22 +msgctxt "constr-name" +msgid "eq-length-and-pt-ln-dist" +msgstr "eşit-uzunluk-ve-çzg-nkt-mesafesi" + +#: constraint.cpp:23 +msgctxt "constr-name" +msgid "eq-pt-line-distances" +msgstr "eşit-nkt-çizgi-mesafesi" + +#: constraint.cpp:24 +msgctxt "constr-name" +msgid "length-ratio" +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: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:31 +msgctxt "constr-name" +msgid "symmetric-h" +msgstr "simetrik-y" + +#: constraint.cpp:32 +msgctxt "constr-name" +msgid "symmetric-v" +msgstr "simetrik-d" + +#: constraint.cpp:33 +msgctxt "constr-name" +msgid "symmetric-line" +msgstr "simetrik-çizgi" + +#: constraint.cpp:34 +msgctxt "constr-name" +msgid "at-midpoint" +msgstr "orta noktada" + +#: constraint.cpp:35 +msgctxt "constr-name" +msgid "horizontal" +msgstr "yatay" + +#: constraint.cpp:36 +msgctxt "constr-name" +msgid "vertical" +msgstr "dikey" + +#: constraint.cpp:37 +msgctxt "constr-name" +msgid "diameter" +msgstr "çap" + +#: constraint.cpp:38 +msgctxt "constr-name" +msgid "pt-on-circle" +msgstr "çemberde-nkt" + +#: constraint.cpp:39 +msgctxt "constr-name" +msgid "same-orientation" +msgstr "aynı-yön" + +#: constraint.cpp:40 +msgctxt "constr-name" +msgid "angle" +msgstr "açı" + +#: constraint.cpp:41 +msgctxt "constr-name" +msgid "parallel" +msgstr "paralel" + +#: constraint.cpp:42 +msgctxt "constr-name" +msgid "arc-line-tangent" +msgstr "yay-çizgi-teğet" + +#: constraint.cpp:43 +msgctxt "constr-name" +msgid "cubic-line-tangent" +msgstr "k.eğri-çizgi-teğet" + +#: constraint.cpp:44 +msgctxt "constr-name" +msgid "curve-curve-tangent" +msgstr "eğri-eğri-teğet" + +#: constraint.cpp:45 +msgctxt "constr-name" +msgid "perpendicular" +msgstr "dik" + +#: constraint.cpp:46 +msgctxt "constr-name" +msgid "eq-radius" +msgstr "eşit-yarıçap" + +#: constraint.cpp:47 +msgctxt "constr-name" +msgid "eq-angle" +msgstr "eşit-açı" + +#: constraint.cpp:48 +msgctxt "constr-name" +msgid "eq-line-len-arc-len" +msgstr "eşit-çizgi-uzn-yay-uzn" + +#: constraint.cpp:49 +msgctxt "constr-name" +msgid "lock-where-dragged" +msgstr "sürüklendiği-yerde-kilitli" + +#: constraint.cpp:50 +msgctxt "constr-name" +msgid "comment" +msgstr "yorum" + +#: 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 "" +"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 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 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: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.\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ğ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: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 "" +"Mesafe / çap kısıtlaması oluşturmak için hatalı seçim. Bu kısıtlama şunlara " +"uygulanabilir:\n" +"\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: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 "" +"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 (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: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 "" +"Eşit uzunluk / yarıçap kısıtlaması oluşturmak için hatalı seçim. Bu " +"kısıtlama şunlara uygulanabilir:\n" +"\n" +" * 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: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ı 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 yay\n" +" * bir yay ve bir çizgi parçası\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 "" +"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 yay\n" +" * bir yay ve bir çizgi parçası\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 "" +"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 (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: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 "" +"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 " +"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:623 +msgid "" +"A workplane must be active when constraining symmetric without an explicit " +"symmetry plane." +msgstr "" +"Simetri kısıtlamasında simetri düzlemini olarak kullanılacak bir çalışma " +"düzlemi etkin olmalıdır." + +#: constraint.cpp:663 +msgid "" +"Activate a workplane (with Sketch -> In Workplane) before applying a " +"horizontal or vertical constraint." +msgstr "" +"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: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 "" +"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" + +#: constraint.cpp:697 +msgid "" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" +"\n" +" * two normals\n" +msgstr "" +"Aynı yön kısıtlaması oluşturmak için hatalı seçim. Bu kısıtlama şunlara " +"uygulanabilir:\n" +"\n" +" * iki normal\n" + +#: constraint.cpp:748 +msgid "Must select an angle constraint." +msgstr "Bir açı kısıtlaması seçilmelidir." + +#: constraint.cpp:761 +msgid "Must select a constraint with associated label." +msgstr "İlgili etikete sahip bir kısıtlama seçilmelidir." + +#: 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çı kısıtlaması oluşturmak için hatalı seçim. Bu kısıtlama şunlara " +"uygulanabilir:\n" +"\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: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: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 "" +"Paralel / Teğet kısıtlaması oluşturmak için hatalı seçim. Bu kısıtlama " +"şunlara uygulanabilir:\n" +"\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: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 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 bir normal\n" +" * iki normal\n" + +#: 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ı kısıtlamak için hatalı seçim. Bu kısıtlama " +"şunlara uygulanabilir:\n" +"\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:952 +msgid "click center of comment text" +msgstr "yorum metninin ortasını tıklatın" + +#: 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 "" +"Katı model yok; katılama ve döndürme komutları ile bir model çizin veya açık " +"çizgileri ve eğrileri dışa vermek için Görüntüyü 2d olarak Dışa Aktar'ı " +"kullanın." + +#: 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 "" +"Kesiti Dışa Aktarmak için hatalı seçim. Lütfen şunlardan birini seçin:\n" +"\n" +" * 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 ve paralel çizgiler boyunca " +"düzlem)\n" + +#: export.cpp:818 +msgid "Active group mesh is empty; nothing to export." +msgstr "Etkin grup ağı boş; dışa aktarılacak bir şey yok." + +#: exportvector.cpp:336 +msgid "freehand lines were replaced with continuous lines" +msgstr "serbest çizgiler, sürekli çizgilerle değiştirildi" + +#: exportvector.cpp:338 +msgid "zigzag lines were replaced with continuous lines" +msgstr "zikzak çizgiler sürekli çizgilerle değiştirildi" + +#: 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:838 +msgid "" +"PDF page size exceeds 200 by 200 inches; many viewers may reject this file." +msgstr "" +"PDF sayfa boyutu 200 x 200 inç'i aşıyor; birçok görüntüleyici bu dosyayı " +"reddedebilir." + +#: file.cpp:44 group.cpp:91 +msgctxt "group-name" +msgid "sketch-in-plane" +msgstr "düzlemde çizim" + +#: file.cpp:62 +msgctxt "group-name" +msgid "#references" +msgstr "#referanslar" + +#: file.cpp:555 +msgid "The file is empty. It may be corrupt." +msgstr "Dosya boş. Bozuk olabilir." + +#: file.cpp:560 +msgid "" +"Unrecognized data in file. This file may be corrupt, or from a newer version " +"of the program." +msgstr "" +"Dosyada veriler tanınmadı. Bu dosya bozuk veya programın daha yeni bir " +"sürümü ile oluşturulmuş olabilir." + +#: file.cpp:876 +msgctxt "title" +msgid "Missing File" +msgstr "Kayıp Dosya" + +#: file.cpp:877 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "\"%s\" bağlantılı dosya yok." + +#: 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 "" +"Konumu El ile bulmak istiyor musunuz?\n" +"\n" +"Reddederseniz, eksik dosyaya bağlı olan geometri kalıcı olarak " +"kaldırılacaktır." + +#: file.cpp:882 +msgctxt "button" +msgid "&Yes" +msgstr "&Evet" + +#: file.cpp:884 +msgctxt "button" +msgid "&No" +msgstr "&Hayır" + +#: file.cpp:886 solvespace.cpp:652 +msgctxt "button" +msgid "&Cancel" +msgstr "&İptal" + +#: graphicswin.cpp:41 +msgid "&File" +msgstr "&Dosya" + +#: graphicswin.cpp:42 +msgid "&New" +msgstr "&Yeni" + +#: graphicswin.cpp:43 +msgid "&Open..." +msgstr "&Aç..." + +#: graphicswin.cpp:44 +msgid "Open &Recent" +msgstr "&Son Erişilenden Aç" + +#: graphicswin.cpp:45 +msgid "&Save" +msgstr "&Kaydet" + +#: graphicswin.cpp:46 +msgid "Save &As..." +msgstr "&Farklı kaydet..." + +#: graphicswin.cpp:48 +msgid "Export &Image..." +msgstr "&Resim olarak dışa aktar..." + +#: graphicswin.cpp:49 +msgid "Export 2d &View..." +msgstr "&Görüntüyü 2d olarak dışa aktar..." + +#: graphicswin.cpp:50 +msgid "Export 2d &Section..." +msgstr "&Kesiti 2d olarak dışa aktar..." + +#: graphicswin.cpp:51 +msgid "Export 3d &Wireframe..." +msgstr "3d &TelKafes olarak dışa aktar..." + +#: graphicswin.cpp:52 +msgid "Export Triangle &Mesh..." +msgstr "&Üçgensel Ağ olarak dışa aktar..." + +#: graphicswin.cpp:53 +msgid "Export &Surfaces..." +msgstr "Y&üzeyleri dışa aktar..." + +#: graphicswin.cpp:54 +msgid "Im&port..." +msgstr "&İçe Aktar..." + +#: graphicswin.cpp:57 +msgid "E&xit" +msgstr "&Çıkış" + +#: graphicswin.cpp:60 +msgid "&Edit" +msgstr "D&üzen" + +#: graphicswin.cpp:61 +msgid "&Undo" +msgstr "&Geri al" + +#: graphicswin.cpp:62 +msgid "&Redo" +msgstr "&Yinele" + +#: graphicswin.cpp:63 +msgid "Re&generate All" +msgstr "Tümünü Yeniden &Oluştur" + +#: graphicswin.cpp:65 +msgid "Snap Selection to &Grid" +msgstr "Seçimi &Izgaraya Tuttur" + +#: graphicswin.cpp:66 +msgid "Rotate Imported &90°" +msgstr "İçe Aktarılanları &90° Döndür" + +#: graphicswin.cpp:68 +msgid "Cu&t" +msgstr "&Kes" + +#: graphicswin.cpp:69 +msgid "&Copy" +msgstr "K&opyala" + +#: graphicswin.cpp:70 +msgid "&Paste" +msgstr "Y&apıştır" + +#: graphicswin.cpp:71 +msgid "Paste &Transformed..." +msgstr "&Dönüştürerek Yapıştır..." + +#: graphicswin.cpp:72 +msgid "&Delete" +msgstr "&Sil" + +#: graphicswin.cpp:74 +msgid "Select &Edge Chain" +msgstr "Kenar &Zincirini Seç" + +#: graphicswin.cpp:75 +msgid "Select &All" +msgstr "&Tümünü Seç" + +#: graphicswin.cpp:76 +msgid "&Unselect All" +msgstr "Tüm Seçimi &Kaldır" + +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "&Çizgi Biçimi..." + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "&Projeksiyonu Görüntüle..." + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "Y&apılandır..." + +#: graphicswin.cpp:84 +msgid "&View" +msgstr "&Görünüm" + +#: graphicswin.cpp:85 +msgid "Zoom &In" +msgstr "&Yakınlaş" + +#: graphicswin.cpp:86 +msgid "Zoom &Out" +msgstr "&Uzaklaş" + +#: graphicswin.cpp:87 +msgid "Zoom To &Fit" +msgstr "&Sığacak Şekilde Yakınlaş" + +#: graphicswin.cpp:89 +msgid "Align View to &Workplane" +msgstr "Görünümü &Çalışma Düzlemine Hizala" + +#: graphicswin.cpp:90 +msgid "Nearest &Ortho View" +msgstr "En Yakın &Dik Görünüm" + +#: graphicswin.cpp:91 +msgid "Nearest &Isometric View" +msgstr "En Yakın &İzometrik Görünüm" + +#: graphicswin.cpp:92 +msgid "&Center View At Point" +msgstr "&Noktayı Merkezde Görüntüle" + +#: graphicswin.cpp:94 +msgid "Show Snap &Grid" +msgstr "Yakalama &Izgarasını Göster" + +#: graphicswin.cpp:95 +msgid "Darken Inactive Solids" +msgstr "Aktif Olmayan Katıları &Koyulaştır" + +#: graphicswin.cpp:96 +msgid "Use &Perspective Projection" +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:99 +msgid "Dimensions in &Millimeters" +msgstr "&Milimetre cinsinden ölçü" + +#: graphicswin.cpp:100 +msgid "Dimensions in M&eters" +msgstr "M&etre cinsinden ölçü" + +#: 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:105 +msgid "Show Property Bro&wser" +msgstr "&Özellik Penceresini Göster" + +#: graphicswin.cpp:107 +msgid "&Full Screen" +msgstr "&Tam Ekran" + +#: graphicswin.cpp:109 +msgid "&New Group" +msgstr "Yeni &Grup" + +#: graphicswin.cpp:110 +msgid "Sketch In &3d" +msgstr "&3d'de Çizim Yap" + +#: graphicswin.cpp:111 +msgid "Sketch In New &Workplane" +msgstr "&Yeni Çalışma Düzleminde Çizim Yap" + +#: graphicswin.cpp:113 +msgid "Step &Translating" +msgstr "&Doğrusal Kopya" + +#: graphicswin.cpp:114 +msgid "Step &Rotating" +msgstr "D&airesel Kopya" + +#: graphicswin.cpp:116 +msgid "E&xtrude" +msgstr "&Uzatarak Katıla" + +#: graphicswin.cpp:117 +msgid "&Helix" +msgstr "&Helis" + +#: graphicswin.cpp:118 +msgid "&Lathe" +msgstr "&Tam döndürerek katıla" + +#: graphicswin.cpp:119 +msgid "Re&volve" +msgstr "&Kısmen döndürerek katıla" + +#: graphicswin.cpp:121 +msgid "Link / Assemble..." +msgstr "Bağla / Montajla..." + +#: graphicswin.cpp:122 +msgid "Link Recent" +msgstr "Son Erişilenden Bağla" + +#: graphicswin.cpp:124 +msgid "&Sketch" +msgstr "&Çizim" + +#: graphicswin.cpp:125 +msgid "In &Workplane" +msgstr "Ç&alışma Düzleminde" + +#: graphicswin.cpp:126 +msgid "Anywhere In &3d" +msgstr "&3d'de Herhangi Bir Yerde" + +#: graphicswin.cpp:128 +msgid "Datum &Point" +msgstr "Referasn &Noktası" + +#: graphicswin.cpp:129 +msgid "Wor&kplane" +msgstr "Ça&lışma Düzlemi" + +#: graphicswin.cpp:131 +msgid "Line &Segment" +msgstr "Ç&izgi" + +#: graphicswin.cpp:132 +msgid "C&onstruction Line Segment" +msgstr "&Yardımcı Çizgi" + +#: graphicswin.cpp:133 +msgid "&Rectangle" +msgstr "&Dikdörtgen" + +#: graphicswin.cpp:134 +msgid "&Circle" +msgstr "D&aire" + +#: graphicswin.cpp:135 +msgid "&Arc of a Circle" +msgstr "Çember &Yayı" + +#: graphicswin.cpp:136 +msgid "&Bezier Cubic Spline" +msgstr "Bezier Kübik &Eğri" + +#: graphicswin.cpp:138 +msgid "&Text in TrueType Font" +msgstr "TrueType Yazı Tipinde &Metin" + +#: 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:142 +msgid "Ta&ngent Arc at Point" +msgstr "&Yuvarlat (Radyus)" + +#: graphicswin.cpp:143 +msgid "Split Curves at &Intersection" +msgstr "Eğrileri Kesişim Yerinden &Böl" + +#: graphicswin.cpp:145 +msgid "&Constrain" +msgstr "&Kısıtla" + +#: graphicswin.cpp:146 +msgid "&Distance / Diameter" +msgstr "&Mesafe / Çap" + +#: graphicswin.cpp:147 +msgid "Re&ference Dimension" +msgstr "&Referans Ölçü" + +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "&Açı / Eşit Açı" + +#: graphicswin.cpp:149 +msgid "Reference An&gle" +msgstr "Referans A&çı" + +#: graphicswin.cpp:150 +msgid "Other S&upplementary Angle" +msgstr "Diğer &Bütünler Açı" + +#: graphicswin.cpp:151 +msgid "Toggle R&eference Dim" +msgstr "Ölçüyü Re&ferans Yap / Yapma" + +#: graphicswin.cpp:153 +msgid "&Horizontal" +msgstr "&Yatay" + +#: graphicswin.cpp:154 +msgid "&Vertical" +msgstr "&Dikey" + +#: graphicswin.cpp:156 +msgid "&On Point / Curve / Plane" +msgstr "&Noktada / Eğride / Düzlemde" + +#: graphicswin.cpp:157 +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:161 +msgid "S&ymmetric" +msgstr "&Simetrik" + +#: graphicswin.cpp:162 +msgid "Para&llel / Tangent" +msgstr "&Paralel / Teğet" + +#: graphicswin.cpp:163 +msgid "&Perpendicular" +msgstr "D&ik" + +#: graphicswin.cpp:164 +msgid "Same Orient&ation" +msgstr "Aynı &Yön" + +#: graphicswin.cpp:165 +msgid "Lock Point Where &Dragged" +msgstr "Sürüklendiği Yerde Noktayı &Kilitle" + +#: graphicswin.cpp:167 +msgid "Comment" +msgstr "Y&orum" + +#: graphicswin.cpp:169 +msgid "&Analyze" +msgstr "&Analiz" + +#: graphicswin.cpp:170 +msgid "Measure &Volume" +msgstr "&Hacmi Hesapla" + +#: graphicswin.cpp:171 +msgid "Measure A&rea" +msgstr "&Alanı Hesapla" + +#: graphicswin.cpp:172 +msgid "Measure &Perimeter" +msgstr "&Çevre Uzunluğunu Hesapla" + +#: graphicswin.cpp:173 +msgid "Show &Interfering Parts" +msgstr "&Engelleyici Parçaları Göster" + +#: graphicswin.cpp:174 +msgid "Show &Naked Edges" +msgstr "A&çık Kenarları Göster" + +#: graphicswin.cpp:175 +msgid "Show &Center of Mass" +msgstr "&Kütle Merkezini Göster" + +#: graphicswin.cpp:177 +msgid "Show &Underconstrained Points" +msgstr "Kı&sıtlanmamış Noktaları Göster" + +#: graphicswin.cpp:179 +msgid "&Trace Point" +msgstr "&İzlenecek Nokta" + +#: graphicswin.cpp:180 +msgid "&Stop Tracing..." +msgstr "&İzlemeyi Durdur..." + +#: graphicswin.cpp:181 +msgid "Step &Dimension..." +msgstr "Adım &Ölçüsünü Ayarla..." + +#: graphicswin.cpp:183 +msgid "&Help" +msgstr "&Yardım" + +#: graphicswin.cpp:184 +msgid "&Language" +msgstr "&Dil" + +#: graphicswin.cpp:185 +msgid "&Website / Manual" +msgstr "&Web sitesi / Kılavuz" + +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "GitHub Commitlere git" + +#: graphicswin.cpp:188 +msgid "&About" +msgstr "&Hakkında" + +#: graphicswin.cpp:362 +msgid "(no recent files)" +msgstr "(son dosyalar yok)" + +#: graphicswin.cpp:370 +#, c-format +msgid "File '%s' does not exist." +msgstr "'%s' dosyası mevcut değil." + +#: 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: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 "" +"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, yapılandırma ekranındaki perspektif " +"katsayısını değiştirin. 0.3 civarında bir değer normaldir." + +#: 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 olacaktır." + +#: 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şmıyor." + +#: 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 "" +"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:1234 +msgid "" +"No workplane is active. Activate a workplane (with Sketch -> In Workplane) " +"to define the plane for the snap grid." +msgstr "" +"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: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 öğ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: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: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ç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: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 (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: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:1362 +msgid "click to place datum point" +msgstr "referans noktayı yerleştirmek için tıklayın" + +#: 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:1365 +msgid "click first point of construction line segment" +msgstr "yardımcı çizginin ilk noktası için tıklayın" + +#: graphicswin.cpp:1366 +msgid "click first point of cubic segment" +msgstr "kübik eğri parçanın ilk noktası için tıklayın" + +#: graphicswin.cpp:1367 +msgid "click center of circle" +msgstr "dairenin merkez konumu için tıklayın" + +#: graphicswin.cpp:1368 +msgid "click origin of workplane" +msgstr "çalışma düzleminin merkez konumu için tıklayın" + +#: graphicswin.cpp:1369 +msgid "click one corner of rectangle" +msgstr "dikdörtgenin bir köşe noktası için tıklayın" + +#: graphicswin.cpp:1370 +msgid "click top left of text" +msgstr "metnin sol üst köşe konumu için tıklayın" + +#: graphicswin.cpp:1376 +msgid "click top left of image" +msgstr "görüntünün sol üst köşe konumu için tıklayın" + +#: 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ı değiştirmeye çalışmadan önce öğeleri " +"seçin." + +#: group.cpp:86 +msgctxt "group-name" +msgid "sketch-in-3d" +msgstr "3d-de-çizim" + +#: 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 çizim oluşturmak için hatalı seçim. Bu grup " +"aşağıdakilerle oluşturulabilir:\n" +"\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: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üzlemine dik olarak katılanacaktır." + +#: group.cpp:179 +msgctxt "group-name" +msgid "extrude" +msgstr "doğrusal katıla" + +#: group.cpp:184 +msgid "Lathe operation can only be applied to planar sketches." +msgstr "" +"Tam tur döndürerek katılama işlemi, yalnızca düzlemsel çizimlere " +"uygulanabilir." + +#: 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 "" +"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 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" + +#: group.cpp:205 +msgctxt "group-name" +msgid "lathe" +msgstr "tamamen döndürerek katıla" + +#: group.cpp:210 +msgid "Revolve operation can only be applied to planar sketches." +msgstr "" +"Kısmen döndürerek katılama işlemi, yalnızca düzlemsel çizimlere " +"uygulanabilir." + +#: 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 "" +"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:233 +msgctxt "group-name" +msgid "revolve" +msgstr "kısmen döndürerek katıla" + +#: 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: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 "" +"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:261 +msgctxt "group-name" +msgid "helix" +msgstr "helis" + +#: 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 "" +"Yeni dairesel kopyalama/çoğaltma oluşturmak için hatalı seçim. Bu grup " +"şunlarla oluşturulabilir:\n" +"\n" +" * 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 ( çizgiye / normale paralel ve " +"noktadan geçen bir eksen etrafında çevirin)\n" + +#: group.cpp:287 +msgctxt "group-name" +msgid "rotate" +msgstr "dairesel kopya" + +#: group.cpp:298 +msgctxt "group-name" +msgid "translate" +msgstr "doğrusal kopya" + +#: group.cpp:422 +msgid "(unnamed)" +msgstr "(isimsiz)" + +#: groupmesh.cpp:710 +msgid "not closed contour, or not all same style!" +msgstr "kapali olmayan kontur veya tümü ayni bicimde degil!" + +#: groupmesh.cpp:723 +msgid "points not all coplanar!" +msgstr "noktaların hepsi aynı düzlemde değil!" + +#: groupmesh.cpp:725 +msgid "contour is self-intersecting!" +msgstr "kontur kendisiyle kesisiyor!" + +#: groupmesh.cpp:727 +msgid "zero-length edge!" +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: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: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 (yardımcı) referans-olmayan " +"çizginin veya dairenin ve çalışma düzleminin birleştiği bir nokta seçin." + +#: 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 " +"kısıtlamaları ile istenen geometriyi elle oluşturmayı deneyin." + +#: modify.cpp:595 +msgid "Couldn't split this entity; lines, circles, or cubics only." +msgstr "" +"Bu öğe bölünemedi; yalnızca çizgiler, daireler veya kübik eğriler " +"bölünebilir." + +#: modify.cpp:622 +msgid "Must be sketching in workplane to split." +msgstr "Bölmek için çalışma düzleminde çizim yapılıyor olmalı." + +#: 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 / daire / yay veya bir " +"çizgi / daire / yay ve bir nokta)." + +#: modify.cpp:734 +msgid "Can't split; no intersection found." +msgstr "Bölünemez; kesişim bulunamadı." + +#: mouse.cpp:558 +msgid "Assign to Style" +msgstr "Biçim Ata" + +#: mouse.cpp:574 +msgid "No Style" +msgstr "Biçim Yok" + +#: mouse.cpp:577 +msgid "Newly Created Custom Style..." +msgstr "Yeni Oluşturulan Özel Biçim ..." + +#: mouse.cpp:584 +msgid "Group Info" +msgstr "Grup Bilgisi" + +#: mouse.cpp:604 +msgid "Style Info" +msgstr "Biçim Bİlgisi" + +#: mouse.cpp:624 +msgid "Select Edge Chain" +msgstr "Kenar Zinciri Seç" + +#: mouse.cpp:630 +msgid "Toggle Reference Dimension" +msgstr "Referans Ölçüyü Değiştir" + +#: mouse.cpp:636 +msgid "Other Supplementary Angle" +msgstr "Diğer Bütünler Açı" + +#: mouse.cpp:641 +msgid "Snap to Grid" +msgstr "Izgaraya Tuttur" + +#: mouse.cpp:650 +msgid "Remove Spline Point" +msgstr "Eğri Noktasını Sil" + +#: mouse.cpp:685 +msgid "Add Spline Point" +msgstr "Eğri Noktası Ekle" + +#: 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:714 +msgid "Toggle Construction" +msgstr "Yapıyı değiştir" + +#: mouse.cpp:730 +msgid "Delete Point-Coincident Constraint" +msgstr "Çakışık-Nokta Kısıtlamasını Sil" + +#: mouse.cpp:748 +msgid "Cut" +msgstr "Kes" + +#: mouse.cpp:750 +msgid "Copy" +msgstr "Kopyala" + +#: mouse.cpp:754 +msgid "Select All" +msgstr "Tümünü Seç" + +#: mouse.cpp:759 +msgid "Paste" +msgstr "Yapıştır" + +#: mouse.cpp:761 +msgid "Paste Transformed..." +msgstr "Dönüştürerek Yapıştır..." + +#: mouse.cpp:766 +msgid "Delete" +msgstr "Sil" + +#: mouse.cpp:769 +msgid "Unselect All" +msgstr "Tüm Seçimi Kaldır" + +#: mouse.cpp:776 +msgid "Unselect Hovered" +msgstr "Üzerine Gelinen Seçimi Kaldır" + +#: mouse.cpp:785 +msgid "Zoom to Fit" +msgstr "Sığdırmak için Yakınlaştır" + +#: 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:993 +msgid "" +"Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"3d'de dikdörtgen çizilemez; önce Çizim -> Çalışma Düzleminde menüsü ile bir " +"çalışma düzlemini etkinleştirin." + +#: mouse.cpp:1027 +msgid "click to place other corner of rectangle" +msgstr "dikdörtgenin diğer köşesini belirtmek için tıklayın" + +#: mouse.cpp:1048 +msgid "click to set radius" +msgstr "yarıçapı ayarlamak için tıklayın" + +#: mouse.cpp:1053 +msgid "" +"Can't draw arc in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"3d'de yay çizemez; önce Çizim -> Çalışma Düzleminde menüsü ile bir çalışma " +"düzlemini etkinleştirin." + +#: mouse.cpp:1072 +msgid "click to place point" +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 eğri noktasını tıklayın veya Esc tuşuna basın" + +#: mouse.cpp:1093 +msgid "" +"Sketching in a workplane already; sketch in 3d before creating new workplane." +msgstr "" +"Zaten bir çalışma düzleminde çizim yapılıyor; 3d'de çizim yapmadan önce yeni " +"çalışma düzlemi oluşturun." + +#: mouse.cpp:1109 +msgid "" +"Can't draw text in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"3d'de metin yazılamaz; önce Çizim -> Çalışma Düzleminde menüsü ile bir " +"çalışma düzlemini etkinleştirin." + +#: mouse.cpp:1126 +msgid "click to place bottom right of text" +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 görüntü/resim eklenemez; önce Çizim -> Çalışma Düzleminde menüsü ile " +"bir çalışma düzlemini etkinleştirin." + +#: platform/gui.cpp:85 platform/gui.cpp:90 solvespace.cpp:583 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "SolveSpace Modelleri" + +#: 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: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 Görüntüsü" + +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "STL mesh" +msgstr "STL mesh (ağ/kafes)" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Wavefront OBJ mesh (ağ/kafes)" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "Görüntüleyicili, Three.js-uyumlu mesh (ağ/kafes)" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "Three.js-uyumlu mesh, sadece mesh (ağ/kafes)" + +#: platform/gui.cpp:104 +msgctxt "file-type" +msgid "VRML text file" +msgstr "VRML metin dosyası" + +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "STEP file" +msgstr "STEP dosyası" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "PDF file" +msgstr "PDF dosyası" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "Encapsulated PostScript (EPS)" + +#: platform/gui.cpp:114 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "Ölçeklenebilir Vektör Grafikleri (SVG)" + +#: platform/gui.cpp:116 platform/gui.cpp:123 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "DXF dosyası (AutoCAD 2007)" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "HPGL file" +msgstr "HPGL dosyası" + +#: platform/gui.cpp:118 +msgctxt "file-type" +msgid "G Code" +msgstr "G Kodu" + +#: platform/gui.cpp:127 +msgctxt "file-type" +msgid "AutoCAD DXF and DWG files" +msgstr "AutoCAD DXF ve DWG dosyaları" + +#: platform/gui.cpp:131 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "Virgülle ayrılmış değerler (CSV)" + +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 +msgid "untitled" +msgstr "isimsiz" + +#: 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:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 +msgctxt "title" +msgid "Open File" +msgstr "Dosyayı Aç" + +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 +msgctxt "button" +msgid "_Cancel" +msgstr "_İptal" + +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 +msgctxt "button" +msgid "_Save" +msgstr "_Kaydet" + +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 +msgctxt "button" +msgid "_Open" +msgstr "_Aç" + +#: solvespace.cpp:175 +msgctxt "title" +msgid "Autosave Available" +msgstr "Otomatik Kaydetme Kullanılabilir" + +#: 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: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:178 +msgctxt "button" +msgid "&Load autosave" +msgstr "&Otomatik kaydı yükle" + +#: solvespace.cpp:180 +msgctxt "button" +msgid "Do&n't Load" +msgstr "&Yükleme" + +#: solvespace.cpp:640 +msgctxt "title" +msgid "Modified File" +msgstr "Değiştirilen Dosya" + +#: 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: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:648 +msgctxt "dialog" +msgid "Your changes will be lost if you don't save them." +msgstr "Bunları kaydetmezseniz değişiklikleriniz kaybolur." + +#: solvespace.cpp:649 +msgctxt "button" +msgid "&Save" +msgstr "&Kaydet" + +#: solvespace.cpp:651 +msgctxt "button" +msgid "Do&n't Save" +msgstr "K&aydetme" + +#: solvespace.cpp:672 +msgctxt "title" +msgid "(new sketch)" +msgstr "(yeni çizim)" + +#: solvespace.cpp:683 +msgctxt "title" +msgid "Property Browser" +msgstr "Özellik Penceresi" + +#: 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 "" +"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:834 +#, c-format +msgid "" +"Can't identify file type from file extension of filename '%s'; try .dxf or ." +"dwg." +msgstr "" +"'%s' dosya adının dosya uzantısından dosya türü belirlenemiyor; .dxf veya ." +"dwg'yi deneyin." + +#: solvespace.cpp:886 +msgid "Constraint must have a label, and must not be a reference dimension." +msgstr "Kısıtlamanın bir etiketi olmalı ve bir referans ölçüsü olmamalıdır." + +#: solvespace.cpp:890 +msgid "Bad selection for step dimension; select a constraint." +msgstr "Adım ölçüsü ayarlamak için hatalı seçim; bir kısıtlama seçin." + +#: solvespace.cpp:914 +msgid "The assembly does not interfere, good." +msgstr "Montajda engel oluşturan parça yok, iyi." + +#: solvespace.cpp:930 +#, c-format +msgid "" +"The volume of the solid model is:\n" +"\n" +" %s" +msgstr "" +"Katı modelin hacmi:\n" +"\n" +" % s" + +#: solvespace.cpp:939 +#, c-format +msgid "" +"\n" +"The volume of current group mesh is:\n" +"\n" +" %s" +msgstr "" +"\n" +"Mevcut ağ (kafes) grubunun hacmi:\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" +"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: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 "" +"Seçili yüzlerin yüzey alanı:\n" +"\n" +" %s\n" +"\n" +"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: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 " +"değil veya kendisiyle kesişiyor." + +#: 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 "" +"Bu grupta çizilen bölgenin alanı:\n" +"\n" +" %s\n" +"\n" +"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: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 "" +"Seçilen öğelerin toplam uzunluğu:\n" +"\n" +" %s\n" +"\n" +"Eğriler, parçalı doğrusal olarak yaklaştırıldı.\n" +"Bu, genel olarak yaklaşık 1%% hataya neden olur." + +#: solvespace.cpp:1006 +msgid "Bad selection for perimeter; select line segments, arcs, and curves." +msgstr "" +"Çevre uzunluğunu hesabı için hatalı seçim; çizgi parçalarını, yayları ve " +"eğrileri seçin." + +#: solvespace.cpp:1022 +msgid "Bad selection for trace; select a single point." +msgstr "Nokta izleme işlemi için hatalı seçim; tek bir nokta seçin." + +#: solvespace.cpp:1049 +#, c-format +msgid "Couldn't write to '%s'" +msgstr "\"%s\" öğesine yazılamadı" + +#: solvespace.cpp:1079 +msgid "The mesh is self-intersecting (NOT okay, invalid)." +msgstr "Ağ (Kafes) kendisiyle kesişiyor (İyi DEĞİL, geçersiz)." + +#: solvespace.cpp:1080 +msgid "The mesh is not self-intersecting (okay, valid)." +msgstr "Ağ (Kafes) kendisi ile kesişmiyor (iyi, geçerli)." + +#: solvespace.cpp:1082 +msgid "The mesh has naked edges (NOT okay, invalid)." +msgstr "Ağın (Kafesin) açık kenarları var (iyi DEĞİL, geçersiz)." + +#: solvespace.cpp:1083 +msgid "The mesh is watertight (okay, valid)." +msgstr "Ağ (Kafes) çok sıkı (iyi, geçerli)" + +#: solvespace.cpp:1086 +#, c-format +msgid "" +"\n" +"\n" +"The model contains %d triangles, from %d surfaces." +msgstr "" +"\n" +"\n" +"Model, %d yüzeyden %d üçgen içeriyor." + +#: 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ıfır sorunlu kenar, iyi.%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 sorunlu kenar, kötü.%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 "" +"Bu SolveSpace'in %s sürümüdür.\n" +"\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ümleri altında \n" +"onu değiştirmekte ve / veya yeniden dağıtmakta özgürsünüz.\n" +"\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: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." + +#: style.cpp:735 +msgid "Style name cannot be empty" +msgstr "Biçim adı boş olamaz" + +#: textscreens.cpp:837 +msgid "Can't repeat fewer than 1 time." +msgstr "1 defadan az tekrarlanamaz." + +#: textscreens.cpp:841 +msgid "Can't repeat more than 999 times." +msgstr "999 defadan fazla tekrarlanamaz." + +#: textscreens.cpp:866 +msgid "Group name cannot be empty" +msgstr "Grup adı boş olamaz" + +#: 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: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ı çiz" + +#: toolbar.cpp:20 +msgid "Sketch rectangle" +msgstr "Dikdörtgen çiz" + +#: toolbar.cpp:22 +msgid "Sketch circle" +msgstr "Daire çiz" + +#: toolbar.cpp:24 +msgid "Sketch arc of a circle" +msgstr "Bir çember yayı çiz" + +#: toolbar.cpp:26 +msgid "Sketch curves from text in a TrueType font" +msgstr "TrueType yazı tipindeki metinden eğriler çiz" + +#: toolbar.cpp:28 +msgid "Sketch image from a file" +msgstr "Bir dosyadan görüntü (resim) ekle" + +#: toolbar.cpp:30 +msgid "Create tangent arc at selected point" +msgstr "Seçilen noktada radyus (teğet yay) oluştur" + +#: toolbar.cpp:32 +msgid "Sketch cubic Bezier spline" +msgstr "Kübik Bezier Eğri Çiz" + +#: toolbar.cpp:34 +msgid "Sketch datum point" +msgstr "Referans Nokta Ekle" + +#: toolbar.cpp:36 +msgid "Toggle construction" +msgstr "Yapıyı değiştir" + +#: toolbar.cpp:38 +msgid "Split lines / curves where they intersect" +msgstr "Çizgileri / eğrileri kesiştikleri yerden böl" + +#: toolbar.cpp:42 +msgid "Constrain distance / diameter / length" +msgstr "Mesafeyi / çapı / uzunluğu kısıtla" + +#: toolbar.cpp:44 +msgid "Constrain angle" +msgstr "Açıyı kısıtla" + +#: toolbar.cpp:46 +msgid "Constrain to be horizontal" +msgstr "Yatay olarak kısıtla" + +#: toolbar.cpp:48 +msgid "Constrain to be vertical" +msgstr "Dikey olarak kısıtla" + +#: toolbar.cpp:50 +msgid "Constrain to be parallel or tangent" +msgstr "Paralel veya teğet olarak kısıtla" + +#: toolbar.cpp:52 +msgid "Constrain to be perpendicular" +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 kısıtla" + +#: toolbar.cpp:56 +msgid "Constrain symmetric" +msgstr "Simetrik olarak kısıtla" + +#: toolbar.cpp:58 +msgid "Constrain equal length / radius / angle" +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 kısıtla" + +#: toolbar.cpp:62 +msgid "Other supplementary angle" +msgstr "Diğer bütünler açı" + +#: toolbar.cpp:64 +msgid "Toggle reference dimension" +msgstr "Ölçüyü Referans Yap / Yapma" + +#: toolbar.cpp:68 +msgid "New group extruding active sketch" +msgstr "Etkin çizimi uzatıp katılayarak yeni grup oluştur" + +#: toolbar.cpp:70 +msgid "New group rotating active sketch" +msgstr "Etkin çizimi tam döndürerek yeni grup oluştur" + +#: toolbar.cpp:72 +msgid "New group helix from active sketch" +msgstr "Etkin çizimden sarmal yeni grup oluştur" + +#: toolbar.cpp:74 +msgid "New group revolve active sketch" +msgstr "Etkin çizimi kısmen döndürerek yeni grup oluştur" + +#: toolbar.cpp:76 +msgid "New group step and repeat rotating" +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ı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ştur (verilen öğeler aracılığıyla)" + +#: toolbar.cpp:82 +msgid "New group in 3d" +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ştur" + +#: toolbar.cpp:88 +msgid "Nearest isometric view" +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 hizala" + +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "Hata" + +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "Mesaj" + +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "&Tamam" + +#: view.cpp:127 +msgid "Scale cannot be zero or negative." +msgstr "Ölçek sıfır veya negatif olamaz." + +#: view.cpp:139 view.cpp:148 +msgid "Bad format: specify x, y, z" +msgstr "Kötü biçim: x, y, z'yi belirtin" + + diff --git a/res/locales/uk_UA.po b/res/locales/uk_UA.po index b8982b56d..24ca895e9 100644 --- a/res/locales/uk_UA.po +++ b/res/locales/uk_UA.po @@ -1,273 +1,381 @@ # Ukrainian translations for SolveSpace package. # Copyright (C) 2017 the SolveSpace authors # This file is distributed under the same license as the SolveSpace package. -# AppSoft4 , 2017. -# +# app4soft , 2021. msgid "" msgstr "" "Project-Id-Version: SolveSpace 3.0\n" -"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" -"POT-Creation-Date: 2018-07-12 22:40+0000\n" -"PO-Revision-Date: 2017-01-05 10:30+0000\n" -"Last-Translator: appsoft@ua.fm\n" -"Language-Team: OpenOrienteeringUkraine\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" "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 3.5\n" -#: clipboard.cpp:274 +#: 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:291 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." -msgstr "" +msgstr "Буфер обміну порожній; немає чого вставляти." -#: clipboard.cpp:338 +#: clipboard.cpp:378 msgid "Number of copies to paste must be at least one." -msgstr "" +msgstr "Кількість копій для вставки має бути не менше одної." -#: clipboard.cpp:354 textscreens.cpp:709 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." -msgstr "" +msgstr "Масштаб не може бути нульовим." -#: clipboard.cpp:392 +#: clipboard.cpp:436 msgid "Select one point to define origin of rotation." -msgstr "" +msgstr "Оберіть одну точку для визначення центру обертання." -#: clipboard.cpp:404 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." -msgstr "" +msgstr "Оберіть дві точки для визначення вектору розміщення." -#: clipboard.cpp:414 +#: clipboard.cpp:458 msgid "" "Transformation is identity. So all copies will be exactly on top of each " "other." -msgstr "" +msgstr "Трансформація ідентична, тому усі копії будуть точно одна на одній." -#: clipboard.cpp:418 +#: clipboard.cpp:462 msgid "Too many items to paste; split this into smaller pastes." msgstr "" +"Забагато об'єктів для вставки; розділіть копіювання на кілька менших етапів." -#: clipboard.cpp:423 +#: clipboard.cpp:467 msgid "No workplane active." -msgstr "" +msgstr "Немає активної площини." -#: confscreen.cpp:336 +#: confscreen.cpp:410 msgid "Bad format: specify coordinates as x, y, z" -msgstr "" +msgstr "Некоректний формат: визначте координати як X, Y, Z" -#: confscreen.cpp:347 style.cpp:653 textscreens.cpp:730 +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 msgid "Bad format: specify color as r, g, b" -msgstr "" +msgstr "Некоректний формат: визначте колір як R, G, B" -#: confscreen.cpp:372 +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use " "Perspective Projection." msgstr "" +"Значення перспективи не матиме ефекту допоки не ввімкнено Вигляд -> " +"Використовувати Перспективну Проєкцію." -#: confscreen.cpp:386 -msgid "Specify between 0 and 8 digits after the decimal." -msgstr "" +#: confscreen.cpp:464 confscreen.cpp:474 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "Визначте кількість десяткових знаків від 0 до %d." -#: confscreen.cpp:398 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" -msgstr "" +msgstr "Масштаб експорту не може бути нульовим!" -#: confscreen.cpp:410 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" -msgstr "" +msgstr "Радіус відступу різання не може бути від'ємним!" -#: confscreen.cpp:464 +#: confscreen.cpp:557 msgid "Bad value: autosave interval should be positive" -msgstr "" +msgstr "Некоректне значення: інтервал автозбереження має бути додатнім" -#: confscreen.cpp:467 +#: 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" msgid "eq-pt-line-distances" -msgstr "" +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 "symmetric-h" -msgstr "" +msgid "length-difference" +msgstr "різниця-довжин" #: constraint.cpp:28 msgctxt "constr-name" -msgid "symmetric-v" -msgstr "" +msgid "arc-arc-len-difference" +msgstr "різниця-довжин-дуга-дуга" #: constraint.cpp:29 msgctxt "constr-name" -msgid "symmetric-line" -msgstr "" +msgid "arc-line-len-difference" +msgstr "різниця-довжин-дуга-лінія" #: constraint.cpp:30 msgctxt "constr-name" -msgid "at-midpoint" -msgstr "" +msgid "symmetric" +msgstr "симетрія" #: constraint.cpp:31 msgctxt "constr-name" -msgid "horizontal" -msgstr "" +msgid "symmetric-h" +msgstr "симетрія-вертикально" #: constraint.cpp:32 msgctxt "constr-name" -msgid "vertical" -msgstr "" +msgid "symmetric-v" +msgstr "симетрія-горизонтально" #: constraint.cpp:33 msgctxt "constr-name" -msgid "diameter" -msgstr "" +msgid "symmetric-line" +msgstr "симетрія-відносно-лінії" #: constraint.cpp:34 msgctxt "constr-name" -msgid "pt-on-circle" -msgstr "" +msgid "at-midpoint" +msgstr "на-середині" #: constraint.cpp:35 msgctxt "constr-name" -msgid "same-orientation" -msgstr "" +msgid "horizontal" +msgstr "горизонталь" #: constraint.cpp:36 msgctxt "constr-name" -msgid "angle" -msgstr "" +msgid "vertical" +msgstr "вертикаль" #: constraint.cpp:37 msgctxt "constr-name" -msgid "parallel" -msgstr "" +msgid "diameter" +msgstr "діаметр" #: constraint.cpp:38 msgctxt "constr-name" -msgid "arc-line-tangent" -msgstr "" +msgid "pt-on-circle" +msgstr "тчк-на-колі" #: constraint.cpp:39 msgctxt "constr-name" -msgid "cubic-line-tangent" -msgstr "" +msgid "same-orientation" +msgstr "співнапрямленість" #: constraint.cpp:40 msgctxt "constr-name" -msgid "curve-curve-tangent" -msgstr "" +msgid "angle" +msgstr "кут" #: constraint.cpp:41 msgctxt "constr-name" -msgid "perpendicular" -msgstr "" +msgid "parallel" +msgstr "паралель" #: constraint.cpp:42 msgctxt "constr-name" -msgid "eq-radius" -msgstr "" +msgid "arc-line-tangent" +msgstr "дотичні-дуга-лінія" #: constraint.cpp:43 msgctxt "constr-name" -msgid "eq-angle" -msgstr "" +msgid "cubic-line-tangent" +msgstr "дотичні-сплайн-лінія" #: constraint.cpp:44 msgctxt "constr-name" -msgid "eq-line-len-arc-len" -msgstr "" +msgid "curve-curve-tangent" +msgstr "дотичні-крива-крива" #: constraint.cpp:45 msgctxt "constr-name" -msgid "lock-where-dragged" -msgstr "" +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 "" +"Обрані вами точки не є кінцевими точками двох дуг. Дуги не поділяють кінцеву " +"точку.\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:160 +#: constraint.cpp:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply " "to:\n" @@ -280,59 +388,107 @@ msgid "" " * a plane face and a point (minimum distance)\n" " * a circle or an arc (diameter)\n" msgstr "" - -#: constraint.cpp:213 +"Поганий вибір для обмеження за Відстанню / Діаметром. Це обмеження можна " +"застосувати до:\n" +"\n" +" * двох точок (відстань між точками)\n" +" * відрізку прямої (довжина)\n" +" * двох точок та відрізку прямої чи нормалі (відстань між проєкціями " +"точок)\n" +" * робочої площини та точки (найменша відстань)\n" +" * відрізку прямої та точки (найменша відстань)\n" +" * грані та точки (найменша відстань)\n" +" * кола або дуги (діаметр)\n" + +#: 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:275 +#: 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:314 +"Поганий вибір для обмеження Рівні Довжина / Радіус. Це обмеження можна " +"застосувати до:\n" +"\n" +" * двох або більше відрізків прямої (рівні довжини)\n" +" * двох відрізків та двох точок (рівні відстані від точок до відповідних " +"відрізків)\n" +" * відрізка та двох точок (рівні відстані від точок до відрізка)\n" +" * відрізка, точки, та другого відрізка (відстань від точки до другого " +"відрізка дорівнює першому)\n" +" * двох або більше кіл чи дуг (рівні радіуси)\n" +" * відрізку та дуги (довжина відрізку дорівнює довжині дуги) \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" +" * двох відрізків прямої\n" +" * двох дуг\n" +" * дуги та відрізка прямої\n" -#: constraint.cpp:331 +#: 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:357 +#: 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:415 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -343,114 +499,167 @@ msgid "" " * workplane, and two points or a line segment (symmetric about " "workplane)\n" msgstr "" - -#: constraint.cpp:429 +"Поганий вибір для обмеження за симетрією. Це обмеження можна застосувати " +"до:\n" +"\n" +" * двох точок або відрізку прямої (симетрія відносно початку координат " +"робочої площини)\n" +" * відрізка та двох точок або іншого відрізка (симетрія відносно " +"відрізка)\n" +" * робочої площини та двох точок або відрізка (симетрія відносно робочої " +"площини)\n" + +#: constraint.cpp:623 msgid "" "A workplane must be active when constraining symmetric without an explicit " "symmetry plane." msgstr "" +"Робоча площина має бути активною коли використовується обмеження за " +"симетрією без явного вибору площини." -#: constraint.cpp:459 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a " "horizontal or vertical constraint." msgstr "" +"Активуйте площину (в Креслення -> У Робочій Площині) перед застосуванням " +"обмеження Горизонтально або Вертикально." -#: constraint.cpp:472 +#: 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:493 +#: 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:545 +#: constraint.cpp:748 msgid "Must select an angle constraint." -msgstr "" +msgstr "Необхідно обрати кут." -#: constraint.cpp:557 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." -msgstr "" +msgstr "Необхідно обрати обмежувач з відповідною міткою." -#: constraint.cpp:568 +#: 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:625 -msgid "" -"The tangent arc and line segment must share an endpoint. Constrain them with " -"Constrain -> On Point before constraining tangent." -msgstr "" - -#: constraint.cpp:649 -msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them " -"with Constrain -> On Point before constraining tangent." -msgstr "" - -#: constraint.cpp:659 +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." -msgstr "" - -#: constraint.cpp:677 -msgid "" -"The curves must share an endpoint. Constrain them with Constrain -> On Point " -"before constraining tangent." -msgstr "" +msgstr "Обмеження дотичності дуг має відбуватися у робочій площині." -#: constraint.cpp:686 +#: 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:704 +"Поганий вибір для обмеження Паралельно / Дотична. Це обмеження можна " +"застосувати до:\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:719 +#: 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:946 mouse.cpp:1160 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "КОМЕНТАР -- ДВІЧІ КЛІКНІТЬ ДЛЯ РЕДАГУВАННЯ" -#: constraint.cpp:730 +#: constraint.cpp:952 msgid "click center of comment text" -msgstr "" +msgstr "клікніть в місце де буде центр коментаря" -#: export.cpp:18 +#: 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 Вигляд\" для еспорту " +"лише ліній та кривих." -#: export.cpp:60 +#: export.cpp:61 msgid "" "Bad selection for export section. Please select:\n" "\n" @@ -459,470 +668,570 @@ msgid "" " * a point and two line segments (plane through point and parallel to " "lines)\n" msgstr "" +"Поганий вибір для експорту розрізу. Будьласка, оберіть:\n" +"\n" +" * нічого, з активною робочою площиною (робоча площина буде площиною " +"розрізу)\n" +" * грань (площина розрізу через грань)\n" +" * точку та два відрізки (площина розрізу через точку та паралельно " +"відрізкам)\n" -#: export.cpp:805 +#: 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:590 +#: exportvector.cpp:592 msgid "" "Some aspects of the drawing have no DXF equivalent and were not exported:\n" msgstr "" +"Деякі аспекти креслення на мають відповідників у форматі DXF і не будуть " +"експортовані:\n" -#: exportvector.cpp:807 +#: exportvector.cpp:838 msgid "" "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." msgstr "" +"Розмір аркуша у PDF перевищує 200×200 дюймів; багато переглядачів можуть не " +"підтримувати цей файл." -#: file.cpp:44 group.cpp:95 +#: file.cpp:44 group.cpp:91 msgctxt "group-name" msgid "sketch-in-plane" -msgstr "" +msgstr "креслення-в-площині" #: file.cpp:62 msgctxt "group-name" msgid "#references" -msgstr "" +msgstr "#базові-площини" -#: file.cpp:539 +#: 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 "&Так" + +#: file.cpp:884 +msgctxt "button" +msgid "&No" +msgstr "&Ні" + +#: file.cpp:886 solvespace.cpp:652 +msgctxt "button" +msgid "&Cancel" +msgstr "&Відмінити" -#: graphicswin.cpp:29 +#: graphicswin.cpp:41 msgid "&File" msgstr "&Файл" -#: graphicswin.cpp:30 +#: graphicswin.cpp:42 msgid "&New" msgstr "&Новий" -#: graphicswin.cpp:31 +#: graphicswin.cpp:43 msgid "&Open..." msgstr "&Відкрити..." -#: graphicswin.cpp:32 +#: graphicswin.cpp:44 msgid "Open &Recent" msgstr "Відкрити &Недавні" -#: graphicswin.cpp:33 +#: graphicswin.cpp:45 msgid "&Save" msgstr "&Зберегти" -#: graphicswin.cpp:34 +#: graphicswin.cpp:46 msgid "Save &As..." msgstr "Зберегти &Як..." -#: graphicswin.cpp:36 +#: graphicswin.cpp:48 msgid "Export &Image..." msgstr "Експортувати &Зображення..." -#: graphicswin.cpp:37 +#: graphicswin.cpp:49 msgid "Export 2d &View..." -msgstr "Експортувати 2d &Вигляд..." +msgstr "Експортувати 2D &Вигляд..." -#: graphicswin.cpp:38 +#: graphicswin.cpp:50 msgid "Export 2d &Section..." -msgstr "Експортувати 2d &Секцію..." +msgstr "Експортувати 2D &Секцію..." -#: graphicswin.cpp:39 +#: graphicswin.cpp:51 msgid "Export 3d &Wireframe..." -msgstr "Експортувати 3d &Скелет..." +msgstr "Експортувати 3D &Скелет..." -#: graphicswin.cpp:40 +#: graphicswin.cpp:52 msgid "Export Triangle &Mesh..." msgstr "Експортувати Тригранний &Каркас..." -#: graphicswin.cpp:41 +#: graphicswin.cpp:53 msgid "Export &Surfaces..." msgstr "Експортувати &Поверхні..." -#: graphicswin.cpp:42 +#: graphicswin.cpp:54 msgid "Im&port..." msgstr "Ім&портувати..." -#: graphicswin.cpp:45 +#: graphicswin.cpp:57 msgid "E&xit" msgstr "В&ихід" -#: graphicswin.cpp:48 +#: graphicswin.cpp:60 msgid "&Edit" msgstr "&Редагувати" -#: graphicswin.cpp:49 +#: graphicswin.cpp:61 msgid "&Undo" msgstr "&Відмінити" -#: graphicswin.cpp:50 +#: graphicswin.cpp:62 msgid "&Redo" msgstr "&Повторити" -#: graphicswin.cpp:51 +#: graphicswin.cpp:63 msgid "Re&generate All" msgstr "Ре&генерувати Все" -#: graphicswin.cpp:53 +#: graphicswin.cpp:65 msgid "Snap Selection to &Grid" msgstr "Прикріпити Виділене до &Сітки" -#: graphicswin.cpp:54 +#: graphicswin.cpp:66 msgid "Rotate Imported &90°" msgstr "Обернути Імпортоване на &90°" -#: graphicswin.cpp:56 +#: graphicswin.cpp:68 msgid "Cu&t" msgstr "Ви&різати" -#: graphicswin.cpp:57 +#: graphicswin.cpp:69 msgid "&Copy" msgstr "&Копіювати" -#: graphicswin.cpp:58 +#: graphicswin.cpp:70 msgid "&Paste" msgstr "&Вставити" -#: graphicswin.cpp:59 +#: graphicswin.cpp:71 msgid "Paste &Transformed..." msgstr "Вставити &Трансфмованим..." -#: graphicswin.cpp:60 +#: graphicswin.cpp:72 msgid "&Delete" -msgstr "&Delete" +msgstr "&Видалити" -#: graphicswin.cpp:62 +#: graphicswin.cpp:74 msgid "Select &Edge Chain" msgstr "Виділити Ланцуг &Ребер" -#: graphicswin.cpp:63 +#: graphicswin.cpp:75 msgid "Select &All" msgstr "Виділити &Усе" -#: graphicswin.cpp:64 +#: graphicswin.cpp:76 msgid "&Unselect All" msgstr "&Зняти Виділення з Усього" -#: graphicswin.cpp:66 +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "Стилі &Ліній..." + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "&Проекція Відображення..." + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "&Налаштування..." + +#: graphicswin.cpp:84 msgid "&View" -msgstr "&View" +msgstr "&Відображення" -#: graphicswin.cpp:67 +#: graphicswin.cpp:85 msgid "Zoom &In" msgstr "На&близити" -#: graphicswin.cpp:68 +#: graphicswin.cpp:86 msgid "Zoom &Out" msgstr "Від&далити" -#: graphicswin.cpp:69 +#: graphicswin.cpp:87 msgid "Zoom To &Fit" msgstr "Умістити на &Екрані" -#: graphicswin.cpp:71 +#: graphicswin.cpp:89 msgid "Align View to &Workplane" msgstr "Вирівняти Вигляд до Робочої &Площини" -#: graphicswin.cpp:72 +#: graphicswin.cpp:90 msgid "Nearest &Ortho View" msgstr "Найближчий &Ортогональний Вигляд" -#: graphicswin.cpp:73 +#: graphicswin.cpp:91 msgid "Nearest &Isometric View" msgstr "Найближчий &Ізометричний Вигляд" -#: graphicswin.cpp:74 +#: graphicswin.cpp:92 msgid "&Center View At Point" -msgstr "&Центрувати Вигляд На Точці" +msgstr "&Центрувати Вигляд на Точці" -#: graphicswin.cpp:76 +#: graphicswin.cpp:94 msgid "Show Snap &Grid" msgstr "Показати &Сітку Прикріплення" -#: graphicswin.cpp:77 +#: graphicswin.cpp:95 +msgid "Darken Inactive Solids" +msgstr "Затінювати Неактивні Тіла" + +#: graphicswin.cpp:96 msgid "Use &Perspective Projection" msgstr "Використовувати &Перспективну Проекцію" -#: graphicswin.cpp:78 +#: graphicswin.cpp:97 +msgid "Show E&xploded View" +msgstr "Показати С&кладальне Креслення" + +#: graphicswin.cpp:98 msgid "Dimension &Units" -msgstr "" +msgstr "Розмірні &Одиниці" -#: graphicswin.cpp:79 -msgid "Dimensions in &Inches" -msgstr "Розміри в &Дюймах" - -#: graphicswin.cpp:80 +#: graphicswin.cpp:99 msgid "Dimensions in &Millimeters" -msgstr "Розміри в &Міліметрах" +msgstr "Розміри у &Міліметрах" -#: graphicswin.cpp:81 +#: graphicswin.cpp:100 msgid "Dimensions in M&eters" -msgstr "" +msgstr "Розміри у &Метрах" + +#: graphicswin.cpp:101 +msgid "Dimensions in &Inches" +msgstr "Розміри у &Дюймах" -#: graphicswin.cpp:83 +#: graphicswin.cpp:102 +msgid "Dimensions in &Feet and Inches" +msgstr "Розміри в &Футах та Дюймах" + +#: graphicswin.cpp:104 msgid "Show &Toolbar" msgstr "Показати Панель &Інструментів" -#: graphicswin.cpp:84 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" msgstr "Показати Вікно Власти&востей" -#: graphicswin.cpp:86 +#: graphicswin.cpp:107 msgid "&Full Screen" msgstr "&Повний Екран" -#: graphicswin.cpp:88 +#: graphicswin.cpp:109 msgid "&New Group" msgstr "&Нова Група" -#: graphicswin.cpp:89 +#: graphicswin.cpp:110 msgid "Sketch In &3d" -msgstr "Креслення В &3d" +msgstr "Креслення у &3D" -#: graphicswin.cpp:90 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" -msgstr "Креслення В Новій Робочій &Площині" +msgstr "Креслення у Новій &Площині" -#: graphicswin.cpp:92 +#: graphicswin.cpp:113 msgid "Step &Translating" msgstr "Покрокове &Переміщення" -#: graphicswin.cpp:93 +#: graphicswin.cpp:114 msgid "Step &Rotating" msgstr "Покрокове &Обертання" -#: graphicswin.cpp:95 +#: graphicswin.cpp:116 msgid "E&xtrude" msgstr "Ви&давити" -#: graphicswin.cpp:96 +#: graphicswin.cpp:117 +msgid "&Helix" +msgstr "&Спіраль" + +#: graphicswin.cpp:118 msgid "&Lathe" msgstr "&Виточити" -#: graphicswin.cpp:98 +#: graphicswin.cpp:119 +msgid "Re&volve" +msgstr "&Обертати" + +#: graphicswin.cpp:121 msgid "Link / Assemble..." -msgstr "Приєднати / Монтувати..." +msgstr "Приєднати / Зібрати..." -#: graphicswin.cpp:99 +#: graphicswin.cpp:122 msgid "Link Recent" -msgstr "Приєднати Недавні" +msgstr "Приєднати Нещодавні" -#: graphicswin.cpp:101 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "&Креслення" -#: graphicswin.cpp:102 +#: graphicswin.cpp:125 msgid "In &Workplane" -msgstr "В Робочій &Площині" +msgstr "У Робочій &Площині" -#: graphicswin.cpp:103 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" -msgstr "Будь-де В &3d" +msgstr "Будь-де в &3D" -#: graphicswin.cpp:105 +#: graphicswin.cpp:128 msgid "Datum &Point" msgstr "Опорна &Точка" -#: graphicswin.cpp:106 -msgid "&Workplane" +#: graphicswin.cpp:129 +msgid "Wor&kplane" msgstr "Робоча &Площина" -#: graphicswin.cpp:108 +#: graphicswin.cpp:131 msgid "Line &Segment" msgstr "&Відрізок Прямої" -#: graphicswin.cpp:109 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" msgstr "Контсрук&ційний Відрізок Прямої" -#: graphicswin.cpp:110 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "&Прямокутник" -#: graphicswin.cpp:111 +#: graphicswin.cpp:134 msgid "&Circle" msgstr "&Коло" -#: graphicswin.cpp:112 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" msgstr "&Дуга Кола" -#: graphicswin.cpp:113 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" msgstr "Кубічний Сплайн &Без'є" -#: graphicswin.cpp:115 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" -msgstr "&Текст з TrueType Шрифтом" +msgstr "&Текст із TrueType Шрифтом" -#: graphicswin.cpp:116 -msgid "&Image" -msgstr "" +#: graphicswin.cpp:139 +msgid "I&mage" +msgstr "&Зображення" -#: graphicswin.cpp:118 +#: graphicswin.cpp:141 msgid "To&ggle Construction" msgstr "Пере&мкнути Конструктивність" -#: graphicswin.cpp:119 -msgid "Tangent &Arc at Point" +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" msgstr "Дотична &Дуга на Точці" -#: graphicswin.cpp:120 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" msgstr "Розрізати Криві на &Перетині" -#: graphicswin.cpp:122 +#: graphicswin.cpp:145 msgid "&Constrain" msgstr "&Обмежити" -#: graphicswin.cpp:123 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "&Відстань / Діаметр" -#: graphicswin.cpp:124 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" msgstr "Від&носний Розмір" -#: graphicswin.cpp:125 -msgid "A&ngle" -msgstr "К&ут" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" +msgstr "К&ут / Рівний Кут" -#: graphicswin.cpp:126 +#: graphicswin.cpp:149 msgid "Reference An&gle" msgstr "Відносний К&ут" -#: graphicswin.cpp:127 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" msgstr "Інший Су&міжний Кут" -#: graphicswin.cpp:128 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" msgstr "Перемкнути Від&носність Розмірів" -#: graphicswin.cpp:130 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "&Горизонтально" -#: graphicswin.cpp:131 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "&Вертикально" -#: graphicswin.cpp:133 +#: graphicswin.cpp:156 msgid "&On Point / Curve / Plane" msgstr "&На точці / Кривій / Площині" -#: graphicswin.cpp:134 -msgid "E&qual Length / Radius / Angle" -msgstr "Рі&вні Довжина / Радіус / Кут" +#: graphicswin.cpp:157 +msgid "E&qual Length / Radius" +msgstr "Р&івна Довжина / Радіус" -#: graphicswin.cpp:135 -msgid "Length Ra&tio" -msgstr "Про&порція Довжин" +#: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" +msgstr "Пропорція довжин" -#: graphicswin.cpp:136 -msgid "Length Diff&erence" -msgstr "Рі&зниця Довжин" +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" +msgstr "Різниця довжин" -#: graphicswin.cpp:137 +#: graphicswin.cpp:160 msgid "At &Midpoint" msgstr "До &Середини" -#: graphicswin.cpp:138 +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "Си&метрично" -#: graphicswin.cpp:139 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "Пара&лельно / Дотична" -#: graphicswin.cpp:140 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "&Препендикулярно" -#: graphicswin.cpp:141 +#: graphicswin.cpp:164 msgid "Same Orient&ation" msgstr "Однакова Орієн&тація" -#: graphicswin.cpp:142 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "Фіксувати Точку Після &Переміщення" -#: graphicswin.cpp:144 +#: graphicswin.cpp:167 msgid "Comment" msgstr "Коментар" -#: graphicswin.cpp:146 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "&Аналізувати" -#: graphicswin.cpp:147 +#: graphicswin.cpp:170 msgid "Measure &Volume" msgstr "Обрахувати &Об'єм" -#: graphicswin.cpp:148 +#: graphicswin.cpp:171 msgid "Measure A&rea" msgstr "Обрахувати Пл&ощу" -#: graphicswin.cpp:149 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" msgstr "Обрахувати &Периметр" -#: graphicswin.cpp:150 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" msgstr "Показати &Дотичні Частини" -#: graphicswin.cpp:151 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" msgstr "Показати &Приховані Ребра" -#: graphicswin.cpp:152 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" -msgstr "" +msgstr "Показати &Центр Масс" -#: graphicswin.cpp:154 -msgid "Show Degrees of &Freedom" -msgstr "Показати Степені &Свободи" +#: graphicswin.cpp:177 +msgid "Show &Underconstrained Points" +msgstr "Показати &Надмірно Обмежені Точки" -#: graphicswin.cpp:156 +#: graphicswin.cpp:179 msgid "&Trace Point" msgstr "&Трасувати Точку" -#: graphicswin.cpp:157 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." msgstr "&Зупити Трасування..." -#: graphicswin.cpp:158 +#: graphicswin.cpp:181 msgid "Step &Dimension..." msgstr "Прорахувати &Розмір..." -#: graphicswin.cpp:160 +#: graphicswin.cpp:183 msgid "&Help" msgstr "&Довідка" -#: graphicswin.cpp:161 +#: graphicswin.cpp:184 +msgid "&Language" +msgstr "&Мова" + +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "&Вебсайт / Посібник" -#: graphicswin.cpp:162 -msgid "&Language" -msgstr "&Мова" +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "&До коміту на GitHub" -#: graphicswin.cpp:164 +#: graphicswin.cpp:188 msgid "&About" msgstr "&Про програму" -#: graphicswin.cpp:491 +#: 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 "" +msgstr "Відсутня активна площина - сітка не відображатиметься." -#: graphicswin.cpp:500 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel " "projection.\n" @@ -930,124 +1239,154 @@ msgid "" "For a perspective projection, modify the perspective factor in the " "configuration screen. A value around 0.3 is typical." msgstr "" +"Встановлено нульовий коефіцієнт перспективи, тому відображення завжди буде " +"паралельною проєкцією." -#: graphicswin.cpp:581 +#: graphicswin.cpp:884 msgid "" "Select a point; this point will become the center of the view on screen." -msgstr "" +msgstr "Оберіть точку. Ця точка стане центром відображення на екрані." -#: graphicswin.cpp:862 +#: graphicswin.cpp:1193 msgid "No additional entities share endpoints with the selected entities." -msgstr "" +msgstr "Жодні сутності не мають спільних кінцевих точок з обраними сутностями." -#: graphicswin.cpp:882 +#: 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:906 +#: 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:913 +#: 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:979 +#: graphicswin.cpp:1326 msgid "No workplane selected. Activating default workplane for this group." msgstr "" +"Жодної робочої площини не обрано. Активую площину за замовченням для цієї " +"групи." -#: graphicswin.cpp:984 +#: 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:1008 +#: 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:1019 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" -msgstr "" +msgstr "клікніть для встановлення точки дуги (проти годинникової стрілки)" -#: graphicswin.cpp:1020 +#: graphicswin.cpp:1362 msgid "click to place datum point" -msgstr "" +msgstr "клікніть для встановлення вихідної точки" -#: graphicswin.cpp:1021 +#: graphicswin.cpp:1363 msgid "click first point of line segment" -msgstr "" +msgstr "клікніть першу точку прямої лінії" -#: graphicswin.cpp:1023 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" -msgstr "" +msgstr "клікніть першу точку конструктивної прямої лінії" -#: graphicswin.cpp:1024 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" -msgstr "" +msgstr "клікніть першу точку кривої" -#: graphicswin.cpp:1025 +#: graphicswin.cpp:1367 msgid "click center of circle" -msgstr "" +msgstr "клікніть в місце де буде центр коментаря" -#: graphicswin.cpp:1026 +#: graphicswin.cpp:1368 msgid "click origin of workplane" -msgstr "" +msgstr "клікніть в центр відліку площини" -#: graphicswin.cpp:1027 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" -msgstr "" +msgstr "клікніть для встановлення першого кута прямокутника" -#: graphicswin.cpp:1028 +#: graphicswin.cpp:1370 msgid "click top left of text" -msgstr "" +msgstr "клікніть для встановлення верхньої лівої межі тексту" -#: graphicswin.cpp:1034 +#: graphicswin.cpp:1376 msgid "click top left of image" -msgstr "" +msgstr "клікніть для встановлення верхньої лівої межі зображення" -#: graphicswin.cpp:1047 +#: graphicswin.cpp:1402 msgid "" "No entities are selected. Select entities before trying to toggle their " "construction state." -msgstr "" +msgstr "Жодної сутності не обрано. Оберіть сутності для перключення їх стану." -#: group.cpp:90 +#: group.cpp:86 msgctxt "group-name" msgid "sketch-in-3d" -msgstr "" +msgstr "ескіз-в-3D" -#: group.cpp:146 +#: 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:158 +#: group.cpp:170 msgid "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " "will be extruded normal to the workplane." msgstr "" +"Активуйте робочу площину ( Ескіз -> У Площині) перед екструдуванням. Ескіз " +"буде екструдовано перпендикулярно до робочої площини." -#: group.cpp:167 +#: 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:179 +#: group.cpp:195 msgid "" "Bad selection for new lathe group. This group can be created with:\n" "\n" @@ -1055,13 +1394,65 @@ 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:210 +msgid "Revolve operation can only be applied to planar sketches." msgstr "" +"Операція прокручування може бути застосована тільки для пласких креслень." -#: group.cpp:202 +#: 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" @@ -1070,425 +1461,695 @@ 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:215 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" -msgstr "" +msgstr "крутіння" -#: group.cpp:226 +#: group.cpp:298 msgctxt "group-name" msgid "translate" -msgstr "" +msgstr "перекладання" -#: group.cpp:340 +#: group.cpp:422 msgid "(unnamed)" -msgstr "" +msgstr "(безіменне)" -#: groupmesh.cpp:626 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" -msgstr "" +msgstr "не замкнений контур або не все в єдиному стилі!" -#: groupmesh.cpp:639 +#: groupmesh.cpp:723 msgid "points not all coplanar!" -msgstr "" +msgstr "не всі точки знаходяться в одній площині!" -#: groupmesh.cpp:641 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" -msgstr "" +msgstr "контур самоперетинається!" -#: groupmesh.cpp:643 +#: groupmesh.cpp:727 msgid "zero-length edge!" -msgstr "" +msgstr "ребро нульової довжини!" -#: modify.cpp:237 +#: 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:284 +#: 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:371 +#: 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:575 +#: modify.cpp:595 msgid "Couldn't split this entity; lines, circles, or cubics only." msgstr "" +"Неможливо розділити цей об'єкт. Дозволено тільки для відрізків, кіл(дуг), чи " +"сплайнів." -#: modify.cpp:601 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." msgstr "" +"Креслення має відбуватися у робочій площині для використання розділення." -#: modify.cpp:608 +#: 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:713 +#: modify.cpp:734 msgid "Can't split; no intersection found." -msgstr "" +msgstr "Неможливо розділити; відсутній перетин." -#: mouse.cpp:522 +#: mouse.cpp:558 +msgid "Assign to Style" +msgstr "Встановити Стиль" + +#: mouse.cpp:574 msgid "No Style" msgstr "Без Стилю" -#: mouse.cpp:523 +#: mouse.cpp:577 msgid "Newly Created Custom Style..." msgstr "Створити Користувацький Стиль..." -#: mouse.cpp:571 -msgid "Assign to Style" -msgstr "Встановити Стиль" - -#: mouse.cpp:574 +#: mouse.cpp:584 msgid "Group Info" msgstr "Параметри Групи" -#: mouse.cpp:577 +#: mouse.cpp:604 msgid "Style Info" msgstr "Параметри Стилю" -#: mouse.cpp:580 +#: mouse.cpp:624 msgid "Select Edge Chain" msgstr "Виділити Ланцюг Ребер" -#: mouse.cpp:585 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" msgstr "Перемкнути Відносність Розміру" -#: mouse.cpp:591 +#: mouse.cpp:636 msgid "Other Supplementary Angle" msgstr "Інший Суміжний Кут" -#: mouse.cpp:596 +#: mouse.cpp:641 msgid "Snap to Grid" msgstr "Прикріпити до Сітки" -#: mouse.cpp:604 +#: mouse.cpp:650 msgid "Remove Spline Point" msgstr "Видалити Точку Сплайну" -#: mouse.cpp:615 +#: mouse.cpp:685 msgid "Add Spline Point" msgstr "Додати Точку Сплайну" -#: mouse.cpp:619 +#: mouse.cpp:689 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "" +"Неможливо додати точку сплайна: перевищено максимальну кількість точок." + +#: mouse.cpp:714 msgid "Toggle Construction" msgstr "Пермкнути Конструктивність" -#: mouse.cpp:633 +#: mouse.cpp:730 msgid "Delete Point-Coincident Constraint" msgstr "Роз'єднати З'єднання Точок" -#: mouse.cpp:639 +#: mouse.cpp:748 msgid "Cut" msgstr "Вирізати" -#: mouse.cpp:640 +#: mouse.cpp:750 msgid "Copy" msgstr "Копіювати" -#: mouse.cpp:643 +#: mouse.cpp:754 msgid "Select All" msgstr "Виділити Усе" -#: mouse.cpp:647 +#: mouse.cpp:759 msgid "Paste" msgstr "Вставити" -#: mouse.cpp:648 +#: mouse.cpp:761 msgid "Paste Transformed..." msgstr "Вставити Трансформованим..." -#: mouse.cpp:652 +#: mouse.cpp:766 msgid "Delete" msgstr "Видалити" -#: mouse.cpp:654 +#: mouse.cpp:769 msgid "Unselect All" msgstr "Зняти Виділення з Усього" -#: mouse.cpp:660 +#: mouse.cpp:776 msgid "Unselect Hovered" msgstr "Зняти Виділення з Наведеного" -#: mouse.cpp:665 +#: mouse.cpp:785 msgid "Zoom to Fit" msgstr "Умістити на Екрані" -#: mouse.cpp:805 -msgid "Cannot add spline point: maximum number of points reached." -msgstr "" - -#: mouse.cpp:1009 mouse.cpp:1292 +#: mouse.cpp:987 mouse.cpp:1276 msgid "click next point of line, or press Esc" -msgstr "" +msgstr "клікніть наступну точку лінії або натисніть Esc" -#: mouse.cpp:1015 +#: mouse.cpp:993 msgid "" "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" +"Неможливо накреслити прямокутник у 3d; спочатку активуйте робочу площину в " +"Креслення -> У Робочій Площині." -#: mouse.cpp:1049 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" -msgstr "" +msgstr "клікніть для встановлення іншого кута прямокутника" -#: mouse.cpp:1069 +#: mouse.cpp:1048 msgid "click to set radius" -msgstr "" +msgstr "клікніть для визначення радіусу" -#: mouse.cpp:1074 +#: mouse.cpp:1053 msgid "" "Can't draw arc in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" +"Неможливо накреслити дугу у 3d; спочатку активуйте робочу площину в " +"Креслення -> У Робочій Площині." -#: mouse.cpp:1093 +#: mouse.cpp:1072 msgid "click to place point" -msgstr "" +msgstr "клікніть для встановлення точки" -#: mouse.cpp:1109 +#: mouse.cpp:1088 msgid "click next point of cubic, or press Esc" -msgstr "" +msgstr "клікніть наступну точку кривої або натисніть Esc" -#: mouse.cpp:1114 +#: mouse.cpp:1093 msgid "" "Sketching in a workplane already; sketch in 3d before creating new workplane." msgstr "" +"Вже обрано креслення у робочій площині; поверніться у креслення будь-ду в 3D " +"для створення нової робочої площини." -#: mouse.cpp:1130 +#: mouse.cpp:1109 msgid "" "Can't draw text in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" +"Неможливо накреслити текст у 3д; спочатку активуйте робочу площину в " +"Креслення -> У Робочій Площині." -#: mouse.cpp:1146 -msgid "click to place bottom left of text" -msgstr "" +#: mouse.cpp:1126 +msgid "click to place bottom right of text" +msgstr "клікніть для встановлення нижньої правої межі тексту" -#: mouse.cpp:1152 +#: mouse.cpp:1132 msgid "" "Can't draw image in 3d; first, activate a workplane with Sketch -> In " "Workplane." msgstr "" +"Неможливо накреслити зображення у 3д; спочатку активуйте робочу площину в " +"Креслення -> У Робочій Площині." -#: mouse.cpp:1178 -msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" -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/cocoamain.mm:481 platform/gtkmain.cpp:607 platform/w32main.cpp:451 -#: platform/w32main.cpp:1388 -msgctxt "title" -msgid "(new sketch)" -msgstr "" +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Wavefront OBJ меш" -#: platform/cocoamain.mm:710 platform/gtkmain.cpp:912 platform/w32main.cpp:1307 -msgid "(no recent files)" -msgstr "" +#: 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/cocoamain.mm:828 platform/gtkmain.cpp:1020 +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "Encapsulated PostScript" + +#: platform/gui.cpp:114 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "Scalable Vector Graphics, векторний формат" + +#: platform/gui.cpp:116 platform/gui.cpp:123 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "DXF файл (AutoCAD 2007)" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "HPGL file" +msgstr "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 "AutoCAD DXF та DWG файли" + +#: platform/gui.cpp:131 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "Comma-separated values" + +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" -msgstr "" +msgstr "без імені" -#: platform/cocoamain.mm:860 -msgid "Do you want to save the changes you made to the new sketch?" -msgstr "" +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 +msgctxt "title" +msgid "Save File" +msgstr "Зберегти Файл" -#: platform/cocoamain.mm:862 -msgid "Your changes will be lost if you don't save them." -msgstr "" +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 +msgctxt "title" +msgid "Open File" +msgstr "Відкрити Файл" -#: platform/cocoamain.mm:863 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" -msgid "Save" -msgstr "" +msgid "_Cancel" +msgstr "_Скасувати" -#: platform/cocoamain.mm:864 platform/cocoamain.mm:905 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" -msgid "Cancel" -msgstr "" +msgid "_Save" +msgstr "_Зберегти" -#: platform/cocoamain.mm:865 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" -msgid "Don't Save" -msgstr "" +msgid "_Open" +msgstr "_Відкрити" -#: platform/cocoamain.mm:880 -msgid "An autosave file is available for this project." -msgstr "" +#: solvespace.cpp:175 +msgctxt "title" +msgid "Autosave Available" +msgstr "Наявні автозбереження" + +#: solvespace.cpp:176 +msgctxt "dialog" +msgid "An autosave file is available for this sketch." +msgstr "Наявні автозбереження для цього креслення." -#: platform/cocoamain.mm:882 +#: solvespace.cpp:177 +msgctxt "dialog" msgid "Do you want to load the autosave file instead?" -msgstr "" +msgstr "Завантажити файл автозбереження?" -#: platform/cocoamain.mm:883 +#: solvespace.cpp:178 msgctxt "button" -msgid "Load" -msgstr "" +msgid "&Load autosave" +msgstr "&Завантажити автозбереження" -#: platform/cocoamain.mm:884 +#: solvespace.cpp:180 msgctxt "button" -msgid "Don't Load" -msgstr "" +msgid "Do&n't Load" +msgstr "&Не Завантажувати" -#: platform/cocoamain.mm:900 -msgid "" -"Do you want to locate it manually?\n" -"If you select “No”, any geometry that depends on the missing file will be " -"removed." -msgstr "" +#: solvespace.cpp:640 +msgctxt "title" +msgid "Modified File" +msgstr "Файл Змінено" -#: platform/cocoamain.mm:903 -msgctxt "button" -msgid "Yes" -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 "Чи хочете ви зберегти зміни зроблені вами у новому ескізі?" -#: platform/cocoamain.mm:906 +#: solvespace.cpp:648 +msgctxt "dialog" +msgid "Your changes will be lost if you don't save them." +msgstr "Ваші зміни буде втрачено якщо ви не збережете їх." + +#: solvespace.cpp:649 msgctxt "button" -msgid "No" -msgstr "" +msgid "&Save" +msgstr "&Зберегти" -#: platform/cocoamain.mm:1126 platform/w32main.cpp:183 +#: solvespace.cpp:651 msgctxt "button" -msgid "OK" -msgstr "" +msgid "Do&n't Save" +msgstr "&Не Зберігати" -#: platform/cocoamain.mm:1211 platform/gtkmain.cpp:1382 -#: platform/w32main.cpp:1410 platform/w32main.cpp:1450 +#: solvespace.cpp:672 msgctxt "title" -msgid "Property Browser" -msgstr "" +msgid "(new sketch)" +msgstr "(нове креслення)" -#: platform/gtkmain.cpp:968 +#: solvespace.cpp:683 msgctxt "title" -msgid "Open File" -msgstr "" +msgid "Property Browser" +msgstr "Браузер Властивостей" -#: platform/gtkmain.cpp:970 -msgid "_Cancel" +#: 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ховайте їх клікнувши на " +"посилання над текстовим вікном." -#: platform/gtkmain.cpp:971 -msgid "_Open" +#: 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." -#: platform/gtkmain.cpp:1010 -msgctxt "title" -msgid "Save File" -msgstr "" +#: solvespace.cpp:886 +msgid "Constraint must have a label, and must not be a reference dimension." +msgstr "Обмежувач має містити мітку і бути не відносним розміром." -#: platform/gtkmain.cpp:1013 platform/gtkmain.cpp:1049 -#: platform/gtkmain.cpp:1097 -msgctxt "button" -msgid "_Cancel" -msgstr "" +#: solvespace.cpp:890 +msgid "Bad selection for step dimension; select a constraint." +msgstr "Поганий вибір для крокової зміни розміру; оберіть обмежувач." -#: platform/gtkmain.cpp:1014 platform/gtkmain.cpp:1047 -msgctxt "button" -msgid "_Save" -msgstr "" +#: solvespace.cpp:914 +msgid "The assembly does not interfere, good." +msgstr "Збірка не перетинається, очман." -#: platform/gtkmain.cpp:1042 platform/w32main.cpp:1167 +#: solvespace.cpp:930 +#, c-format msgid "" -"The file has changed since it was last saved.\n" +"The volume of the solid model is:\n" "\n" -"Do you want to save the changes?" +" %s" msgstr "" +"Об'єм твердого тіла становить:\n" +"\n" +" %s" -#: platform/gtkmain.cpp:1046 platform/w32main.cpp:1169 -msgctxt "title" -msgid "Modified File" +#: solvespace.cpp:939 +#, c-format +msgid "" +"\n" +"The volume of current group mesh is:\n" +"\n" +" %s" msgstr "" +"\n" +"Об'єм поточної групи мешу становить:\n" +"\n" +" %s" -#: platform/gtkmain.cpp:1048 -msgctxt "button" -msgid "Do_n't Save" +#: 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%." -#: platform/gtkmain.cpp:1066 platform/w32main.cpp:1193 +#: solvespace.cpp:959 +#, c-format msgid "" -"An autosave file is available for this project.\n" +"The surface area of the selected faces is:\n" "\n" -"Do you want to load the autosave file instead?" +" %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%%." -#: platform/gtkmain.cpp:1070 platform/w32main.cpp:1195 -msgctxt "title" -msgid "Autosave Available" +#: solvespace.cpp:968 +msgid "" +"This group does not contain a correctly-formed 2d closed area. It is open, " +"not coplanar, or self-intersecting." msgstr "" +"Ця група не місить коректно сформованого замкненої 2D площини. Вона " +"відкрита, не компланарна, або ж самоперетинається." -#: platform/gtkmain.cpp:1071 -msgctxt "button" -msgid "_Load autosave" +#: 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%%." -#: platform/gtkmain.cpp:1072 -msgctxt "button" -msgid "Do_n't Load" +#: 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%%." -#: platform/gtkmain.cpp:1093 platform/w32main.cpp:1223 -msgctxt "title" -msgid "Missing File" -msgstr "" +#: solvespace.cpp:1006 +msgid "Bad selection for perimeter; select line segments, arcs, and curves." +msgstr "Поганий вибір для периметру; оберіть відрізки, дуги та криві." -#: platform/gtkmain.cpp:1094 -msgctxt "button" -msgid "_Yes" +#: 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 поверхнях." -#: platform/gtkmain.cpp:1095 -msgctxt "button" -msgid "_No" +#: 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" -#: platform/gtkmain.cpp:1306 platform/w32main.cpp:179 -msgctxt "title" -msgid "Error" +#: 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" -#: platform/gtkmain.cpp:1306 platform/w32main.cpp:179 -msgctxt "title" -msgid "Message" +#: 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 "" +"Це SolveSpace версії %s.\n" +"\n" +"Більше інформації на сайті http://solvespace.com/\n" +"\n" +"SolveSpace є вільною програмою: ви можете модифікувати\n" +"та/або розповсюджувати її відповідно до ліцензії GNU\n" +"General Public License (GPL) версії 3 чи новішої.\n" +"\n" +"ВІЧСУТНІ БУДЬ-ЯКІ ГАРАНТІЇ, в межах, доволених\n" +"законом. Деталі на сайті http://gnu.org/licenses/\n" +"\n" +"© 2008-%d Jonathan Westhues та інші автори.\n" -#: style.cpp:160 +#: 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:659 +#: style.cpp:735 msgid "Style name cannot be empty" -msgstr "" +msgstr "Стиль не може містити порожнє ім'я" -#: textscreens.cpp:662 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." -msgstr "" +msgstr "Не можливо повторити менше 1 разу." -#: textscreens.cpp:666 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." -msgstr "" +msgstr "Не можливо повторити понад 999 разів." -#: textscreens.cpp:695 +#: textscreens.cpp:866 msgid "Group name cannot be empty" -msgstr "" +msgstr "Група не може містити порожнє ім'я" -#: textscreens.cpp:739 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." -msgstr "" +msgstr "Непрозорість має бути між 0 та 1." -#: textscreens.cpp:778 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." -msgstr "" +msgstr "Радіус не може бути нульовим чи від'ємним." #: toolbar.cpp:18 msgid "Sketch line segment" @@ -1512,7 +2173,7 @@ msgstr "Накреслити криві з тексту на TrueType шрифт #: toolbar.cpp:28 msgid "Sketch image from a file" -msgstr "" +msgstr "Вставити зображення з файлу" #: toolbar.cpp:30 msgid "Create tangent arc at selected point" @@ -1584,107 +2245,85 @@ 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 "Нова група спіралі з активного ескізу" + +#: toolbar.cpp:74 +msgid "New group revolve active sketch" +msgstr "Нова група обертання активного ескізу" + +#: toolbar.cpp:76 msgid "New group step and repeat rotating" msgstr "Нова група крокування і повторення обертання" -#: toolbar.cpp:74 +#: toolbar.cpp:78 msgid "New group step and repeat translating" msgstr "Нова група крокування і повторення зміщення" -#: toolbar.cpp:76 +#: toolbar.cpp:80 msgid "New group in new workplane (thru given entities)" msgstr "Нова група в новій площині (через обрані об'екти)" -#: toolbar.cpp:78 +#: toolbar.cpp:82 msgid "New group in 3d" -msgstr "Нова група в 3d" +msgstr "Нова група в 3D" -#: toolbar.cpp:80 +#: toolbar.cpp:84 msgid "New group linking / assembling file" msgstr "Нова група приєднання / монтування файлу" -#: toolbar.cpp:84 +#: toolbar.cpp:88 msgid "Nearest isometric view" msgstr "Найближчий ізометричний вигляд" -#: toolbar.cpp:86 +#: toolbar.cpp:90 msgid "Align view to active workplane" msgstr "Вирівняти вигляд до активної робочої площини" -#: ui.h:69 -msgid "SolveSpace models" -msgstr "" - -#: ui.h:74 -msgid "PNG file" -msgstr "" - -#: ui.h:79 -msgid "STL mesh" -msgstr "" - -#: ui.h:80 -msgid "Wavefront OBJ mesh" -msgstr "" - -#: ui.h:81 -msgid "Three.js-compatible mesh, with viewer" -msgstr "" - -#: ui.h:82 -msgid "Three.js-compatible mesh, mesh only" -msgstr "" - -#: ui.h:87 ui.h:95 ui.h:103 -msgid "STEP file" -msgstr "" - -#: ui.h:92 -msgid "PDF file" -msgstr "" +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "Помилка" -#: ui.h:93 -msgid "Encapsulated PostScript" -msgstr "" +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "Повідомлення" -#: ui.h:94 -msgid "Scalable Vector Graphics" -msgstr "" +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "&OK" -#: ui.h:96 ui.h:104 -msgid "DXF file (AutoCAD 2007)" -msgstr "" +#: view.cpp:127 +msgid "Scale cannot be zero or negative." +msgstr "Масштаб не може бути нульовим чи від'ємним." -#: ui.h:97 -msgid "HPGL file" -msgstr "" +#: view.cpp:139 view.cpp:148 +msgid "Bad format: specify x, y, z" +msgstr "Некоректний формат: визначте X, Y, Z" -#: ui.h:98 -msgid "G Code" -msgstr "" +#~ msgid "A&ngle" +#~ msgstr "К&ут" -#: ui.h:109 -msgid "AutoCAD DXF and DWG files" -msgstr "" +#~ msgid "E&qual Length / Radius / Angle" +#~ msgstr "Рі&вні Довжина / Радіус / Кут" -#: ui.h:114 -msgid "Comma-separated values" -msgstr "" +#~ msgid "Length Ra&tio" +#~ msgstr "Про&порція Довжин" -#: view.cpp:78 -msgid "Scale cannot be zero or negative." -msgstr "" +#~ msgid "Length Diff&erence" +#~ msgstr "Рі&зниця Довжин" -#: view.cpp:90 view.cpp:99 -msgid "Bad format: specify x, y, z" -msgstr "" +#~ msgid "Show Degrees of &Freedom" +#~ msgstr "Показати Степені &Свободи" #~ msgid "Show Menu &Bar" #~ msgstr "Показати Панель &Меню" diff --git a/res/locales/zh_CN.po b/res/locales/zh_CN.po new file mode 100644 index 000000000..03bfaecc1 --- /dev/null +++ b/res/locales/zh_CN.po @@ -0,0 +1,2378 @@ +# Chinese translations for SolveSpace package. +# Copyright (C) 2020 the PACKAGE authors +# This file is distributed under the same license as the SolveSpace package. +# , 2020. +# , 2023. +# +# 译词一致性 +# 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.0\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" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.4.2\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 "缩放不能为零。" + +#: clipboard.cpp:436 +msgid "Select one point to define origin of rotation." +msgstr "选择一个点来定义旋转中心。" + +#: clipboard.cpp:448 +msgid "Select two points to define translation vector." +msgstr "选择两个点来定义平移向量。" + +#: clipboard.cpp:458 +msgid "" +"Transformation is identity. So all copies will be exactly on top of each " +"other." +msgstr "输入的变换是不动,因此所有副本将互相重叠。" + +#: 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 "导出比例不能为零!" + +#: 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 "" + +#: 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" +"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 "" +"要相切的三次曲线段和线段必须有共同端点。约束相切之前,使用\"约束 -> 在点上" +"\"来约束它们。\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 "" +"要约束的曲线必须有共同端点。约束相切之前,使用\"约束 -> 在点上\"来约束它" +"们。\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 "" +"距离/直径约束的选择错误。此约束可用于:\n" +"\n" +" * 两点(点距)\n" +" * 线段(长度)\n" +" * 两点与一条线段或法线(投影距离)\n" +" * 一点与一工作面(最小距离)\n" +" * 一点与一条线段(最小距离)\n" +" * 一点与一平表面(最小距离)\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" +" * 两个以上的点(点点重合)\n" +" * 一点与一平面(点在面上)\n" +" * 一点与一条线段(点在直线上)\n" +" * 一点与一个圆或弧(点在曲线上)\n" +" * 一点与一到三个平表面(点在表面上)\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" +" * 两条以上线段(长度相等)\n" +" * 两条线段与两点(点线距离相等)\n" +" * 一条线段与两点(点线距离相等)\n" +" * 一条线段与一点和线段(点线距离等于长度)\n" +" * 一条线段和一条弧(线长等于弧长)\n" +" * 两个以上圆或弧(半径相等)\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" +" * 两条线段\n" +" * 两条弧线\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" +" * 两条线段\n" +" * 两条弧线\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" +" * 两点或一条线段(关于工作面坐标轴对称)\n" +" * 一条线段与两点或一条线段(关于直线对称)\n" +" * 一工作面和两点或一条线段(关于工作面对称)\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" +" * 两个以上的点\n" +" * 一条以上的线段\n" + +#: 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: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" +" * 两条法线\n" +" * 一条线段和一条法线\n" +"之间的角度\n" +" * 四条线段或法线(AB之间与CD之间角度相等)\n" +" * 三条线段或法线(AB之间与BC之间角度相等)\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" +" * 两个表面\n" +" * 两条以上线段(平行)\n" +" * 两条以上法线(平行)\n" +" * 一条以上线段与一条以上法线(平行)\n" +" * 有共同端点的两条弧/线段/贝塞尔曲线(相切)\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: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: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: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 "" +"不存在实心体模型;用拉伸和扫略来绘制,或使用\"导出2D视图\"导出光杆直线和曲" +"线。" + +#: 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 "" +"导出截面的选择错误。请选择:\n" +"\n" +" * 空,有活动工作面时(工作面为截面)\n" +" * 一个表面(截面穿过表面)\n" +" * 一点与两条线段(截面穿过点并与线平行)\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英寸;许多查看器可能会拒绝此文件。" + +#: 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 "您是否想手动查找?如果拒绝,基于丢失文件的所有几何图形将被永久删除。" + +#: 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 "导出2D视图...(&V)" + +#: graphicswin.cpp:50 +msgid "Export 2d &Section..." +msgstr "导出2D截面...(&S)" + +#: graphicswin.cpp:51 +msgid "Export 3d &Wireframe..." +msgstr "导出3D线框...(&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 "导入...(&P)" + +#: 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 "选择相连边(&E)" + +#: 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 "适合窗口(&F)" + +#: 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 "视图中心置于点(&C)" + +#: 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 "显示爆炸视图(&E)" + +#: 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 "挤出(&E)" + +#: 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 "构造线线段(&C)" + +#: 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 "网站/手册(&W)" + +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" +msgstr "转到Github commit(&G)" + +#: graphicswin.cpp:188 +msgid "&About" +msgstr "关于(&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 "" +"透视因数已设为零,因此视图将始终为平行投影。\n" +"\n" +"要使用透视投影,请在配置界面中修改透视因数。典型值大概是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" +" * 一点与两条线段(穿过点,平行于线)\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 "边长度为零!" + +#: 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 "要创建内切弧,请选择活动组和工作面中,两个非构造线或圆连接处。" + +#: 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 "选择两个相交的物件(例如两条线/圆/弧,或一条线/圆/弧与一点)。" + +#: 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 "封装的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文件(AutoCAD 2007)" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "HPGL file" +msgstr "HPGL文件" + +#: 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 "AutoCAD DXF/DWG文件" + +#: platform/gui.cpp:131 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "逗号分隔数据" + +#: 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 "" +"是否保存您对草图\n" +"“%s”\n" +"的修改?" + +#: 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" +"曲面已近似为三角形。这会引入误差,通常约为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" +"曲线已近似为线性分段。这会引入误差,通常约为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" +"曲线已近似为线性分段。这会引入误差,通常约为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" +"曲线已近似为线性分段。这会引入误差,通常约为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 "" +"当前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: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 "半径偏移不能为负数。" + +#: 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 "好(&O)" + +#: view.cpp:127 +msgid "Scale cannot be zero or negative." +msgstr "缩放不能为零。" + +#: 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" + +#~ 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" +#~ msgstr "Q3D对象文件" diff --git a/res/messages.pot b/res/messages.pot index e58b9cf3c..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: 2018-07-12 22:40+0000\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,75 +17,76 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: clipboard.cpp:274 +#: clipboard.cpp:314 msgid "" "Cut, paste, and copy work only in a workplane.\n" "\n" "Activate one with Sketch -> In Workplane." msgstr "" -#: clipboard.cpp:291 +#: clipboard.cpp:331 msgid "Clipboard is empty; nothing to paste." msgstr "" -#: clipboard.cpp:338 +#: clipboard.cpp:378 msgid "Number of copies to paste must be at least one." msgstr "" -#: clipboard.cpp:354 textscreens.cpp:709 +#: clipboard.cpp:394 textscreens.cpp:879 msgid "Scale cannot be zero." msgstr "" -#: clipboard.cpp:392 +#: clipboard.cpp:436 msgid "Select one point to define origin of rotation." msgstr "" -#: clipboard.cpp:404 +#: clipboard.cpp:448 msgid "Select two points to define translation vector." msgstr "" -#: clipboard.cpp:414 +#: clipboard.cpp:458 msgid "Transformation is identity. So all copies will be exactly on top of each other." msgstr "" -#: clipboard.cpp:418 +#: clipboard.cpp:462 msgid "Too many items to paste; split this into smaller pastes." msgstr "" -#: clipboard.cpp:423 +#: clipboard.cpp:467 msgid "No workplane active." msgstr "" -#: confscreen.cpp:336 +#: confscreen.cpp:410 msgid "Bad format: specify coordinates as x, y, z" msgstr "" -#: confscreen.cpp:347 style.cpp:653 textscreens.cpp:730 +#: confscreen.cpp:420 style.cpp:729 textscreens.cpp:910 msgid "Bad format: specify color as r, g, b" msgstr "" -#: confscreen.cpp:372 +#: confscreen.cpp:446 msgid "" "The perspective factor will have no effect until you enable View -> Use Perspective Projection." msgstr "" -#: confscreen.cpp:386 -msgid "Specify between 0 and 8 digits after the decimal." +#: confscreen.cpp:464 confscreen.cpp:474 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." msgstr "" -#: confscreen.cpp:398 +#: confscreen.cpp:486 msgid "Export scale must not be zero!" msgstr "" -#: confscreen.cpp:410 +#: confscreen.cpp:498 msgid "Cutter radius offset must not be negative!" msgstr "" -#: confscreen.cpp:464 +#: confscreen.cpp:557 msgid "Bad value: autosave interval should be positive" msgstr "" -#: confscreen.cpp:467 +#: confscreen.cpp:560 msgid "Bad format: specify interval in integral minutes" msgstr "" @@ -156,115 +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:160 +#: 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 "" + +#: 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: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 "" + +#: constraint.cpp:303 msgid "" "Bad selection for distance / diameter constraint. This constraint can apply to:\n" "\n" @@ -277,46 +347,48 @@ msgid "" " * a circle or an arc (diameter)\n" msgstr "" -#: constraint.cpp:213 +#: 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:275 +#: 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:314 +#: 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:331 +#: 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:357 +#: constraint.cpp:550 msgid "" "Bad selection for at midpoint constraint. This constraint can apply to:\n" "\n" @@ -324,7 +396,7 @@ msgid "" " * a line segment and a workplane (line's midpoint on plane)\n" msgstr "" -#: constraint.cpp:415 +#: constraint.cpp:608 msgid "" "Bad selection for symmetric constraint. This constraint can apply to:\n" "\n" @@ -333,107 +405,102 @@ msgid "" " * workplane, and two points or a line segment (symmetric about workplane)\n" msgstr "" -#: constraint.cpp:429 +#: constraint.cpp:623 msgid "A workplane must be active when constraining symmetric without an explicit symmetry plane." msgstr "" -#: constraint.cpp:459 +#: constraint.cpp:663 msgid "" "Activate a workplane (with Sketch -> In Workplane) before applying a horizontal or vertical " "constraint." msgstr "" -#: constraint.cpp:472 +#: 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:493 +#: constraint.cpp:697 msgid "" "Bad selection for same orientation constraint. This constraint can apply to:\n" "\n" " * two normals\n" msgstr "" -#: constraint.cpp:545 +#: constraint.cpp:748 msgid "Must select an angle constraint." msgstr "" -#: constraint.cpp:557 +#: constraint.cpp:761 msgid "Must select a constraint with associated label." msgstr "" -#: constraint.cpp:568 +#: 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:625 -msgid "" -"The tangent arc and line segment must share an endpoint. Constrain them with Constrain -> On " -"Point before constraining tangent." -msgstr "" - -#: constraint.cpp:649 -msgid "" -"The tangent cubic and line segment must share an endpoint. Constrain them with Constrain -> On " -"Point before constraining tangent." -msgstr "" - -#: constraint.cpp:659 +#: constraint.cpp:872 msgid "Curve-curve tangency must apply in workplane." msgstr "" -#: constraint.cpp:677 -msgid "" -"The curves must share an endpoint. Constrain them with Constrain -> On Point before constraining " -"tangent." -msgstr "" - -#: constraint.cpp:686 +#: 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:704 +#: 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:719 +#: 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:730 +#: 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:18 +#: 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:60 +#: export.cpp:61 msgid "" "Bad selection for export section. Please select:\n" "\n" @@ -442,27 +509,27 @@ msgid "" " * a point and two line segments (plane through point and parallel to lines)\n" msgstr "" -#: export.cpp:805 +#: 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:590 +#: exportvector.cpp:592 msgid "Some aspects of the drawing have no DXF equivalent and were not exported:\n" msgstr "" -#: exportvector.cpp:807 +#: exportvector.cpp:838 msgid "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." msgstr "" -#: file.cpp:44 group.cpp:95 +#: file.cpp:44 group.cpp:91 msgctxt "group-name" msgid "sketch-in-plane" msgstr "" @@ -472,435 +539,518 @@ msgctxt "group-name" msgid "#references" msgstr "" -#: file.cpp:539 +#: 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 "" -#: graphicswin.cpp:29 +#: 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 "" + +#: 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 "" + +#: file.cpp:882 +msgctxt "button" +msgid "&Yes" +msgstr "" + +#: file.cpp:884 +msgctxt "button" +msgid "&No" +msgstr "" + +#: file.cpp:886 solvespace.cpp:652 +msgctxt "button" +msgid "&Cancel" +msgstr "" + +#: graphicswin.cpp:41 msgid "&File" msgstr "" -#: graphicswin.cpp:30 +#: graphicswin.cpp:42 msgid "&New" msgstr "" -#: graphicswin.cpp:31 +#: graphicswin.cpp:43 msgid "&Open..." msgstr "" -#: graphicswin.cpp:32 +#: graphicswin.cpp:44 msgid "Open &Recent" msgstr "" -#: graphicswin.cpp:33 +#: graphicswin.cpp:45 msgid "&Save" msgstr "" -#: graphicswin.cpp:34 +#: graphicswin.cpp:46 msgid "Save &As..." msgstr "" -#: graphicswin.cpp:36 +#: graphicswin.cpp:48 msgid "Export &Image..." msgstr "" -#: graphicswin.cpp:37 +#: graphicswin.cpp:49 msgid "Export 2d &View..." msgstr "" -#: graphicswin.cpp:38 +#: graphicswin.cpp:50 msgid "Export 2d &Section..." msgstr "" -#: graphicswin.cpp:39 +#: graphicswin.cpp:51 msgid "Export 3d &Wireframe..." msgstr "" -#: graphicswin.cpp:40 +#: graphicswin.cpp:52 msgid "Export Triangle &Mesh..." msgstr "" -#: graphicswin.cpp:41 +#: graphicswin.cpp:53 msgid "Export &Surfaces..." msgstr "" -#: graphicswin.cpp:42 +#: graphicswin.cpp:54 msgid "Im&port..." msgstr "" -#: graphicswin.cpp:45 +#: graphicswin.cpp:57 msgid "E&xit" msgstr "" -#: graphicswin.cpp:48 +#: graphicswin.cpp:60 msgid "&Edit" msgstr "" -#: graphicswin.cpp:49 +#: graphicswin.cpp:61 msgid "&Undo" msgstr "" -#: graphicswin.cpp:50 +#: graphicswin.cpp:62 msgid "&Redo" msgstr "" -#: graphicswin.cpp:51 +#: graphicswin.cpp:63 msgid "Re&generate All" msgstr "" -#: graphicswin.cpp:53 +#: graphicswin.cpp:65 msgid "Snap Selection to &Grid" msgstr "" -#: graphicswin.cpp:54 +#: graphicswin.cpp:66 msgid "Rotate Imported &90°" msgstr "" -#: graphicswin.cpp:56 +#: graphicswin.cpp:68 msgid "Cu&t" msgstr "" -#: graphicswin.cpp:57 +#: graphicswin.cpp:69 msgid "&Copy" msgstr "" -#: graphicswin.cpp:58 +#: graphicswin.cpp:70 msgid "&Paste" msgstr "" -#: graphicswin.cpp:59 +#: graphicswin.cpp:71 msgid "Paste &Transformed..." msgstr "" -#: graphicswin.cpp:60 +#: graphicswin.cpp:72 msgid "&Delete" msgstr "" -#: graphicswin.cpp:62 +#: graphicswin.cpp:74 msgid "Select &Edge Chain" msgstr "" -#: graphicswin.cpp:63 +#: graphicswin.cpp:75 msgid "Select &All" msgstr "" -#: graphicswin.cpp:64 +#: graphicswin.cpp:76 msgid "&Unselect All" msgstr "" -#: graphicswin.cpp:66 +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "" + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "" + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "" + +#: graphicswin.cpp:84 msgid "&View" msgstr "" -#: graphicswin.cpp:67 +#: graphicswin.cpp:85 msgid "Zoom &In" msgstr "" -#: graphicswin.cpp:68 +#: graphicswin.cpp:86 msgid "Zoom &Out" msgstr "" -#: graphicswin.cpp:69 +#: graphicswin.cpp:87 msgid "Zoom To &Fit" msgstr "" -#: graphicswin.cpp:71 +#: graphicswin.cpp:89 msgid "Align View to &Workplane" msgstr "" -#: graphicswin.cpp:72 +#: graphicswin.cpp:90 msgid "Nearest &Ortho View" msgstr "" -#: graphicswin.cpp:73 +#: graphicswin.cpp:91 msgid "Nearest &Isometric View" msgstr "" -#: graphicswin.cpp:74 +#: graphicswin.cpp:92 msgid "&Center View At Point" msgstr "" -#: graphicswin.cpp:76 +#: graphicswin.cpp:94 msgid "Show Snap &Grid" msgstr "" -#: graphicswin.cpp:77 +#: graphicswin.cpp:95 +msgid "Darken Inactive Solids" +msgstr "" + +#: graphicswin.cpp:96 msgid "Use &Perspective Projection" msgstr "" -#: graphicswin.cpp:78 -msgid "Dimension &Units" +#: graphicswin.cpp:97 +msgid "Show E&xploded View" msgstr "" -#: graphicswin.cpp:79 -msgid "Dimensions in &Inches" +#: graphicswin.cpp:98 +msgid "Dimension &Units" msgstr "" -#: graphicswin.cpp:80 +#: graphicswin.cpp:99 msgid "Dimensions in &Millimeters" msgstr "" -#: graphicswin.cpp:81 +#: graphicswin.cpp:100 msgid "Dimensions in M&eters" msgstr "" -#: graphicswin.cpp:83 +#: 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:84 +#: graphicswin.cpp:105 msgid "Show Property Bro&wser" msgstr "" -#: graphicswin.cpp:86 +#: graphicswin.cpp:107 msgid "&Full Screen" msgstr "" -#: graphicswin.cpp:88 +#: graphicswin.cpp:109 msgid "&New Group" msgstr "" -#: graphicswin.cpp:89 +#: graphicswin.cpp:110 msgid "Sketch In &3d" msgstr "" -#: graphicswin.cpp:90 +#: graphicswin.cpp:111 msgid "Sketch In New &Workplane" msgstr "" -#: graphicswin.cpp:92 +#: graphicswin.cpp:113 msgid "Step &Translating" msgstr "" -#: graphicswin.cpp:93 +#: graphicswin.cpp:114 msgid "Step &Rotating" msgstr "" -#: graphicswin.cpp:95 +#: graphicswin.cpp:116 msgid "E&xtrude" msgstr "" -#: graphicswin.cpp:96 +#: graphicswin.cpp:117 +msgid "&Helix" +msgstr "" + +#: graphicswin.cpp:118 msgid "&Lathe" msgstr "" -#: graphicswin.cpp:98 +#: graphicswin.cpp:119 +msgid "Re&volve" +msgstr "" + +#: graphicswin.cpp:121 msgid "Link / Assemble..." msgstr "" -#: graphicswin.cpp:99 +#: graphicswin.cpp:122 msgid "Link Recent" msgstr "" -#: graphicswin.cpp:101 +#: graphicswin.cpp:124 msgid "&Sketch" msgstr "" -#: graphicswin.cpp:102 +#: graphicswin.cpp:125 msgid "In &Workplane" msgstr "" -#: graphicswin.cpp:103 +#: graphicswin.cpp:126 msgid "Anywhere In &3d" msgstr "" -#: graphicswin.cpp:105 +#: graphicswin.cpp:128 msgid "Datum &Point" msgstr "" -#: graphicswin.cpp:106 -msgid "&Workplane" +#: graphicswin.cpp:129 +msgid "Wor&kplane" msgstr "" -#: graphicswin.cpp:108 +#: graphicswin.cpp:131 msgid "Line &Segment" msgstr "" -#: graphicswin.cpp:109 +#: graphicswin.cpp:132 msgid "C&onstruction Line Segment" msgstr "" -#: graphicswin.cpp:110 +#: graphicswin.cpp:133 msgid "&Rectangle" msgstr "" -#: graphicswin.cpp:111 +#: graphicswin.cpp:134 msgid "&Circle" msgstr "" -#: graphicswin.cpp:112 +#: graphicswin.cpp:135 msgid "&Arc of a Circle" msgstr "" -#: graphicswin.cpp:113 +#: graphicswin.cpp:136 msgid "&Bezier Cubic Spline" msgstr "" -#: graphicswin.cpp:115 +#: graphicswin.cpp:138 msgid "&Text in TrueType Font" msgstr "" -#: graphicswin.cpp:116 -msgid "&Image" +#: graphicswin.cpp:139 +msgid "I&mage" msgstr "" -#: graphicswin.cpp:118 +#: graphicswin.cpp:141 msgid "To&ggle Construction" msgstr "" -#: graphicswin.cpp:119 -msgid "Tangent &Arc at Point" +#: graphicswin.cpp:142 +msgid "Ta&ngent Arc at Point" msgstr "" -#: graphicswin.cpp:120 +#: graphicswin.cpp:143 msgid "Split Curves at &Intersection" msgstr "" -#: graphicswin.cpp:122 +#: graphicswin.cpp:145 msgid "&Constrain" msgstr "" -#: graphicswin.cpp:123 +#: graphicswin.cpp:146 msgid "&Distance / Diameter" msgstr "" -#: graphicswin.cpp:124 +#: graphicswin.cpp:147 msgid "Re&ference Dimension" msgstr "" -#: graphicswin.cpp:125 -msgid "A&ngle" +#: graphicswin.cpp:148 +msgid "A&ngle / Equal Angle" msgstr "" -#: graphicswin.cpp:126 +#: graphicswin.cpp:149 msgid "Reference An&gle" msgstr "" -#: graphicswin.cpp:127 +#: graphicswin.cpp:150 msgid "Other S&upplementary Angle" msgstr "" -#: graphicswin.cpp:128 +#: graphicswin.cpp:151 msgid "Toggle R&eference Dim" msgstr "" -#: graphicswin.cpp:130 +#: graphicswin.cpp:153 msgid "&Horizontal" msgstr "" -#: graphicswin.cpp:131 +#: graphicswin.cpp:154 msgid "&Vertical" msgstr "" -#: graphicswin.cpp:133 +#: graphicswin.cpp:156 msgid "&On Point / Curve / Plane" msgstr "" -#: graphicswin.cpp:134 -msgid "E&qual Length / Radius / Angle" +#: graphicswin.cpp:157 +msgid "E&qual Length / Radius" msgstr "" -#: graphicswin.cpp:135 -msgid "Length Ra&tio" +#: graphicswin.cpp:158 +msgid "Length / Arc Ra&tio" msgstr "" -#: graphicswin.cpp:136 -msgid "Length Diff&erence" +#: graphicswin.cpp:159 +msgid "Length / Arc Diff&erence" msgstr "" -#: graphicswin.cpp:137 +#: graphicswin.cpp:160 msgid "At &Midpoint" msgstr "" -#: graphicswin.cpp:138 +#: graphicswin.cpp:161 msgid "S&ymmetric" msgstr "" -#: graphicswin.cpp:139 +#: graphicswin.cpp:162 msgid "Para&llel / Tangent" msgstr "" -#: graphicswin.cpp:140 +#: graphicswin.cpp:163 msgid "&Perpendicular" msgstr "" -#: graphicswin.cpp:141 +#: graphicswin.cpp:164 msgid "Same Orient&ation" msgstr "" -#: graphicswin.cpp:142 +#: graphicswin.cpp:165 msgid "Lock Point Where &Dragged" msgstr "" -#: graphicswin.cpp:144 +#: graphicswin.cpp:167 msgid "Comment" msgstr "" -#: graphicswin.cpp:146 +#: graphicswin.cpp:169 msgid "&Analyze" msgstr "" -#: graphicswin.cpp:147 +#: graphicswin.cpp:170 msgid "Measure &Volume" msgstr "" -#: graphicswin.cpp:148 +#: graphicswin.cpp:171 msgid "Measure A&rea" msgstr "" -#: graphicswin.cpp:149 +#: graphicswin.cpp:172 msgid "Measure &Perimeter" msgstr "" -#: graphicswin.cpp:150 +#: graphicswin.cpp:173 msgid "Show &Interfering Parts" msgstr "" -#: graphicswin.cpp:151 +#: graphicswin.cpp:174 msgid "Show &Naked Edges" msgstr "" -#: graphicswin.cpp:152 +#: graphicswin.cpp:175 msgid "Show &Center of Mass" msgstr "" -#: graphicswin.cpp:154 -msgid "Show Degrees of &Freedom" +#: graphicswin.cpp:177 +msgid "Show &Underconstrained Points" msgstr "" -#: graphicswin.cpp:156 +#: graphicswin.cpp:179 msgid "&Trace Point" msgstr "" -#: graphicswin.cpp:157 +#: graphicswin.cpp:180 msgid "&Stop Tracing..." msgstr "" -#: graphicswin.cpp:158 +#: graphicswin.cpp:181 msgid "Step &Dimension..." msgstr "" -#: graphicswin.cpp:160 +#: graphicswin.cpp:183 msgid "&Help" msgstr "" -#: graphicswin.cpp:161 +#: graphicswin.cpp:184 +msgid "&Language" +msgstr "" + +#: graphicswin.cpp:185 msgid "&Website / Manual" msgstr "" -#: graphicswin.cpp:162 -msgid "&Language" +#: graphicswin.cpp:186 +msgid "&Go to GitHub commit" msgstr "" -#: graphicswin.cpp:164 +#: graphicswin.cpp:188 msgid "&About" msgstr "" -#: graphicswin.cpp:491 +#: graphicswin.cpp:362 +msgid "(no recent files)" +msgstr "" + +#: graphicswin.cpp:370 +#, c-format +msgid "File '%s' does not exist." +msgstr "" + +#: graphicswin.cpp:779 msgid "No workplane is active, so the grid will not appear." msgstr "" -#: graphicswin.cpp:500 +#: graphicswin.cpp:794 msgid "" "The perspective factor is set to zero, so the view will always be a parallel projection.\n" "\n" @@ -908,118 +1058,123 @@ msgid "" "around 0.3 is typical." msgstr "" -#: graphicswin.cpp:581 +#: graphicswin.cpp:884 msgid "Select a point; this point will become the center of the view on screen." msgstr "" -#: graphicswin.cpp:862 +#: graphicswin.cpp:1193 msgid "No additional entities share endpoints with the selected entities." msgstr "" -#: graphicswin.cpp:882 +#: 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:906 +#: 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:913 +#: 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:979 +#: graphicswin.cpp:1326 msgid "No workplane selected. Activating default workplane for this group." msgstr "" -#: graphicswin.cpp:984 +#: 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:1008 +#: 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:1019 +#: graphicswin.cpp:1361 msgid "click point on arc (draws anti-clockwise)" msgstr "" -#: graphicswin.cpp:1020 +#: graphicswin.cpp:1362 msgid "click to place datum point" msgstr "" -#: graphicswin.cpp:1021 +#: graphicswin.cpp:1363 msgid "click first point of line segment" msgstr "" -#: graphicswin.cpp:1023 +#: graphicswin.cpp:1365 msgid "click first point of construction line segment" msgstr "" -#: graphicswin.cpp:1024 +#: graphicswin.cpp:1366 msgid "click first point of cubic segment" msgstr "" -#: graphicswin.cpp:1025 +#: graphicswin.cpp:1367 msgid "click center of circle" msgstr "" -#: graphicswin.cpp:1026 +#: graphicswin.cpp:1368 msgid "click origin of workplane" msgstr "" -#: graphicswin.cpp:1027 +#: graphicswin.cpp:1369 msgid "click one corner of rectangle" msgstr "" -#: graphicswin.cpp:1028 +#: graphicswin.cpp:1370 msgid "click top left of text" msgstr "" -#: graphicswin.cpp:1034 +#: graphicswin.cpp:1376 msgid "click top left of image" msgstr "" -#: graphicswin.cpp:1047 +#: graphicswin.cpp:1402 msgid "No entities are selected. Select entities before trying to toggle their construction state." msgstr "" -#: group.cpp:90 +#: group.cpp:86 msgctxt "group-name" msgid "sketch-in-3d" msgstr "" -#: group.cpp:146 +#: 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:158 +#: group.cpp:170 msgid "" "Activate a workplane (Sketch -> In Workplane) before extruding. The sketch will be extruded " "normal to the workplane." msgstr "" -#: group.cpp:167 +#: group.cpp:179 msgctxt "group-name" msgid "extrude" msgstr "" -#: group.cpp:179 +#: 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" @@ -1028,12 +1183,48 @@ msgid "" " * a line segment (revolved about line segment)\n" msgstr "" -#: group.cpp:189 +#: group.cpp:205 msgctxt "group-name" msgid "lathe" msgstr "" -#: group.cpp:202 +#: 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 "" + +#: 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 "" + +#: 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" @@ -1042,411 +1233,590 @@ msgid "" "line / normal)\n" msgstr "" -#: group.cpp:215 +#: group.cpp:287 msgctxt "group-name" msgid "rotate" msgstr "" -#: group.cpp:226 +#: group.cpp:298 msgctxt "group-name" msgid "translate" msgstr "" -#: group.cpp:340 +#: group.cpp:422 msgid "(unnamed)" msgstr "" -#: groupmesh.cpp:626 +#: groupmesh.cpp:710 msgid "not closed contour, or not all same style!" msgstr "" -#: groupmesh.cpp:639 +#: groupmesh.cpp:723 msgid "points not all coplanar!" msgstr "" -#: groupmesh.cpp:641 +#: groupmesh.cpp:725 msgid "contour is self-intersecting!" msgstr "" -#: groupmesh.cpp:643 +#: groupmesh.cpp:727 msgid "zero-length edge!" msgstr "" -#: modify.cpp:237 +#: 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:284 +#: 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:371 +#: 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:575 +#: modify.cpp:595 msgid "Couldn't split this entity; lines, circles, or cubics only." msgstr "" -#: modify.cpp:601 +#: modify.cpp:622 msgid "Must be sketching in workplane to split." msgstr "" -#: modify.cpp:608 +#: 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:713 +#: modify.cpp:734 msgid "Can't split; no intersection found." msgstr "" -#: mouse.cpp:522 -msgid "No Style" +#: mouse.cpp:558 +msgid "Assign to Style" msgstr "" -#: mouse.cpp:523 -msgid "Newly Created Custom Style..." +#: mouse.cpp:574 +msgid "No Style" msgstr "" -#: mouse.cpp:571 -msgid "Assign to Style" +#: mouse.cpp:577 +msgid "Newly Created Custom Style..." msgstr "" -#: mouse.cpp:574 +#: mouse.cpp:584 msgid "Group Info" msgstr "" -#: mouse.cpp:577 +#: mouse.cpp:604 msgid "Style Info" msgstr "" -#: mouse.cpp:580 +#: mouse.cpp:624 msgid "Select Edge Chain" msgstr "" -#: mouse.cpp:585 +#: mouse.cpp:630 msgid "Toggle Reference Dimension" msgstr "" -#: mouse.cpp:591 +#: mouse.cpp:636 msgid "Other Supplementary Angle" msgstr "" -#: mouse.cpp:596 +#: mouse.cpp:641 msgid "Snap to Grid" msgstr "" -#: mouse.cpp:604 +#: mouse.cpp:650 msgid "Remove Spline Point" msgstr "" -#: mouse.cpp:615 +#: mouse.cpp:685 msgid "Add Spline Point" msgstr "" -#: mouse.cpp:619 +#: mouse.cpp:689 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "" + +#: mouse.cpp:714 msgid "Toggle Construction" msgstr "" -#: mouse.cpp:633 +#: mouse.cpp:730 msgid "Delete Point-Coincident Constraint" msgstr "" -#: mouse.cpp:639 +#: mouse.cpp:748 msgid "Cut" msgstr "" -#: mouse.cpp:640 +#: mouse.cpp:750 msgid "Copy" msgstr "" -#: mouse.cpp:643 +#: mouse.cpp:754 msgid "Select All" msgstr "" -#: mouse.cpp:647 +#: mouse.cpp:759 msgid "Paste" msgstr "" -#: mouse.cpp:648 +#: mouse.cpp:761 msgid "Paste Transformed..." msgstr "" -#: mouse.cpp:652 +#: mouse.cpp:766 msgid "Delete" msgstr "" -#: mouse.cpp:654 +#: mouse.cpp:769 msgid "Unselect All" msgstr "" -#: mouse.cpp:660 +#: mouse.cpp:776 msgid "Unselect Hovered" msgstr "" -#: mouse.cpp:665 +#: mouse.cpp:785 msgid "Zoom to Fit" msgstr "" -#: mouse.cpp:805 -msgid "Cannot add spline point: maximum number of points reached." -msgstr "" - -#: mouse.cpp:1009 mouse.cpp:1292 +#: mouse.cpp:987 mouse.cpp:1276 msgid "click next point of line, or press Esc" msgstr "" -#: mouse.cpp:1015 +#: mouse.cpp:993 msgid "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In Workplane." msgstr "" -#: mouse.cpp:1049 +#: mouse.cpp:1027 msgid "click to place other corner of rectangle" msgstr "" -#: mouse.cpp:1069 +#: mouse.cpp:1048 msgid "click to set radius" msgstr "" -#: mouse.cpp:1074 +#: mouse.cpp:1053 msgid "Can't draw arc in 3d; first, activate a workplane with Sketch -> In Workplane." msgstr "" -#: mouse.cpp:1093 +#: mouse.cpp:1072 msgid "click to place point" msgstr "" -#: mouse.cpp:1109 +#: mouse.cpp:1088 msgid "click next point of cubic, or press Esc" msgstr "" -#: mouse.cpp:1114 +#: mouse.cpp:1093 msgid "Sketching in a workplane already; sketch in 3d before creating new workplane." msgstr "" -#: mouse.cpp:1130 +#: mouse.cpp:1109 msgid "Can't draw text in 3d; first, activate a workplane with Sketch -> In Workplane." msgstr "" -#: mouse.cpp:1146 -msgid "click to place bottom left of text" +#: mouse.cpp:1126 +msgid "click to place bottom right of text" msgstr "" -#: mouse.cpp:1152 +#: mouse.cpp:1132 msgid "Can't draw image in 3d; first, activate a workplane with Sketch -> In Workplane." msgstr "" -#: mouse.cpp:1178 -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/cocoamain.mm:481 platform/gtkmain.cpp:607 platform/w32main.cpp:451 -#: platform/w32main.cpp:1388 -msgctxt "title" -msgid "(new sketch)" +#: platform/gui.cpp:89 +msgctxt "file-type" +msgid "ALL" msgstr "" -#: platform/cocoamain.mm:710 platform/gtkmain.cpp:912 platform/w32main.cpp:1307 -msgid "(no recent files)" +#: platform/gui.cpp:91 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "" + +#: 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:100 +msgctxt "file-type" +msgid "STL mesh" msgstr "" -#: platform/cocoamain.mm:828 platform/gtkmain.cpp:1020 +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "" + +#: platform/gui.cpp:104 +msgctxt "file-type" +msgid "VRML text file" +msgstr "" + +#: platform/gui.cpp:108 platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "STEP file" +msgstr "" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "PDF file" +msgstr "" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "" + +#: platform/gui.cpp:114 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "" + +#: platform/gui.cpp:116 platform/gui.cpp:123 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "HPGL file" +msgstr "" + +#: platform/gui.cpp:118 +msgctxt "file-type" +msgid "G Code" +msgstr "" + +#: platform/gui.cpp:127 +msgctxt "file-type" +msgid "AutoCAD DXF and DWG files" +msgstr "" + +#: platform/gui.cpp:131 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "" + +#: platform/guigtk.cpp:1434 platform/guimac.mm:1513 platform/guiwin.cpp:1641 msgid "untitled" msgstr "" -#: platform/cocoamain.mm:860 -msgid "Do you want to save the changes you made to the new sketch?" +#: platform/guigtk.cpp:1445 platform/guigtk.cpp:1481 platform/guimac.mm:1471 +#: platform/guiwin.cpp:1639 +msgctxt "title" +msgid "Save File" msgstr "" -#: platform/cocoamain.mm:862 -msgid "Your changes will be lost if you don't save them." +#: platform/guigtk.cpp:1446 platform/guigtk.cpp:1482 platform/guimac.mm:1454 +#: platform/guiwin.cpp:1645 +msgctxt "title" +msgid "Open File" msgstr "" -#: platform/cocoamain.mm:863 +#: platform/guigtk.cpp:1449 platform/guigtk.cpp:1488 msgctxt "button" -msgid "Save" +msgid "_Cancel" msgstr "" -#: platform/cocoamain.mm:864 platform/cocoamain.mm:905 +#: platform/guigtk.cpp:1450 platform/guigtk.cpp:1486 msgctxt "button" -msgid "Cancel" +msgid "_Save" msgstr "" -#: platform/cocoamain.mm:865 +#: platform/guigtk.cpp:1451 platform/guigtk.cpp:1487 msgctxt "button" -msgid "Don't Save" +msgid "_Open" msgstr "" -#: platform/cocoamain.mm:880 -msgid "An autosave file is available for this project." +#: solvespace.cpp:175 +msgctxt "title" +msgid "Autosave Available" +msgstr "" + +#: solvespace.cpp:176 +msgctxt "dialog" +msgid "An autosave file is available for this sketch." msgstr "" -#: platform/cocoamain.mm:882 +#: solvespace.cpp:177 +msgctxt "dialog" msgid "Do you want to load the autosave file instead?" msgstr "" -#: platform/cocoamain.mm:883 +#: solvespace.cpp:178 msgctxt "button" -msgid "Load" +msgid "&Load autosave" msgstr "" -#: platform/cocoamain.mm:884 +#: solvespace.cpp:180 msgctxt "button" -msgid "Don't Load" +msgid "Do&n't Load" msgstr "" -#: platform/cocoamain.mm:900 -msgid "" -"Do you want to locate it manually?\n" -"If you select “No”, any geometry that depends on the missing file will be removed." +#: solvespace.cpp:640 +msgctxt "title" +msgid "Modified File" msgstr "" -#: platform/cocoamain.mm:903 -msgctxt "button" -msgid "Yes" +#: solvespace.cpp:642 +#, c-format +msgctxt "dialog" +msgid "Do you want to save the changes you made to the sketch “%s”?" +msgstr "" + +#: 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 "" -#: platform/cocoamain.mm:906 +#: solvespace.cpp:649 msgctxt "button" -msgid "No" +msgid "&Save" msgstr "" -#: platform/cocoamain.mm:1126 platform/w32main.cpp:183 +#: solvespace.cpp:651 msgctxt "button" -msgid "OK" +msgid "Do&n't Save" msgstr "" -#: platform/cocoamain.mm:1211 platform/gtkmain.cpp:1382 platform/w32main.cpp:1410 -#: platform/w32main.cpp:1450 +#: solvespace.cpp:672 msgctxt "title" -msgid "Property Browser" +msgid "(new sketch)" msgstr "" -#: platform/gtkmain.cpp:968 +#: solvespace.cpp:683 msgctxt "title" -msgid "Open File" +msgid "Property Browser" msgstr "" -#: platform/gtkmain.cpp:970 -msgid "_Cancel" +#: 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 "" -#: platform/gtkmain.cpp:971 -msgid "_Open" +#: solvespace.cpp:834 +#, c-format +msgid "Can't identify file type from file extension of filename '%s'; try .dxf or .dwg." msgstr "" -#: platform/gtkmain.cpp:1010 -msgctxt "title" -msgid "Save File" +#: solvespace.cpp:886 +msgid "Constraint must have a label, and must not be a reference dimension." msgstr "" -#: platform/gtkmain.cpp:1013 platform/gtkmain.cpp:1049 platform/gtkmain.cpp:1097 -msgctxt "button" -msgid "_Cancel" +#: solvespace.cpp:890 +msgid "Bad selection for step dimension; select a constraint." msgstr "" -#: platform/gtkmain.cpp:1014 platform/gtkmain.cpp:1047 -msgctxt "button" -msgid "_Save" +#: solvespace.cpp:914 +msgid "The assembly does not interfere, good." msgstr "" -#: platform/gtkmain.cpp:1042 platform/w32main.cpp:1167 +#: solvespace.cpp:930 +#, c-format msgid "" -"The file has changed since it was last saved.\n" +"The volume of the solid model is:\n" "\n" -"Do you want to save the changes?" +" %s" msgstr "" -#: platform/gtkmain.cpp:1046 platform/w32main.cpp:1169 -msgctxt "title" -msgid "Modified File" +#: solvespace.cpp:939 +#, c-format +msgid "" +"\n" +"The volume of current group mesh is:\n" +"\n" +" %s" msgstr "" -#: platform/gtkmain.cpp:1048 -msgctxt "button" -msgid "Do_n't Save" +#: solvespace.cpp:944 +msgid "" +"\n" +"\n" +"Curved surfaces have been approximated as triangles.\n" +"This introduces error, typically of around 1%." msgstr "" -#: platform/gtkmain.cpp:1066 platform/w32main.cpp:1193 +#: solvespace.cpp:959 +#, c-format msgid "" -"An autosave file is available for this project.\n" +"The surface area of the selected faces is:\n" +"\n" +" %s\n" "\n" -"Do you want to load the autosave file instead?" +"Curves have been approximated as piecewise linear.\n" +"This introduces error, typically of around 1%%." msgstr "" -#: platform/gtkmain.cpp:1070 platform/w32main.cpp:1195 -msgctxt "title" -msgid "Autosave Available" +#: solvespace.cpp:968 +msgid "" +"This group does not contain a correctly-formed 2d closed area. It is open, not coplanar, or self-" +"intersecting." msgstr "" -#: platform/gtkmain.cpp:1071 -msgctxt "button" -msgid "_Load autosave" +#: 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 "" -#: platform/gtkmain.cpp:1072 -msgctxt "button" -msgid "Do_n't Load" +#: 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 "" -#: platform/gtkmain.cpp:1093 platform/w32main.cpp:1223 -msgctxt "title" -msgid "Missing File" +#: solvespace.cpp:1006 +msgid "Bad selection for perimeter; select line segments, arcs, and curves." msgstr "" -#: platform/gtkmain.cpp:1094 -msgctxt "button" -msgid "_Yes" +#: solvespace.cpp:1022 +msgid "Bad selection for trace; select a single point." msgstr "" -#: platform/gtkmain.cpp:1095 -msgctxt "button" -msgid "_No" +#: solvespace.cpp:1049 +#, c-format +msgid "Couldn't write to '%s'" msgstr "" -#: platform/gtkmain.cpp:1306 platform/w32main.cpp:179 -msgctxt "title" -msgid "Error" +#: solvespace.cpp:1079 +msgid "The mesh is self-intersecting (NOT okay, invalid)." msgstr "" -#: platform/gtkmain.cpp:1306 platform/w32main.cpp:179 -msgctxt "title" -msgid "Message" +#: 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 "" -#: style.cpp:160 +#: solvespace.cpp:1086 +#, c-format +msgid "" +"\n" +"\n" +"The model contains %d triangles, from %d surfaces." +msgstr "" + +#: solvespace.cpp:1090 +#, c-format +msgid "" +"%s\n" +"\n" +"%s\n" +"\n" +"Zero problematic edges, good.%s" +msgstr "" + +#: solvespace.cpp:1093 +#, c-format +msgid "" +"%s\n" +"\n" +"%s\n" +"\n" +"%d problematic edges, bad.%s" +msgstr "" + +#: 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:659 +#: style.cpp:735 msgid "Style name cannot be empty" msgstr "" -#: textscreens.cpp:662 +#: textscreens.cpp:837 msgid "Can't repeat fewer than 1 time." msgstr "" -#: textscreens.cpp:666 +#: textscreens.cpp:841 msgid "Can't repeat more than 999 times." msgstr "" -#: textscreens.cpp:695 +#: textscreens.cpp:866 msgid "Group name cannot be empty" msgstr "" -#: textscreens.cpp:739 +#: textscreens.cpp:918 msgid "Opacity must be between zero and one." msgstr "" -#: textscreens.cpp:778 +#: textscreens.cpp:953 msgid "Radius cannot be zero or negative." msgstr "" @@ -1551,97 +1921,60 @@ msgid "New group rotating active sketch" msgstr "" #: toolbar.cpp:72 -msgid "New group step and repeat rotating" +msgid "New group helix from active sketch" msgstr "" #: toolbar.cpp:74 -msgid "New group step and repeat translating" +msgid "New group revolve active sketch" msgstr "" #: toolbar.cpp:76 -msgid "New group in new workplane (thru given entities)" +msgid "New group step and repeat rotating" msgstr "" #: toolbar.cpp:78 -msgid "New group in 3d" +msgid "New group step and repeat translating" msgstr "" #: toolbar.cpp:80 -msgid "New group linking / assembling file" -msgstr "" - -#: toolbar.cpp:84 -msgid "Nearest isometric view" -msgstr "" - -#: toolbar.cpp:86 -msgid "Align view to active workplane" -msgstr "" - -#: ui.h:69 -msgid "SolveSpace models" -msgstr "" - -#: ui.h:74 -msgid "PNG file" -msgstr "" - -#: ui.h:79 -msgid "STL mesh" -msgstr "" - -#: ui.h:80 -msgid "Wavefront OBJ mesh" -msgstr "" - -#: ui.h:81 -msgid "Three.js-compatible mesh, with viewer" -msgstr "" - -#: ui.h:82 -msgid "Three.js-compatible mesh, mesh only" -msgstr "" - -#: ui.h:87 ui.h:95 ui.h:103 -msgid "STEP file" -msgstr "" - -#: ui.h:92 -msgid "PDF file" +msgid "New group in new workplane (thru given entities)" msgstr "" -#: ui.h:93 -msgid "Encapsulated PostScript" +#: toolbar.cpp:82 +msgid "New group in 3d" msgstr "" -#: ui.h:94 -msgid "Scalable Vector Graphics" +#: toolbar.cpp:84 +msgid "New group linking / assembling file" msgstr "" -#: ui.h:96 ui.h:104 -msgid "DXF file (AutoCAD 2007)" +#: toolbar.cpp:88 +msgid "Nearest isometric view" msgstr "" -#: ui.h:97 -msgid "HPGL file" +#: toolbar.cpp:90 +msgid "Align view to active workplane" msgstr "" -#: ui.h:98 -msgid "G Code" +#: util.cpp:165 +msgctxt "title" +msgid "Error" msgstr "" -#: ui.h:109 -msgid "AutoCAD DXF and DWG files" +#: util.cpp:165 +msgctxt "title" +msgid "Message" msgstr "" -#: ui.h:114 -msgid "Comma-separated values" +#: util.cpp:170 +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/threejs/SolveSpaceControls.js b/res/threejs/SolveSpaceControls.js index e141aceec..ca74fd520 100644 --- a/res/threejs/SolveSpaceControls.js +++ b/res/threejs/SolveSpaceControls.js @@ -470,9 +470,9 @@ solvespace = function(obj, params) { changeBasis.makeBasis(camera.right, camera.up, n); for (var i = 0; i < 2; i++) { - var newLightPos = changeBasis.applyToVector3Array( - [obj.lights.d[i].direction[0], obj.lights.d[i].direction[1], - obj.lights.d[i].direction[2]]); + var newLightPos = new THREE.Vector3(obj.lights.d[i].direction[0], + obj.lights.d[i].direction[1], + obj.lights.d[i].direction[2]).applyMatrix4(changeBasis); directionalLightArray[i].position.set(newLightPos[0], newLightPos[1], newLightPos[2]); } @@ -515,7 +515,7 @@ solvespace = function(obj, params) { } geometry.computeBoundingSphere(); - return new THREE.Mesh(geometry, new THREE.MultiMaterial(materialList)); + return new THREE.Mesh(geometry, materialList); } function createEdges(meshObj) { diff --git a/res/threejs/three-r111.min.js.gz b/res/threejs/three-r111.min.js.gz new file mode 100644 index 000000000..7e6f57f19 Binary files /dev/null and b/res/threejs/three-r111.min.js.gz differ diff --git a/res/threejs/three-r76.js.gz b/res/threejs/three-r76.js.gz deleted file mode 100644 index e2b9ca165..000000000 Binary files a/res/threejs/three-r76.js.gz and /dev/null differ diff --git a/res/win32/versioninfo.rc.in b/res/win32/versioninfo.rc.in index 0772a39dd..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-2016 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 36fdc5f53..3eba0328b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,11 +6,6 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) -set(HAVE_BACKTRACE ${Backtrace_FOUND}) -if(HAVE_BACKTRACE) - set(BACKTRACE_HEADER <${Backtrace_HEADER}>) -endif() - set(HAVE_SPACEWARE ${SPACEWARE_FOUND}) if(NOT WIN32 OR APPLE) @@ -24,301 +19,307 @@ endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) -# platform utilities - -if(WIN32) - set(util_SOURCES - platform/utilwin.cpp) -else() - set(util_SOURCES - platform/utilunix.cpp) -endif() - -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}) - -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}) -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} - ${Q3D_INCLUDE_DIR}) + ${MIMALLOC_INCLUDE_DIR} + ${EIGEN3_INCLUDE_DIRS}) +target_link_libraries(slvs_deps INTERFACE + dxfrw + ${ZLIB_LIBRARY} + ${PNG_LIBRARY} + ${FREETYPE_LIBRARY} + ${CAIRO_LIBRARIES} + mimalloc-static) 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 - dsc.h - expr.h - polygon.h - sketch.h +# libslvs +set(libslvs_sources solvespace.h - ui.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 - constrainteq.cpp - describescreen.cpp - draw.cpp - drawconstraint.cpp - drawentity.cpp + util.cpp entity.cpp - export.cpp - exportstep.cpp - exportvector.cpp expr.cpp - file.cpp - generate.cpp - graphicswin.cpp - group.cpp - groupmesh.cpp - importdxf.cpp - mesh.cpp - modify.cpp - mouse.cpp - polyline.cpp - polygon.cpp - resource.cpp - request.cpp - style.cpp + constrainteq.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 - q3d_header) - -target_link_libraries(solvespace-core - dxfrw - ${util_LIBRARIES} - ${ZLIB_LIBRARY} - ${PNG_LIBRARY} - ${FREETYPE_LIBRARY} - flatbuffers) - -if(Backtrace_FOUND) - target_link_libraries(solvespace-core - ${Backtrace_LIBRARY}) + platform/platformbase.cpp + lib.cpp +) + +if(ENABLE_PYTHON_LIB) + add_library(slvs STATIC ${libslvs_sources}) + target_compile_definitions(slvs PRIVATE -DSTATIC_LIB) +elseif(ENABLE_EMSCRIPTEN_LIB) + set(CMAKE_EXECUTABLE_SUFFIX ".js") + add_executable(slvs ${libslvs_sources} jslib.cpp) +else() + add_library(slvs SHARED ${libslvs_sources}) + target_compile_definitions(slvs PRIVATE -DEXPORT_DLL) endif() -target_compile_options(solvespace-core - PRIVATE ${COVERAGE_FLAGS}) +set_target_properties(slvs PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_compile_definitions(slvs PRIVATE -DLIBRARY) +target_link_libraries(slvs PRIVATE mimalloc-static) -# solvespace translations - -if(HAVE_GETTEXT) - set(inputs - ${solvespace_core_SOURCES} - ${solvespace_core_HEADERS} - ${every_platform_SOURCES}) - - set(templ_po ${CMAKE_CURRENT_BINARY_DIR}/../res/messages.po) +target_include_directories(slvs + PRIVATE + ${MIMALLOC_INCLUDE_DIR} + ${EIGEN3_INCLUDE_DIRS} + PUBLIC + ${CMAKE_SOURCE_DIR}/include) - 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) +set_target_properties(slvs PROPERTIES + PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/include/slvs.h + VERSION ${PROJECT_VERSION} + SOVERSION 1 +) + +if(ENABLE_EMSCRIPTEN_LIB) + set_target_properties(slvs PROPERTIES LINK_FLAGS "\ + -l embind \ + -s MODULARIZE=1\ + -s EXPORT_NAME=\"solvespace\" \ + -s SINGLE_FILE=1 \ + -s INITIAL_MEMORY=536870912 \ + -s ALLOW_MEMORY_GROWTH \ + -O3 \ + -flto \ + --closure 1") +endif() - 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() +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_PYTHON_LIB) 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} + OUTPUT lib.c + COMMAND cython lib.pyx -o ${CMAKE_CURRENT_BINARY_DIR}/lib.c --include-dir ${CMAKE_SOURCE_DIR}/include --module-name solvespace + DEPENDS lib.pyx WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMENT "Extracting translations" - VERBATIM) + ) + find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) + Python_add_library(solvespace MODULE lib.c) + set_target_properties(solvespace PROPERTIES POSITION_INDEPENDENT_CODE ON) + target_compile_definitions(solvespace PRIVATE -DEXPORT_DLL) + target_link_libraries( + solvespace + PRIVATE + slvs + Python::Module + ) + if(SKBUILD) + install(TARGETS solvespace DESTINATION ${SKBUILD_PROJECT_NAME}) + else() + install(TARGETS solvespace DESTINATION "slvs") + install( + DIRECTORY ${CMAKE_SOURCE_DIR}/python/slvs/ + DESTINATION "slvs" + ) + endif() +else() + # if(NOT WIN32) + install(TARGETS slvs + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + # endif() +endif() - 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}) +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 + dsc.h + expr.h + polygon.h + sketch.h + solvespace.h + ui.h + platform/platform.h + render/render.h + render/gl3shader.h + srf/surface.h + bsp.cpp + clipboard.cpp + confscreen.cpp + constraint.cpp + 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 + importmesh.cpp + mesh.cpp + modify.cpp + mouse.cpp + polyline.cpp + polygon.cpp + resource.cpp + request.cpp + style.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/shell.cpp + srf/surface.cpp + srf/surfinter.cpp + srf/triangulate.cpp) + + 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_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 @@ -326,49 +327,142 @@ endif() if(ENABLE_GUI) add_executable(solvespace WIN32 MACOSX_BUNDLE ${solvespace_core_gl_SOURCES} - ${platform_SOURCES} + platform/entrygui.cpp $) 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 /STACK:33554432") + # Platform-specific + if(WIN32) + 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) + set(SHELL ${CMAKE_CURRENT_SOURCE_DIR}/platform/html/emshell.html) + set(LINK_FLAGS + --bind --shell-file ${SHELL} + --no-heap-copy -s ALLOW_MEMORY_GROWTH=1 -s WASM=1 -s ASYNCIFY=1 + -s DYNCALLS=1 -s ASSERTIONS=1 + -s TOTAL_STACK=33554432 -s TOTAL_MEMORY=134217728) + + get_target_property(resource_names resources NAMES) + foreach(resource ${resource_names}) + list(APPEND LINK_FLAGS --preload-file ${resource}) + endforeach() + + if(CMAKE_BUILD_TYPE STREQUAL Debug) + list(APPEND LINK_FLAGS + --emrun --emit-symbol-map + -s DEMANGLE_SUPPORT=1 + -s SAFE_HEAP=1) + endif() + + target_sources(solvespace PRIVATE + platform/guihtml.cpp) + + string(REPLACE ";" " " LINK_FLAGS "${LINK_FLAGS}") + set_target_properties(solvespace PROPERTIES + LINK_FLAGS "${LINK_FLAGS}") + set_source_files_properties(platform/guihtml.cpp PROPERTIES + OBJECT_DEPENDS ${SHELL}) + + add_custom_command( + TARGET solvespace POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/platform/html/solvespaceui.css + ${EXECUTABLE_OUTPUT_PATH}/solvespaceui.css + COMMENT "Copying UI stylesheet" + VERBATIM) + add_custom_command( + TARGET solvespace POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/platform/html/solvespaceui.js + ${EXECUTABLE_OUTPUT_PATH}/solvespaceui.js + COMMENT "Copying UI script solvespaceui.js" + VERBATIM) + add_custom_command( + TARGET solvespace POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/platform/html/filemanagerui.js + ${EXECUTABLE_OUTPUT_PATH}/filemanagerui.js + COMMENT "Copying UI script filemanagerui.sj" + VERBATIM) + else() + if(USE_GTK4) + target_sources(solvespace PRIVATE + platform/guigtk4.cpp) + else() + target_sources(solvespace PRIVATE + platform/guigtk.cpp) + endif() + + 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(MSVC) set_target_properties(solvespace PROPERTIES - OUTPUT_NAME SolveSpace) + LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /INCREMENTAL:NO /OPT:REF") endif() endif() # 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 @@ -392,7 +486,7 @@ endif() # solvespace unix package -if(NOT (WIN32 OR APPLE)) +if(NOT (WIN32 OR APPLE OR EMSCRIPTEN)) if(ENABLE_GUI) install(TARGETS solvespace RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) @@ -406,22 +500,30 @@ endif() # solvespace macOS package if(APPLE) - set(bundle SolveSpace) - set(bundle_bin ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app/Contents/MacOS) - - add_custom_command(TARGET solvespace POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory ${bundle_bin} - COMMAND ${CMAKE_COMMAND} -E copy $ ${bundle_bin} - COMMENT "Bundling executable solvespace-cli" - VERBATIM) - - add_custom_command(OUTPUT ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg - COMMAND ${CMAKE_COMMAND} -E remove ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg - COMMAND hdiutil create -srcfolder ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app - ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg - DEPENDS solvespace - COMMENT "Building ${bundle}.dmg" - VERBATIM) - add_custom_target(${bundle}-dmg ALL - DEPENDS ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg) + set(LIBOMP_LIB_PATH ${OpenMP_CXX_INCLUDE_DIRS}/../lib/libomp.dylib) + set(LIBOMP_LINK_PATH "@executable_path/../Resources/libomp.dylib") + set(LIBOMP_LINK_PATH_UTILS "@executable_path/SolveSpace.app/Contents/Resources/libomp.dylib") + if(ENABLE_GUI) + add_custom_command(TARGET solvespace POST_BUILD + COMMAND cp -r ${CMAKE_BINARY_DIR}/Resources $ + ) + if(ENABLE_OPENMP) + execute_process(COMMAND install_name_tool -id ${LIBOMP_LINK_PATH} ${LIBOMP_LIB_PATH}) + message("FROM " ${${LIBOMP_LIB_PATH}} "TO" $/Resources/libomp.dylib) + add_custom_command(TARGET solvespace POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${LIBOMP_LIB_PATH} $/Resources/libomp.dylib + COMMAND install_name_tool -change ${LIBOMP_LINK_PATH} ${LIBOMP_LINK_PATH_UTILS} $ + ) + endif() + endif() + if(ENABLE_TESTS AND ENABLE_OPENMP) + add_custom_command(TARGET solvespace POST_BUILD + COMMAND install_name_tool -change ${LIBOMP_LINK_PATH} ${LIBOMP_LINK_PATH_UTILS} $) + endif() + if(ENABLE_CLI) + add_custom_command(TARGET solvespace POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ $ + COMMENT "Bundling executable solvespace-cli" + VERBATIM) + endif() endif() diff --git a/src/bsp.cpp b/src/bsp.cpp index 9dcaace9b..bac5ff303 100644 --- a/src/bsp.cpp +++ b/src/bsp.cpp @@ -62,14 +62,17 @@ void SBsp3::InsertInPlane(bool pos2, STriangle *tr, SMesh *m) { ll = ll->more; } - if(m->flipNormal && ((!pos2 && !onFace) || - (onFace && !sameNormal && m->keepCoplanar))) - { - m->AddTriangle(tr->meta, tr->c, tr->b, tr->a); - } else if(!(m->flipNormal) && ((pos2 && !onFace) || - (onFace && sameNormal && m->keepCoplanar))) - { - m->AddTriangle(tr->meta, tr->a, tr->b, tr->c); + if((!onFace && ((m->keepInsideOtherShell && !pos2) || + (!m->keepInsideOtherShell && pos2))) || + (onFace && ((m->keepCoplanar && m->flipNormal && !sameNormal) || + (m->keepCoplanar && !m->flipNormal && sameNormal)))) { + // We have decided that we need to keep a triangle either inside, + // outside or on the other shell. So add it and flip it if requested. + if(!(m->flipNormal)) { + m->AddTriangle(tr->meta, tr->a, tr->b, tr->c); + } else { + m->AddTriangle(tr->meta, tr->c, tr->b, tr->a); + } } else { m->atLeastOneDiscarded = true; } @@ -101,10 +104,15 @@ void SBsp3::InsertHow(BspClass how, STriangle *tr, SMesh *instead) { return; alt: - if(how == BspClass::POS && !(instead->flipNormal)) { - instead->AddTriangle(tr->meta, tr->a, tr->b, tr->c); - } else if(how == BspClass::NEG && instead->flipNormal) { - instead->AddTriangle(tr->meta, tr->c, tr->b, tr->a); + if(((BspClass::POS == how) && !instead->keepInsideOtherShell) || + ((BspClass::NEG == how) && instead->keepInsideOtherShell)) { + // We have decided that we need to keep a triangle (either inside or + // outside the other shell. So add it and flip it if requested. + if(!(instead->flipNormal)) { + instead->AddTriangle(tr->meta, tr->a, tr->b, tr->c); + } else { + instead->AddTriangle(tr->meta, tr->c, tr->b, tr->a); + } } else if(how == BspClass::COPLANAR) { if(edges) { edges->InsertTriangle(tr, instead, this); @@ -431,6 +439,9 @@ SBsp3 *SBsp3::InsertConvex(STriMeta meta, Vector *vertex, size_t cnt, SMesh *ins SBsp3 *SBsp3::InsertOrCreate(SBsp3 *where, STriangle *tr, SMesh *instead) { if(where == NULL) { if(instead) { + // ruevs: I do not think this code is reachable, but in + // principle should we use instead->keepInsideOtherShell + // in place of instead->flipNormal ? if(instead->flipNormal) { instead->atLeastOneDiscarded = true; } else { diff --git a/src/clipboard.cpp b/src/clipboard.cpp index 011b658d4..8b16481c7 100644 --- a/src/clipboard.cpp +++ b/src/clipboard.cpp @@ -138,18 +138,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); } } @@ -227,7 +226,6 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) { MakeSelected(hr.entity(j)); } } - Constraint *cc; for(cc = SS.clipboard.c.First(); cc; cc = SS.clipboard.c.NextAfter(cc)) { Constraint c = {}; @@ -246,25 +244,68 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) { c.reference = cc->reference; c.disp = cc->disp; c.comment = cc->comment; + bool dontAddConstraint = false; switch(c.type) { case Constraint::Type::COMMENT: c.disp.offset = c.disp.offset.Plus(trans); break; - - case Constraint::Type::PT_PT_DISTANCE: case Constraint::Type::PT_LINE_DISTANCE: + 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: { + 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; + } + break; + } + case Constraint::Type::CUBIC_LINE_TANGENT: { + // Nothing to do for CUBIC unlike the ARC_OF_CIRCLE above and below. + break; + } + case Constraint::Type::CURVE_CURVE_TANGENT: { + 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 constraints + if (EXACT(fmod(theta + (PI/2), PI) == 0)) { + if(c.type == Constraint::Type::HORIZONTAL) { + c.type = Constraint::Type::VERTICAL; + } else { + c.type = Constraint::Type::HORIZONTAL; + } + } else if (fmod(theta, PI/2) != 0) { + dontAddConstraint = true; + } + break; default: break; } - - hConstraint hc = Constraint::AddConstraint(&c, /*rememberForUndo=*/false); - if(c.type == Constraint::Type::COMMENT) { - MakeSelected(hc); + if (!dontAddConstraint) { + hConstraint hc = Constraint::AddConstraint(&c, /*rememberForUndo=*/false); + if(c.type == Constraint::Type::COMMENT) { + MakeSelected(hc); + } } } } diff --git a/src/config.h.in b/src/config.h.in index 475b973d2..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" @@ -12,10 +13,6 @@ /* What OpenGL version do we use? */ #define HAVE_OPENGL @OPENGL@ -/* Do we have backtrace() function, and if yes, in which library? */ -#cmakedefine HAVE_BACKTRACE -#define BACKTRACE_HEADER @BACKTRACE_HEADER@ - /* If we use GTK, can we use the native file chooser? */ #cmakedefine HAVE_GTK_FILECHOOSERNATIVE diff --git a/src/confscreen.cpp b/src/confscreen.cpp index 646f91e01..329465463 100644 --- a/src/confscreen.cpp +++ b/src/confscreen.cpp @@ -5,18 +5,9 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" - -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; -} +#if defined(_OPENMP) +#include +#endif void TextWindow::ScreenChangeColor(int link, uint32_t v) { SS.TW.ShowEditControlWithColorPicker(13, SS.modelColor[v]); @@ -49,13 +40,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; } @@ -80,14 +66,27 @@ 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; } +void TextWindow::ScreenChangeExportBackgroundColor(int link, uint32_t v) { + SS.exportBackgroundColor = !SS.exportBackgroundColor; +} + void TextWindow::ScreenChangeBackFaces(int link, uint32_t v) { SS.drawBackFaces = !SS.drawBackFaces; SS.GW.Invalidate(/*clearPersistent=*/true); @@ -102,6 +101,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); @@ -158,7 +161,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; } @@ -168,7 +171,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': @@ -178,12 +186,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); @@ -194,6 +202,16 @@ void TextWindow::ScreenChangeAutosaveInterval(int link, uint32_t v) { SS.TW.edit.meaning = Edit::AUTOSAVE_INTERVAL; } +void TextWindow::ScreenChangeFindConstraintTimeout(int link, uint32_t v) { + SS.TW.ShowEditControl(3, std::to_string(SS.timeoutRedundantConstr)); + 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)"); @@ -209,16 +227,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, ""); Printf(false, "%Ft chord tolerance (in percents)%E"); Printf(false, "%Ba %@ %% %Fl%Ll%f%D[change]%E; %@ mm, %d triangles", @@ -240,11 +248,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(), @@ -289,6 +292,9 @@ void TextWindow::ShowConfiguration() { Printf(false, " %Fd%f%Ll%s fix white exported lines%E", &ScreenChangeFixExportColors, SS.fixExportColors ? CHECK_TRUE : CHECK_FALSE); + Printf(false, " %Fd%f%Ll%s export background color%E", + &ScreenChangeExportBackgroundColor, + SS.exportBackgroundColor ? CHECK_TRUE : CHECK_FALSE); Printf(false, ""); Printf(false, "%Ft export canvas size: " @@ -345,15 +351,30 @@ 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", SS.autosaveInterval, &ScreenChangeAutosaveInterval); + Printf(false, ""); + 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; @@ -363,6 +384,11 @@ void TextWindow::ShowConfiguration() { Printf(false, " %Ft renderer %E%s", gl_renderer); Printf(false, " %Ft version %E%s", gl_version); } + + #if defined(_OPENMP) + Printf(false, " %FtOpenMP enabled"); + Printf(false, " %Ft threads %E%d", omp_get_max_threads()); + #endif } bool TextWindow::EditControlDoneForConfiguration(const std::string &s) { @@ -371,7 +397,10 @@ bool TextWindow::EditControlDoneForConfiguration(const std::string &s) { SS.lightIntensity[edit.i] = min(1.0, max(0.0, atof(s.c_str()))); SS.GW.Invalidate(); break; - + case Edit::LIGHT_AMBIENT: + SS.ambientIntensity = min(1.0, max(0.0, atof(s.c_str()))); + SS.GW.Invalidate(); + break; case Edit::LIGHT_DIRECTION: { double x, y, z; if(sscanf(s.c_str(), "%lf, %lf, %lf", &x, &y, &z)==3) { @@ -424,6 +453,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) { @@ -448,7 +482,7 @@ bool TextWindow::EditControlDoneForConfiguration(const std::string &s) { Expr *e = Expr::From(s, /*popUpError=*/true); if(e) { double ev = e->Eval(); - if(fabs(ev) < 0.001 || isnan(ev)) { + if(fabs(ev) < 0.001 || IsReasonable(ev)) { Error(_("Export scale must not be zero!")); } else { SS.exportScale = (float)ev; @@ -460,7 +494,7 @@ bool TextWindow::EditControlDoneForConfiguration(const std::string &s) { Expr *e = Expr::From(s, /*popUpError=*/true); if(e) { double ev = SS.ExprToMm(e); - if(isnan(ev) || ev < 0) { + if(IsReasonable(ev) || ev < 0) { Error(_("Cutter radius offset must not be negative!")); } else { SS.exportOffset = (float)ev; @@ -492,6 +526,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()); @@ -522,6 +561,26 @@ bool TextWindow::EditControlDoneForConfiguration(const std::string &s) { } break; } + case Edit::FIND_CONSTRAINT_TIMEOUT: { + int timeout = atoi(s.c_str()); + if(timeout) { + if(timeout >= 1) { + SS.timeoutRedundantConstr = timeout; + } else { + SS.timeoutRedundantConstr = 1000; + } + } + 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; } diff --git a/src/constraint.cpp b/src/constraint.cpp index 8ab76f278..8e0acb99d 100644 --- a/src/constraint.cpp +++ b/src/constraint.cpp @@ -22,7 +22,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 +131,129 @@ 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, + 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(), + a2 = SK.GetEntity(arc->point[2])->PointGetNum(); + if(l0.Equals(a1) || l1.Equals(a1)) { + c->other = false; + } else if(l0.Equals(a2) || l1.Equals(a2)) { + c->other = true; + } else 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.\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, + Entity *curveendpoint) { + Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), + l1 = SK.GetEntity(line->point[1])->PointGetNum(); + Vector as = cubic->CubicGetStartNum(), + af = cubic->CubicGetFinishNum(); + + if(l0.Equals(as) || l1.Equals(as)) { + c->other = false; + } else if(l0.Equals(af) || l1.Equals(af)) { + c->other = true; + } else 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 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.")); + return false; + } + return true; +} + +bool Constraint::ConstrainCurveCurveTangent(Constraint *c, Entity *eA, Entity *eB, Entity *epA, + Entity *epB) { + Vector as = eA->EndpointStart(), + af = eA->EndpointFinish(), + bs = eB->EndpointStart(), + bf = eB->EndpointFinish(); + if(as.Equals(bs)) { + c->other = false; + c->other2 = false; + } else if(as.Equals(bf)) { + c->other = false; + c->other2 = true; + } else if(af.Equals(bs)) { + c->other = true; + c->other2 = false; + } else if(af.Equals(bf)) { + c->other = true; + c->other2 = true; + } else 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.\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(); @@ -167,6 +293,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" @@ -196,57 +328,69 @@ void Constraint::MenuConstrain(Command id) { c.valA = 0; c.ModifyToSatisfy(); AddConstraint(&c); - if (SS.immediatelyEditDimension) { - SS.GW.EditConstraint(c.h); - } + 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. @@ -255,27 +399,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) { @@ -285,38 +422,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: @@ -324,16 +461,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: @@ -341,16 +496,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: @@ -360,13 +533,19 @@ void Constraint::MenuConstrain(Command id) { c.ptA = gs.point[0]; // If a point is at-midpoint, then no reason to also constrain - // it on-line; so auto-remove that. + // it on-line; so auto-remove that. Handle as one undo group. + 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" @@ -376,7 +555,7 @@ void Constraint::MenuConstrain(Command id) { "(line's midpoint on plane)\n")); return; } - AddConstraint(&c); + break; case Command::SYMMETRIC: @@ -436,6 +615,7 @@ void Constraint::MenuConstrain(Command id) { "(symmetric about workplane)\n")); return; } + // We may remove constraints so remember manually if(c.entityA == Entity::NO_ENTITY) { // Horizontal / vertical symmetry, implicit symmetry plane // normal to the workplane @@ -457,44 +637,54 @@ void Constraint::MenuConstrain(Command id) { if(gs.lineSegments == 1) { // If this line segment is already constrained horiz or // vert, then auto-remove that redundant constraint. + // Handle as one undo group. + SS.UndoRemember(); DeleteAllConstraintsFor(Type::HORIZONTAL, (gs.entity[0]), Entity::NO_ENTITY); 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; } @@ -561,6 +752,7 @@ void Constraint::MenuConstrain(Command id) { if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->HasLabel() && c->type != Type::COMMENT) { + SS.UndoRemember(); (c->reference) = !(c->reference); SS.MarkGroupDirty(c->group, /*onlyThis=*/true); break; @@ -571,7 +763,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]; @@ -579,9 +783,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; } @@ -610,120 +820,107 @@ void Constraint::MenuConstrain(Command id) { c.ModifyToSatisfy(); AddConstraint(&c); - if (SS.immediatelyEditDimension) { - SS.GW.EditConstraint(c.h); - } + 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) { - Entity *line = SK.GetEntity(gs.entity[0]); - Entity *arc = SK.GetEntity(gs.entity[1]); + 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); } - Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), - l1 = SK.GetEntity(line->point[1])->PointGetNum(); - Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(), - a2 = SK.GetEntity(arc->point[2])->PointGetNum(); - - if(l0.Equals(a1) || l1.Equals(a1)) { - c.other = false; - } else if(l0.Equals(a2) || l1.Equals(a2)) { - c.other = true; - } else { - Error(_("The tangent arc and line segment must share an " - "endpoint. Constrain them with Constrain -> " - "On Point before constraining tangent.")); + 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) { - Entity *line = SK.GetEntity(gs.entity[0]); - Entity *cubic = SK.GetEntity(gs.entity[1]); + 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); } - Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), - l1 = SK.GetEntity(line->point[1])->PointGetNum(); - Vector as = cubic->CubicGetStartNum(), - af = cubic->CubicGetFinishNum(); - - if(l0.Equals(as) || l1.Equals(as)) { - c.other = false; - } else if(l0.Equals(af) || l1.Equals(af)) { - c.other = true; - } else { - Error(_("The tangent cubic and line segment must share an " - "endpoint. Constrain them with Constrain -> " - "On Point before constraining tangent.")); + 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]); - Vector as = eA->EndpointStart(), - af = eA->EndpointFinish(), - bs = eB->EndpointStart(), - bf = eB->EndpointFinish(); - if(as.Equals(bs)) { - c.other = false; c.other2 = false; - } else if(as.Equals(bf)) { - c.other = false; c.other2 = true; - } else if(af.Equals(bs)) { - c.other = true; c.other2 = false; - } else if(af.Equals(bf)) { - c.other = true; c.other2 = true; - } else { - Error(_("The curves must share an endpoint. Constrain them " - "with Constrain -> On Point before constraining " - "tangent.")); + 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: @@ -737,35 +934,53 @@ 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 (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; + } + } - 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; + 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(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((id == Command::DISTANCE_DIA || id == Command::ANGLE || + id == Command::RATIO || id == Command::DIFFERENCE) && + SS.immediatelyEditDimension) { + SS.GW.EditConstraint(nc.h); } } diff --git a/src/constrainteq.cpp b/src/constrainteq.cpp index 985545c7d..c21d10721 100644 --- a/src/constrainteq.cpp +++ b/src/constrainteq.cpp @@ -18,7 +18,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; @@ -28,6 +32,54 @@ bool ConstraintBase::HasLabel() const { } } +bool ConstraintBase::IsProjectible() const { + switch(type) { + case Type::POINTS_COINCIDENT: + case Type::PT_PT_DISTANCE: + case Type::PT_LINE_DISTANCE: + case Type::PT_ON_LINE: + case Type::EQUAL_LENGTH_LINES: + case Type::EQ_LEN_PT_LINE_D: + 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: + case Type::SYMMETRIC_LINE: + case Type::AT_MIDPOINT: + case Type::HORIZONTAL: + case Type::VERTICAL: + case Type::ANGLE: + case Type::PARALLEL: + case Type::PERPENDICULAR: + case Type::WHERE_DRAGGED: + case Type::COMMENT: + return true; + + case Type::PT_PLANE_DISTANCE: + case Type::PT_FACE_DISTANCE: + case Type::PROJ_PT_DISTANCE: + case Type::PT_IN_PLANE: + case Type::PT_ON_FACE: + case Type::EQUAL_LINE_ARC_LEN: + case Type::DIAMETER: + case Type::PT_ON_CIRCLE: + case Type::SAME_ORIENTATION: + case Type::CUBIC_LINE_TANGENT: + case Type::CURVE_CURVE_TANGENT: + case Type::ARC_LINE_TANGENT: + case Type::EQUAL_RADIUS: + return false; + } + ssassert(false, "Impossible"); +} + ExprVector ConstraintBase::VectorsParallel3d(ExprVector a, ExprVector b, hParam p) { return a.Minus(b.ScaledBy(Expr::From(p))); } @@ -290,6 +342,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); @@ -299,7 +455,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(); @@ -467,7 +727,7 @@ void ConstraintBase::GenerateEquations(IdList *l, } } return; - + case Type::SYMMETRIC: if(workplane == EntityBase::FREE_IN_3D) { EntityBase *plane = SK.GetEntity(entityA); diff --git a/src/describescreen.cpp b/src/describescreen.cpp index 4ebe20e56..06590349a 100644 --- a/src/describescreen.cpp +++ b/src/describescreen.cpp @@ -19,6 +19,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; @@ -40,6 +51,17 @@ void TextWindow::ScreenSetTtfFont(int link, uint32_t v) { SS.ScheduleShowTW(); } +void TextWindow::ScreenConstraintToggleReference(int link, uint32_t v) { + hConstraint hc = { v }; + Constraint *c = SK.GetConstraint(hc); + + SS.UndoRemember(); + c->reference = !c->reference; + + SS.MarkGroupDirty(c->group); + SS.ScheduleShowTW(); +} + void TextWindow::ScreenConstraintShowAsRadius(int link, uint32_t v) { hConstraint hc = { v }; Constraint *c = SK.GetConstraint(hc); @@ -53,17 +75,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: @@ -71,8 +112,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: @@ -93,20 +135,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; @@ -126,18 +168,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()); @@ -149,10 +191,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: @@ -160,19 +203,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. @@ -211,60 +259,105 @@ void TextWindow::DescribeSelection() { break; } - Group *g = SK.GetGroup(e->group); Printf(false, ""); - Printf(false, "%FtIN GROUP%E %s", g->DescriptionString().c_str()); + if(e->h.isFromRequest()) { + Request *r = SK.GetRequest(e->h.request()); + if(e->h == r->h.entity(0)) { + Printf(false, "%FtFROM REQUEST%E %s", + r->DescriptionString().c_str()); + } else { + Printf(false, "%FtFROM REQUEST%E %Fl%Ll%D%f%h%s%E", + r->h.v, (&TextWindow::ScreenSelectRequest), &(TextWindow::ScreenHoverRequest), + r->DescriptionString().c_str()); + } + } + Group *g = SK.GetGroup(e->group); + Printf(false, "%FtIN GROUP%E %Fl%Ll%D%f%s%E", + g->h.v, (&TextWindow::ScreenSelectGroup), + g->DescriptionString().c_str()); if(e->workplane == Entity::FREE_IN_3D) { Printf(false, "%FtNOT LOCKED IN WORKPLANE%E"); } else { Entity *w = SK.GetEntity(e->workplane); - Printf(false, "%FtIN WORKPLANE%E %s", w->DescriptionString().c_str()); + if(w->h.isFromRequest()) { + Printf(false, "%FtIN WORKPLANE%E %Fl%Ll%D%f%h%s%E", + w->h.request().v, + (&TextWindow::ScreenSelectRequest), &(TextWindow::ScreenHoverRequest), + w->DescriptionString().c_str()); + } else { + Printf(false, "%FtIN WORKPLANE%E %Fl%Ll%D%f%h%s%E", + w->h.group().v, + (&TextWindow::ScreenSelectGroup), (&TextWindow::ScreenHoverGroupWorkplane), + w->DescriptionString().c_str()); + } } - if(e->style.v) { - Style *s = Style::Get(e->style); - Printf(false, "%FtIN STYLE%E %s", s->DescriptionString().c_str()); - } else { - Printf(false, "%FtIN STYLE%E none"); + if(e->IsStylable()) { + if(e->style.v) { + Style *s = Style::Get(e->style); + Printf(false, "%FtIN STYLE%E %Fl%Ll%D%f%s%E", + s->h.v, (&TextWindow::ScreenShowStyleInfo), + s->DescriptionString().c_str()); + } else { + Printf(false, "%FtIN STYLE%E none"); + } } if(e->construction) { Printf(false, "%FtCONSTRUCTION"); } std::vector lhc = {}; - for(const Constraint &c : SK.constraint) { - if(!(c.ptA == e->h || - c.ptB == e->h || - c.entityA == e->h || - c.entityB == e->h || - c.entityC == e->h || - c.entityD == e->h)) - continue; - lhc.push_back(c.h); + auto FindConstraints = [&](hEntity he) { + for(const Constraint &c : SK.constraint) { + if(!(c.ptA == he || c.ptB == he || + c.entityA == he || c.entityB == he || c.entityC == he || c.entityD == he)) + continue; + lhc.push_back(c.h); + } + }; + FindConstraints(e->h); + if(!e->IsPoint()) { + for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) { + if(e->point[i].v == 0) break; + FindConstraints(e->point[i]); + } } - if(!lhc.empty()) { - Printf(true, "%FtCONSTRAINED BY:%E"); + std::sort(lhc.begin(), lhc.end()); + lhc.erase(std::unique(lhc.begin(), lhc.end()), lhc.end()); + auto ListConstraints = [&](bool reference) { + bool first = true; int a = 0; for(hConstraint hc : lhc) { Constraint *c = SK.GetConstraint(hc); - std::string s = c->DescriptionString(); - Printf(false, "%Bp %Fl%Ll%D%f%h%s%E %s", + if(c->reference != reference) continue; + if(first) { + first = false; + if(reference) { + Printf(true, "%FtMEASURED BY:%E"); + } else { + Printf(true, "%FtCONSTRAINED BY:%E"); + } + } + Printf(false, "%Bp %Fl%Ll%D%f%h%s%E", (a & 1) ? 'd' : 'a', c->h.v, (&TextWindow::ScreenSelectConstraint), - (&TextWindow::ScreenHoverConstraint), s.c_str(), - c->reference ? "(ref)" : ""); + (&TextWindow::ScreenHoverConstraint), + c->DescriptionString().c_str()); a++; } - } + }; + ListConstraints(/*reference=*/false); + ListConstraints(/*reference=*/true); } 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)); - double d = (p1.Minus(p0)).Magnitude(); - Printf(true, " d = %Fi%s", SS.MmToString(d).c_str()); + 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_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) { @@ -273,9 +366,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()); @@ -284,22 +377,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) { @@ -307,11 +400,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(); @@ -330,8 +423,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); @@ -341,15 +434,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); @@ -358,23 +446,67 @@ 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 == 0 && gs.constraints == 1) { - Constraint *c = SK.GetConstraint(gs.constraint[0]); + } else if(gs.n == 3 && gs.faces == 3) { + Printf(false, "%FtTHREE PLANE FACES"); - if(c->type == Constraint::Type::DIAMETER) { - Printf(false, "%FtDIAMETER CONSTRAINT"); + ListFaces(); - Printf(true, " %Fd%f%D%Ll%s show as radius", - &ScreenConstraintShowAsRadius, gs.constraint[0].v, - c->other ? CHECK_TRUE : CHECK_FALSE); + // 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()); + } else { + Printf(false, "%FtDIMENSION%E %s", desc.c_str()); + } + Printf(true, " %Fd%f%D%Ll%s reference", + &ScreenConstraintToggleReference, gs.constraint[0].v, + c->reference ? CHECK_TRUE : CHECK_FALSE); + if(c->type == Constraint::Type::DIAMETER) { + Printf(false, " %Fd%f%D%Ll%s use radius", + &ScreenConstraintShowAsRadius, gs.constraint[0].v, + c->other ? CHECK_TRUE : CHECK_FALSE); + } } else { - Printf(false, "%FtSELECTED:%E %s", - c->DescriptionString().c_str()); + Printf(false, "%FtCONSTRAINT%E %s", desc.c_str()); + } + + if(c->IsProjectible()) { + if(c->workplane == Entity::FREE_IN_3D) { + Printf(true, "%FtNOT PROJECTED TO WORKPLANE%E"); + } else { + Entity *w = SK.GetEntity(c->workplane); + if(w->h.isFromRequest()) { + Printf(true, "%FtIN WORKPLANE%E %Fl%Ll%D%f%h%s%E", + w->h.request().v, + (&TextWindow::ScreenSelectRequest), &(TextWindow::ScreenHoverRequest), + w->DescriptionString().c_str()); + } else { + Printf(true, "%FtIN WORKPLANE%E %Fl%Ll%D%f%h%s%E", + w->h.group().v, + (&TextWindow::ScreenSelectGroup), (&TextWindow::ScreenHoverGroupWorkplane), + w->DescriptionString().c_str()); + } + } } std::vector lhe = {}; @@ -391,16 +523,20 @@ void TextWindow::DescribeSelection() { lhe.erase(it, lhe.end()); if(!lhe.empty()) { - Printf(true, "%FtCONSTRAINS:%E"); + if(c->reference) { + Printf(true, "%FtMEASURES:%E"); + } else { + Printf(true, "%FtCONSTRAINS:%E"); + } int a = 0; for(hEntity he : lhe) { - Request *r = SK.GetRequest(he.request()); - std::string s = r->DescriptionString(); + Entity *e = SK.GetEntity(he); Printf(false, "%Bp %Fl%Ll%D%f%h%s%E", (a & 1) ? 'd' : 'a', - r->h.v, (&TextWindow::ScreenSelectRequest), - &(TextWindow::ScreenHoverRequest), s.c_str()); + e->h.v, (&TextWindow::ScreenSelectEntity), + &(TextWindow::ScreenHoverEntity), + e->DescriptionString().c_str()); a++; } } diff --git a/src/draw.cpp b/src/draw.cpp index 907ca0044..acc986e1a 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -69,9 +69,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 +185,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(); @@ -207,25 +209,39 @@ void GraphicsWindow::MakeSelected(Selection *stog) { //----------------------------------------------------------------------------- void GraphicsWindow::SelectByMarquee() { Point2d marqueePoint = ProjectPoint(orig.marqueePoint); - BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, -1), - Vector::From(orig.mouse.x, orig.mouse.y, 1)); + BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, VERY_NEGATIVE), + Vector::From(orig.mouse.x, orig.mouse.y, VERY_POSITIVE)); - Entity *e; - for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - 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. //----------------------------------------------------------------------------- @@ -305,9 +321,16 @@ void GraphicsWindow::GroupSelection() { Camera GraphicsWindow::GetCamera() const { Camera camera = {}; - window->GetContentSize(&camera.width, &camera.height); - camera.pixelRatio = window->GetDevicePixelRatio(); - camera.gridFit = (window->GetDevicePixelRatio() == 1); + if(window) { + window->GetContentSize(&camera.width, &camera.height); + camera.pixelRatio = window->GetDevicePixelRatio(); + camera.gridFit = (window->GetDevicePixelRatio() == 1); + } else { // solvespace-cli + camera.width = 297.0; // A4? Whatever... + camera.height = 210.0; + camera.pixelRatio = 1.0; + camera.gridFit = camera.pixelRatio == 1.0; + } camera.offset = offset; camera.projUp = projUp; camera.projRight = projRight; @@ -335,6 +358,8 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() { Group *activeGroup = SK.GetGroup(SS.GW.activeGroup); int bestOrder = -1; int bestZIndex = 0; + double bestDepth = VERY_POSITIVE; + for(const Hover &hov : hoverList) { hGroup hg = {}; if(hov.selection.entity.v != 0) { @@ -345,26 +370,49 @@ GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() { Group *g = SK.GetGroup(hg); if(g->order > activeGroup->order) continue; - if(bestOrder != -1 && (bestOrder >= g->order || bestZIndex > hov.zIndex)) continue; + if(bestOrder != -1 && (bestOrder > g->order || bestZIndex > hov.zIndex)) continue; + // we have hov.zIndex is >= best and hov.group is >= best (but not > active group) + if(hov.depth > bestDepth && bestOrder == g->order && bestZIndex == hov.zIndex) continue; bestOrder = g->order; bestZIndex = hov.zIndex; + bestDepth = hov.depth; sel = hov.selection; } return sel; } +// This uses the same logic as hovering and static entity selection +// but ignores points known not to be draggable GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToDrag() { Selection sel = {}; + if(hoverList.IsEmpty()) + return sel; + + Group *activeGroup = SK.GetGroup(SS.GW.activeGroup); + int bestOrder = -1; + int bestZIndex = 0; + double bestDepth = VERY_POSITIVE; + for(const Hover &hov : hoverList) { - if(hov.selection.entity.v == 0) continue; - if(!hov.selection.entity.isFromRequest()) continue; + hGroup hg = {}; + if(hov.selection.entity.v != 0) { + Entity *e = SK.GetEntity(hov.selection.entity); + if (!e->CanBeDragged()) continue; + hg = e->group; + } else if(hov.selection.constraint.v != 0) { + hg = SK.GetConstraint(hov.selection.constraint)->group; + } + + Group *g = SK.GetGroup(hg); + if(g->order > activeGroup->order) continue; + if(bestOrder != -1 && (bestOrder > g->order || bestZIndex > hov.zIndex)) continue; + // we have hov.zIndex is >= best and hov.group is >= best (but not > active group) + if(hov.depth > bestDepth && bestOrder == g->order && bestZIndex == hov.zIndex) continue; + bestOrder = g->order; + bestZIndex = hov.zIndex; sel = hov.selection; - break; - } - if(!sel.IsEmpty()) { - return sel; } - return ChooseFromHoverToSelect(); + return sel; } void GraphicsWindow::HitTestMakeSelection(Point2d mp) { @@ -380,8 +428,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; } } @@ -416,6 +464,7 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) { Hover hov = {}; hov.distance = canvas.minDistance; hov.zIndex = canvas.maxZIndex; + hov.depth = canvas.minDepth; hov.selection.entity = e.h; hoverList.Add(&hov); } @@ -769,9 +818,13 @@ void GraphicsWindow::Draw(Canvas *canvas) { const double size = 10.0; const int subdiv = 16; double h = Style::DefaultTextHeight() / camera.scale; - canvas->DrawVectorText(ssprintf("%.3f, %.3f, %.3f", p.x, p.y, p.z), h, + std::string s = + SS.MmToStringSI(p.x) + ", " + + SS.MmToStringSI(p.y) + ", " + + SS.MmToStringSI(p.z); + canvas->DrawVectorText(s.c_str(), h, p.Plus(u.ScaledBy((size + 5.0)/scale)).Minus(v.ScaledBy(h / 2.0)), - u, v,hcsDatum); + u, v, hcsDatum); u = u.WithMagnitude(size / scale); v = v.WithMagnitude(size / scale); @@ -841,17 +894,15 @@ void GraphicsWindow::Paint() { ForceTextWindowShown(); } - auto renderStartTime = std::chrono::high_resolution_clock::now(); - canvas->SetLighting(lighting); canvas->SetCamera(camera); canvas->StartFrame(); + + // Draw the 3d objects. Draw(canvas.get()); canvas->FlushFrame(); - auto renderEndTime = std::chrono::high_resolution_clock::now(); - std::chrono::duration renderTime = renderEndTime - renderStartTime; - + // Draw the 2d UI overlay. camera.LoadIdentity(); camera.offset.x = -(double)camera.width / 2.0; camera.offset.y = -(double)camera.height / 2.0; @@ -888,19 +939,6 @@ void GraphicsWindow::Paint() { ToolbarDraw(&uiCanvas); } - // Also display an fps counter. - RgbaColor renderTimeColor; - if(renderTime.count() > 16.67) { - // We aim for a steady 60fps; draw the counter in red when we're slower. - renderTimeColor = { 255, 0, 0, 255 }; - } else { - renderTimeColor = { 255, 255, 255, 255 }; - } - uiCanvas.DrawBitmapText(ssprintf("rendered in %ld ms (%ld 1/s)", - (long)renderTime.count(), - (long)(1000 / std::max(0.1, renderTime.count()))), - 5, 5, renderTimeColor); - canvas->FlushFrame(); canvas->FinishFrame(); canvas->Clear(); diff --git a/src/drawconstraint.cpp b/src/drawconstraint.cpp index 2cdb9afce..e84c0ce48 100644 --- a/src/drawconstraint.cpp +++ b/src/drawconstraint.cpp @@ -12,7 +12,7 @@ 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 +267,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 +291,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 +306,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 +449,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 +555,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 +578,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 +607,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 +633,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 +646,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 +712,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 +754,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 +772,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 +797,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 +854,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 +877,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 +912,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 +930,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 +953,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 +963,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 +977,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 +1005,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 +1029,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 +1052,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 +1068,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 +1112,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 +1132,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 +1159,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 +1193,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 +1231,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 +1256,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 +1307,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 +1361,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 +1374,7 @@ bool Constraint::HasLabel() const { return false; } } + +bool Constraint::ShouldDrawExploded() const { + return SK.GetGroup(group)->ShouldDrawExploded(); +} diff --git a/src/drawentity.cpp b/src/drawentity.cpp index de93d730b..7ac03386d 100644 --- a/src/drawentity.cpp +++ b/src/drawentity.cpp @@ -26,7 +26,7 @@ void Entity::GenerateEdges(SEdgeList *el) { List lv = {}; sb->MakePwlInto(&lv); for(int j = 1; j < lv.n; j++) { - el->AddEdge(lv[j-1], lv[j], style.v, i); + el->AddEdge(lv[j-1], lv[j], Style::ForEntity(h).v, i); } lv.Clear(); } @@ -64,13 +64,13 @@ BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) { Vector proj = SS.GW.ProjectPoint3(PointGetNum()); screenBBox = BBox::From(proj, proj); } else if(IsNormal()) { - Vector proj = SK.GetEntity(point[0])->PointGetNum(); + Vector proj = SS.GW.ProjectPoint3(SK.GetEntity(point[0])->PointGetNum()); screenBBox = BBox::From(proj, proj); } else if(!sbl->l.IsEmpty()) { Vector first = SS.GW.ProjectPoint3(sbl->l[0].ctrl[0]); screenBBox = BBox::From(first, first); for(auto &sb : sbl->l) { - for(int i = 0; i < sb.deg; ++i) { screenBBox.Include(SS.GW.ProjectPoint3(sb.ctrl[i])); } + for(int i = 0; i <= sb.deg; ++i) { screenBBox.Include(SS.GW.ProjectPoint3(sb.ctrl[i])); } } } else ssassert(false, "Expected entity to be a point or have beziers"); @@ -88,7 +88,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 +103,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; } @@ -120,6 +120,8 @@ void Entity::GetReferencePoints(std::vector *refs) { case Type::FACE_N_ROT_TRANS: case Type::FACE_N_TRANS: case Type::FACE_N_ROT_AA: + case Type::FACE_ROT_NORMAL_PT: + case Type::FACE_N_ROT_AXIS_TRANS: break; } } @@ -178,6 +180,48 @@ 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 { + 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 PtCanDrag(point[0]); + } + // if we're not certain it can't be dragged then default to true + return true; +} + void Entity::CalculateNumerical(bool forExport) { if(IsPoint()) actPoint = PointGetNum(); if(IsNormal()) actNormal = NormalGetNum(); @@ -295,9 +339,13 @@ void Entity::ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) const { } else { // The wrapping would work, except when n = 1 and everything // wraps to zero... - if(i > 0) bm.A[i][i - 1] = eq.x; - /**/ bm.A[i][i] = eq.y; - if(i < (n-1)) bm.A[i][i + 1] = eq.z; + if(i > 0) { + bm.A[i][i - 1] = eq.x; + } + bm.A[i][i] = eq.y; + if(i < (n-1)) { + bm.A[i][i + 1] = eq.z; + } } } bm.Solve(); @@ -427,7 +475,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; } @@ -438,23 +487,43 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) const { // Record our style for all of the Beziers that we just created. for(; i < sbl->l.n; i++) { - sbl->l[i].auxA = style.v; + sbl->l[i].auxA = Style::ForEntity(h).v; + } +} + +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; int zIndex; if(IsPoint()) { - zIndex = 5; + zIndex = 6; } else if(how == DrawAs::HIDDEN) { zIndex = 2; } else if(group != SS.GW.activeGroup) { zIndex = 3; } else { - zIndex = 4; + zIndex = 5; } hStyle hs; @@ -464,6 +533,9 @@ void Entity::Draw(DrawAs how, Canvas *canvas) { hs.v = Style::NORMALS; } else { hs = Style::ForEntity(h); + if (hs.v == Style::CONSTRUCTION) { + zIndex = 4; + } } Canvas::Stroke stroke = Style::Stroke(hs); @@ -530,16 +602,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; } @@ -588,13 +661,13 @@ void Entity::Draw(DrawAs how, Canvas *canvas) { double w = 60 - camera.width / 2.0; // Shift the axis to the right if they would overlap with the toolbar. if(SS.showToolbar) { - if(h + 30 > -(34*16 + 3*16 + 8) / 2) + if(h + 30 > -(32*18 + 3*16 + 8) / 2) w += 60; } tail = camera.projRight.ScaledBy(w/s).Plus( 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); @@ -682,8 +755,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); @@ -693,12 +790,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: { @@ -730,7 +829,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]); @@ -755,6 +854,8 @@ void Entity::Draw(DrawAs how, Canvas *canvas) { case Type::FACE_N_ROT_TRANS: case Type::FACE_N_TRANS: case Type::FACE_N_ROT_AA: + case Type::FACE_ROT_NORMAL_PT: + case Type::FACE_N_ROT_AXIS_TRANS: // Do nothing; these are drawn with the triangle mesh return; } diff --git a/src/dsc.h b/src/dsc.h index ce92c5963..bbb9b2c8c 100644 --- a/src/dsc.h +++ b/src/dsc.h @@ -7,9 +7,8 @@ #ifndef SOLVESPACE_DSC_H #define SOLVESPACE_DSC_H -#include "solvespace.h" - #include +#include /// Trait indicating which types are handle types and should get the associated operators. /// Specialize for each handle type and inherit from std::true_type. @@ -155,6 +154,52 @@ inline bool Vector::Equals(Vector v, double tol) const { return dv.MagSquared() < tol*tol; } +inline Vector Vector::From(double x, double y, double z) { + return {x, y, z}; +} + +inline Vector Vector::Plus(Vector b) const { + return {x + b.x, y + b.y, z + b.z}; +} + +inline Vector Vector::Minus(Vector b) const { + return {x - b.x, y - b.y, z - b.z}; +} + +inline Vector Vector::Negated() const { + return {-x, -y, -z}; +} + +inline Vector Vector::Cross(Vector b) const { + return {-(z * b.y) + (y * b.z), (z * b.x) - (x * b.z), -(y * b.x) + (x * b.y)}; +} + +inline double Vector::Dot(Vector b) const { + return (x * b.x + y * b.y + z * b.z); +} + +inline double Vector::MagSquared() const { + return x * x + y * y + z * z; +} + +inline double Vector::Magnitude() const { + return sqrt(x * x + y * y + z * z); +} + +inline Vector Vector::ScaledBy(const double v) const { + return {x * v, y * v, z * v}; +} + +inline void Vector::MakeMaxMin(Vector *maxv, Vector *minv) const { + maxv->x = max(maxv->x, x); + maxv->y = max(maxv->y, y); + maxv->z = max(maxv->z, z); + + minv->x = min(minv->x, x); + minv->y = min(minv->y, y); + minv->z = min(minv->z, z); +} + struct VectorHash { size_t operator()(const Vector &v) const; }; @@ -215,12 +260,12 @@ class List { void ReserveMore(int howMuch) { if(n + howMuch > elemsAllocated) { elemsAllocated = n + howMuch; - T *newElem = (T *)MemAlloc((size_t)elemsAllocated*sizeof(T)); + 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(); } - MemFree(elem); + ::operator delete[](elem); elem = newElem; } } @@ -286,7 +331,7 @@ class List { void Clear() { for(int i = 0; i < n; i++) elem[i].~T(); - if(elem) MemFree(elem); + if(elem) ::operator delete[](elem); elem = NULL; n = elemsAllocated = 0; } @@ -325,15 +370,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 @@ -341,144 +399,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); - Add(t); - - return t->h; - } - T * LowerBound(T const& t) { - if(IsEmpty()) { - return nullptr; - } - auto it = std::lower_bound(begin(), end(), t, Compare()); - return it; - } + // Add at the end of the list. + elemstore.push_back(*t); + elemidx.push_back(elemstore.size()-1); + ++n; - T * LowerBound(H const& h) { - if(IsEmpty()) { - return nullptr; - } - auto it = std::lower_bound(begin(), end(), h, Compare()); - return it; + return t->h; } - 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 *)MemAlloc((size_t)elemsAllocated*sizeof(T)); - for(int i = 0; i < n; i++) { - new(&newElem[i]) T(std::move(elem[i])); - elem[i].~T(); - } - MemFree(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"); - // Copy-construct at the end of the list. - new(&elem[n]) T(*t); + // Find out where the added element should be. + 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? + } + ++n; - // The item we just added is trivially sorted, so "merge" - std::inplace_merge(begin(), end() - 1, end(), Compare()); } 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; } @@ -495,22 +561,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(); @@ -518,28 +585,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 *)MemAlloc(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) MemFree(elem); - elem = NULL; - elemsAllocated = n = 0; + freelist.clear(); + elemidx.clear(); + elemstore.clear(); + n = 0; } }; diff --git a/src/entity.cpp b/src/entity.cpp index 38800ab68..dd42cba65 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -26,6 +26,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 +65,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 +85,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( @@ -701,6 +710,8 @@ bool EntityBase::IsFace() const { case Type::FACE_N_ROT_TRANS: case Type::FACE_N_TRANS: case Type::FACE_N_ROT_AA: + case Type::FACE_ROT_NORMAL_PT: + case Type::FACE_N_ROT_AXIS_TRANS: return true; default: return false; @@ -728,7 +739,7 @@ ExprVector EntityBase::FaceGetNormalExprs() const { r = q.Rotate(r); } else if(type == Type::FACE_N_TRANS) { r = ExprVector::From(numNormal.vx, numNormal.vy, numNormal.vz); - } else if(type == Type::FACE_N_ROT_AA) { + } else if((type == Type::FACE_N_ROT_AA) || (type == Type::FACE_ROT_NORMAL_PT)) { r = ExprVector::From(numNormal.vx, numNormal.vy, numNormal.vz); ExprQuaternion q = GetAxisAngleQuaternionExprs(3); r = q.Rotate(r); @@ -751,7 +762,7 @@ Vector EntityBase::FaceGetNormalNum() const { r = q.Rotate(r); } else if(type == Type::FACE_N_TRANS) { r = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz); - } else if(type == Type::FACE_N_ROT_AA) { + } else if((type == Type::FACE_N_ROT_AA) || (type == Type::FACE_ROT_NORMAL_PT)) { r = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz); Quaternion q = GetAxisAngleQuaternion(3); r = q.Rotate(r); @@ -761,7 +772,7 @@ Vector EntityBase::FaceGetNormalNum() const { ExprVector EntityBase::FaceGetPointExprs() const { ExprVector r; - if(type == Type::FACE_NORMAL_PT) { + if((type == Type::FACE_NORMAL_PT) || (type==Type::FACE_ROT_NORMAL_PT)) { r = SK.GetEntity(point[0])->PointGetExprs(); } else if(type == Type::FACE_XPROD) { r = ExprVector::From(numPoint); @@ -773,6 +784,15 @@ ExprVector EntityBase::FaceGetPointExprs() const { r = ExprVector::From(numPoint); r = q.Rotate(r); r = r.Plus(trans); + } else if(type == Type::FACE_N_ROT_AXIS_TRANS) { + ExprVector orig = ExprVector::From(numPoint); + ExprVector trans = ExprVector::From(param[0], param[1], param[2]); + ExprVector displace = ExprVector::From(param[4], param[5], param[6]) + .WithMagnitude(Expr::From(param[7])).ScaledBy(Expr::From(timesApplied)); + ExprQuaternion q = GetAxisAngleQuaternionExprs(3); + orig = orig.Minus(trans); + orig = q.Rotate(orig); + r = orig.Plus(trans).Plus(displace); } else if(type == Type::FACE_N_TRANS) { ExprVector trans = ExprVector::From(param[0], param[1], param[2]); r = ExprVector::From(numPoint); @@ -790,7 +810,7 @@ ExprVector EntityBase::FaceGetPointExprs() const { Vector EntityBase::FaceGetPointNum() const { Vector r; - if(type == Type::FACE_NORMAL_PT) { + if((type == Type::FACE_NORMAL_PT) || (type==Type::FACE_ROT_NORMAL_PT)) { r = SK.GetEntity(point[0])->PointGetNum(); } else if(type == Type::FACE_XPROD) { r = numPoint; @@ -800,6 +820,14 @@ Vector EntityBase::FaceGetPointNum() const { Quaternion q = Quaternion::From(param[3], param[4], param[5], param[6]); r = q.Rotate(numPoint); r = r.Plus(trans); + } else if(type == Type::FACE_N_ROT_AXIS_TRANS) { + Vector offset = Vector::From(param[0], param[1], param[2]); + Vector displace = Vector::From(param[4], param[5], param[6]) + .WithMagnitude(SK.GetParam(param[7])->val).ScaledBy(timesApplied); + Quaternion q = PointGetQuaternion(); + r = numPoint.Minus(offset); + r = q.Rotate(r); + r = r.Plus(offset).Plus(displace); } else if(type == Type::FACE_N_TRANS) { Vector trans = Vector::From(param[0], param[1], param[2]); r = numPoint.Plus(trans.ScaledBy(timesApplied)); diff --git a/src/export.cpp b/src/export.cpp index 621fa2e62..cfa53fcd2 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -207,7 +207,6 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const Platform::Path &filename, bool for(auto &entity : SK.entity) { Entity *e = &entity; if(!e->IsVisible()) continue; - if(e->construction) continue; if(SS.exportPwlCurves || sm || fabs(SS.exportOffset) > LENGTH_EPS) { @@ -228,7 +227,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(); @@ -367,9 +366,9 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s // And calculate lighting for the triangle Vector n = tt.Normal().WithMagnitude(1); - double lighting = SS.ambientIntensity + + double lighting = min(1.0, SS.ambientIntensity + max(0.0, (SS.lightIntensity[0])*(n.Dot(l0))) + - max(0.0, (SS.lightIntensity[1])*(n.Dot(l1))); + max(0.0, (SS.lightIntensity[1])*(n.Dot(l1)))); double r = min(1.0, tt.meta.color.redF() * lighting), g = min(1.0, tt.meta.color.greenF() * lighting), b = min(1.0, tt.meta.color.blueF() * lighting); @@ -717,13 +716,16 @@ void VectorFileWriter::OutputLinesAndMesh(SBezierLoopSetSet *sblss, SMesh *sm) { ptMin.y -= s*SS.exportMargin.bottom; ptMax.y += s*SS.exportMargin.top; } else { - ptMin.x = -(s*SS.exportCanvas.dx); - ptMin.y = -(s*SS.exportCanvas.dy); + ptMin.x = (s*SS.exportCanvas.dx); + ptMin.y = (s*SS.exportCanvas.dy); ptMax.x = ptMin.x + (s*SS.exportCanvas.width); ptMax.y = ptMin.y + (s*SS.exportCanvas.height); } StartFile(); + if(SS.exportBackgroundColor) { + Background(SS.backgroundColor); + } if(sm && SS.exportShadedTriangles) { for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { Triangle(tr); @@ -732,25 +734,22 @@ void VectorFileWriter::OutputLinesAndMesh(SBezierLoopSetSet *sblss, SMesh *sm) { if(sblss) { SBezierLoopSet *sbls; for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { - SBezierLoop *sbl; - sbl = sbls->l.First(); - if(!sbl) continue; - b = sbl->l.First(); - if(!b || !Style::Exportable(b->auxA)) continue; - - hStyle hs = { (uint32_t)b->auxA }; - Style *stl = Style::Get(hs); - double lineWidth = Style::WidthMm(b->auxA)*s; - RgbaColor strokeRgb = Style::Color(hs, /*forExport=*/true); - RgbaColor fillRgb = Style::FillColor(hs, /*forExport=*/true); - - StartPath(strokeRgb, lineWidth, stl->filled, fillRgb, hs); - for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { + for(SBezierLoop *sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { + b = sbl->l.First(); + if(!b || !Style::Exportable(b->auxA)) continue; + + hStyle hs = { (uint32_t)b->auxA }; + Style *stl = Style::Get(hs); + double lineWidth = Style::WidthMm(b->auxA)*s; + RgbaColor strokeRgb = Style::Color(hs, /*forExport=*/true); + RgbaColor fillRgb = Style::FillColor(hs, /*forExport=*/true); + + StartPath(strokeRgb, lineWidth, stl->filled, fillRgb, hs); for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { Bezier(b); } + FinishPath(strokeRgb, lineWidth, stl->filled, fillRgb, hs); } - FinishPath(strokeRgb, lineWidth, stl->filled, fillRgb, hs); } } FinishAndCloseFile(); @@ -840,8 +839,6 @@ void SolveSpaceUI::ExportMeshTo(const Platform::Path &filename) { ExportMeshAsObjTo(f, fMtl, m); fclose(fMtl); - } else if(filename.HasExtension("q3do")) { - ExportMeshAsQ3doTo(f, m); } else if(filename.HasExtension("js") || filename.HasExtension("html")) { SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines); @@ -895,54 +892,6 @@ void SolveSpaceUI::ExportMeshAsStlTo(FILE *f, SMesh *sm) { } } -//----------------------------------------------------------------------------- -// Export the mesh as a Q3DO (https://github.com/q3k/q3d) file. -//----------------------------------------------------------------------------- - -#include "q3d_object_generated.h" -void SolveSpaceUI::ExportMeshAsQ3doTo(FILE *f, SMesh *sm) { - flatbuffers::FlatBufferBuilder builder(1024); - double s = SS.exportScale; - - // Create a material for every colour used, keep note of triangles belonging to color/material. - std::map, RgbaColorCompare> materials; - std::map>, RgbaColorCompare> materialTriangles; - for (const STriangle &t : sm->l) { - auto color = t.meta.color; - if (materials.find(color) == materials.end()) { - auto name = builder.CreateString(ssprintf("Color #%02x%02x%02x%02x", color.red, color.green, color.blue, color.alpha)); - auto co = q3d::CreateColor(builder, color.red, color.green, color.blue, color.alpha); - auto mo = q3d::CreateMaterial(builder, name, co); - materials.emplace(color, mo); - } - - Vector faceNormal = t.Normal(); - auto a = q3d::Vector3(t.a.x/s, t.a.y/s, t.a.z/s); - auto b = q3d::Vector3(t.b.x/s, t.b.y/s, t.b.z/s); - auto c = q3d::Vector3(t.c.x/s, t.c.y/s, t.c.z/s); - auto fn = q3d::Vector3(faceNormal.x, faceNormal.y, faceNormal.x); - auto n1 = q3d::Vector3(t.normals[0].x, t.normals[0].y, t.normals[0].z); - auto n2 = q3d::Vector3(t.normals[1].x, t.normals[1].y, t.normals[1].z); - auto n3 = q3d::Vector3(t.normals[2].x, t.normals[2].y, t.normals[2].z); - auto tri = q3d::CreateTriangle(builder, &a, &b, &c, &fn, &n1, &n2, &n3); - materialTriangles[color].push_back(tri); - } - - // Build all meshes sorted by material. - std::vector> meshes; - for (auto &it : materials) { - auto &mato = it.second; - auto to = builder.CreateVector(materialTriangles[it.first]); - auto mo = q3d::CreateMesh(builder, to, mato); - meshes.push_back(mo); - } - - auto mo = builder.CreateVector(meshes); - auto o = q3d::CreateObject(builder, mo); - q3d::FinishObjectBuffer(builder, o); - fwrite(builder.GetBufferPointer(), builder.GetSize(), 1, f); -} - //----------------------------------------------------------------------------- // Export the mesh as Wavefront OBJ format. This requires us to reduce all the // identical vertices to the same identifier, so do that first. @@ -1003,15 +952,20 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename SPointList spl = {}; STriangle *tr; Vector bndl, bndh; + + const std::string THREE_FN("three-r111.min.js"); + const std::string HAMMER_FN("hammer-2.0.8.js"); + const std::string CONTROLS_FN("SolveSpaceControls.js"); + const char htmlbegin[] = R"( Three.js Solvespace Mesh - - - + + + @@ -1061,9 +1015,12 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename if(filename.HasExtension("html")) { fprintf(f, htmlbegin, - LoadStringFromGzip("threejs/three-r76.js.gz").c_str(), - LoadStringFromGzip("threejs/hammer-2.0.8.js.gz").c_str(), - LoadString("threejs/SolveSpaceControls.js").c_str()); + THREE_FN.c_str(), + LoadStringFromGzip("threejs/" + THREE_FN + ".gz").c_str(), + HAMMER_FN.c_str(), + LoadStringFromGzip("threejs/" + HAMMER_FN + ".gz").c_str(), + CONTROLS_FN.c_str(), + LoadString("threejs/" + CONTROLS_FN).c_str()); } fprintf(f, "var solvespace_model_%s = {\n" @@ -1239,7 +1196,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, diff --git a/src/exportstep.cpp b/src/exportstep.cpp index d5c9d406a..5bfa57f67 100644 --- a/src/exportstep.cpp +++ b/src/exportstep.cpp @@ -273,9 +273,45 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) { } fprintf(f, "),#%d,.T.);\n", srfid); - fprintf(f, "\n"); advancedFaces.Add(&advFaceId); + // Export the surface color and transparency + // https://www.cax-if.org/documents/rec_prac_styling_org_v16.pdf sections 4.4.2 4.2.4 etc. + // https://tracker.dev.opencascade.org/view.php?id=31550 + fprintf(f, "#%d=COLOUR_RGB('',%.2f,%.2f,%.2f);\n", ++id, ss->color.redF(), + ss->color.greenF(), ss->color.blueF()); + +/* // This works in Kisters 3DViewStation but not in KiCAD and Horison EDA, + // it seems they do not support transparency so use the more verbose one below + fprintf(f, "#%d=SURFACE_STYLE_TRANSPARENT(%.2f);\n", ++id, 1.0 - ss->color.alphaF()); + ++id; + fprintf(f, "#%d=SURFACE_STYLE_RENDERING_WITH_PROPERTIES(.NORMAL_SHADING.,#%d,(#%d));\n", + id, id - 2, id - 1); + ++id; + fprintf(f, "#%d=SURFACE_SIDE_STYLE('',(#%d));\n", id, id - 1); +*/ + + // This works in Horison EDA but is more verbose. + ++id; + fprintf(f, "#%d=FILL_AREA_STYLE_COLOUR('',#%d);\n", id, id - 1); + ++id; + fprintf(f, "#%d=FILL_AREA_STYLE('',(#%d));\n", id, id - 1); + ++id; + fprintf(f, "#%d=SURFACE_STYLE_FILL_AREA(#%d);\n", id, id - 1); + fprintf(f, "#%d=SURFACE_STYLE_TRANSPARENT(%.2f);\n", ++id, 1.0 - ss->color.alphaF()); + ++id; + fprintf(f, "#%d=SURFACE_STYLE_RENDERING_WITH_PROPERTIES(.NORMAL_SHADING.,#%d,(#%d));\n", id, id - 5, id - 1); + ++id; + fprintf(f, "#%d=SURFACE_SIDE_STYLE('',(#%d, #%d));\n", id, id - 3, id - 1); + + ++id; + fprintf(f, "#%d=SURFACE_STYLE_USAGE(.BOTH.,#%d);\n", id, id - 1); + ++id; + fprintf(f, "#%d=PRESENTATION_STYLE_ASSIGNMENT((#%d));\n", id, id - 1); + ++id; + fprintf(f, "#%d=STYLED_ITEM('',(#%d),#%d);\n", id, id - 1, advFaceId); + fprintf(f, "\n"); + id++; listOfLoops.Clear(); } @@ -317,22 +353,21 @@ void StepFileWriter::ExportSurfacesTo(const Platform::Path &filename) { 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(); } diff --git a/src/exportvector.cpp b/src/exportvector.cpp index 4de45f568..55abac685 100644 --- a/src/exportvector.cpp +++ b/src/exportvector.cpp @@ -170,22 +170,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 +193,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 +208,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 +276,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 +286,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; } @@ -551,6 +550,9 @@ void DxfFileWriter::StartFile() { paths.clear(); } +void DxfFileWriter::Background(RgbaColor color) { +} + void DxfFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { @@ -696,6 +698,35 @@ void EpsFileWriter::StartFile() { MmToPts(ptMax.y - ptMin.y)); } +void EpsFileWriter::Background(RgbaColor color) { + double width = ptMax.x - ptMin.x; + double height = ptMax.y - ptMin.y; + + fprintf(f, +"%.3f %.3f %.3f setrgbcolor\r\n" +"newpath\r\n" +" %.3f %.3f moveto\r\n" +" %.3f %.3f lineto\r\n" +" %.3f %.3f lineto\r\n" +" %.3f %.3f lineto\r\n" +" closepath\r\n" +"gsave fill grestore\r\n", + color.redF(), color.greenF(), color.blueF(), + MmToPts(0), MmToPts(0), + MmToPts(width), MmToPts(0), + MmToPts(width), MmToPts(height), + MmToPts(0), MmToPts(height)); + + // same issue with cracks, stroke it to avoid them + double sw = max(width, height) / 1000; + fprintf(f, +"1 setlinejoin\r\n" +"1 setlinecap\r\n" +"%.3f setlinewidth\r\n" +"gsave stroke grestore\r\n", + MmToPts(sw)); +} + void EpsFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { @@ -926,6 +957,30 @@ void PdfFileWriter::FinishAndCloseFile() { } +void PdfFileWriter::Background(RgbaColor color) { + double width = ptMax.x - ptMin.x; + double height = ptMax.y - ptMin.y; + double sw = max(width, height) / 1000; + + fprintf(f, +"1 J 1 j\r\n" +"%.3f %.3f %.3f RG\r\n" +"%.3f %.3f %.3f rg\r\n" +"%.3f w\r\n" +"%.3f %.3f m\r\n" +"%.3f %.3f l\r\n" +"%.3f %.3f l\r\n" +"%.3f %.3f l\r\n" +"b\r\n", + color.redF(), color.greenF(), color.blueF(), + color.redF(), color.greenF(), color.blueF(), + MmToPts(sw), + MmToPts(0), MmToPts(0), + MmToPts(width), MmToPts(0), + MmToPts(width), MmToPts(height), + MmToPts(0), MmToPts(height)); +} + void PdfFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { @@ -1027,17 +1082,18 @@ void SvgFileWriter::StartFile() { double sw = max(ptMax.x - ptMin.x, ptMax.y - ptMin.y) / 1000; fprintf(f, "stroke-width:%f;\r\n", sw); fprintf(f, "}\r\n"); - for(auto &style : SK.style) { - Style *s = &style; - RgbaColor strokeRgb = Style::Color(s->h, /*forExport=*/true); - StipplePattern pattern = Style::PatternType(s->h); - double stippleScale = Style::StippleScaleMm(s->h); + auto export_style = [&](hStyle hs) { + Style *s = Style::Get(hs); + RgbaColor strokeRgb = Style::Color(hs, /*forExport=*/true); + RgbaColor fillRgb = Style::FillColor(hs, /*forExport=*/true); + StipplePattern pattern = Style::PatternType(hs); + double stippleScale = Style::StippleScaleMm(hs); - fprintf(f, ".s%x {\r\n", s->h.v); + fprintf(f, ".s%x {\r\n", hs.v); fprintf(f, "stroke:#%02x%02x%02x;\r\n", strokeRgb.red, strokeRgb.green, strokeRgb.blue); // don't know why we have to take a half of the width - fprintf(f, "stroke-width:%f;\r\n", Style::WidthMm(s->h.v) / 2.0); + fprintf(f, "stroke-width:%f;\r\n", Style::WidthMm(hs.v) / 2.0); fprintf(f, "stroke-linecap:round;\r\n"); fprintf(f, "stroke-linejoin:round;\r\n"); std::string patternStr = MakeStipplePattern(pattern, stippleScale, ',', @@ -1045,12 +1101,33 @@ void SvgFileWriter::StartFile() { if(!patternStr.empty()) { fprintf(f, "stroke-dasharray:%s;\r\n", patternStr.c_str()); } - fprintf(f, "fill:none;\r\n"); + if(s->filled) { + fprintf(f, "fill:#%02x%02x%02x;\r\n", fillRgb.red, fillRgb.green, fillRgb.blue); + } + else { + fprintf(f, "fill:none;\r\n"); + } fprintf(f, "}\r\n"); + }; + + export_style({Style::NO_STYLE}); + for(auto &style : SK.style) { + Style *s = &style; + export_style(s->h); } fprintf(f, "]]>\r\n"); } +void SvgFileWriter::Background(RgbaColor color) { + fprintf(f, +"\r\n", + color.red, color.green, color.blue); +} + void SvgFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { @@ -1146,6 +1223,9 @@ void HpglFileWriter::StartFile() { fprintf(f, "SP1;\r\n"); } +void HpglFileWriter::Background(RgbaColor color) { +} + void HpglFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { @@ -1187,6 +1267,8 @@ void GCodeFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { } +void GCodeFileWriter::Background(RgbaColor color) { +} void GCodeFileWriter::FinishPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { @@ -1226,9 +1308,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()); } } @@ -1248,6 +1330,9 @@ void Step2dFileWriter::StartFile() { sfw.WriteHeader(); } +void Step2dFileWriter::Background(RgbaColor color) { +} + void Step2dFileWriter::Triangle(STriangle *tr) { } diff --git a/src/expr.cpp b/src/expr.cpp index 99d68ec4a..d5ed87b58 100644 --- a/src/expr.cpp +++ b/src/expr.cpp @@ -400,15 +400,24 @@ 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(std::vector *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; + if(list->end() != std::find_if(list->begin(), list->end(), + [=](const hParam &p) { return p.v == param.v; })) { + // We found ourselves in the list already, early out. + return; + } + list->push_back(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,6 +433,11 @@ bool Expr::DependsOn(hParam p) const { bool Expr::Tol(double a, double b) { return fabs(a - b) < 0.001; } + +bool Expr::IsZeroConst() const { + return op == Op::CONSTANT && EXACT(v == 0.0); +} + Expr *Expr::FoldConstants() { Expr *n = AllocExpr(); *n = *this; diff --git a/src/expr.h b/src/expr.h index 7109cf651..7eef4e005 100644 --- a/src/expr.h +++ b/src/expr.h @@ -70,9 +70,10 @@ class Expr { Expr *PartialWrt(hParam p) const; double Eval() const; - uint64_t ParamsUsed() const; + void ParamsUsedList(std::vector *list) const; bool DependsOn(hParam p) const; static bool Tol(double a, double b); + bool IsZeroConst() const; Expr *FoldConstants(); void Substitute(hParam oldh, hParam newh); diff --git a/src/file.cpp b/src/file.cpp index 657a4b2ae..3202e8a16 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -246,7 +246,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 +261,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 +285,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 +359,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 +378,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)); } @@ -468,6 +471,7 @@ void SolveSpaceUI::LoadUsingTable(const Platform::Path &filename, char *key, cha } bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel) { + bool fileIsEmpty = true; allConsistent = false; fileLoadError = false; @@ -485,6 +489,8 @@ bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel) char line[1024]; while(fgets(line, (int)sizeof(line), fh)) { + fileIsEmpty = false; + char *s = strchr(line, '\n'); if(s) *s = '\0'; // We should never get files with \r characters in them, but mailers @@ -545,6 +551,11 @@ bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel) fclose(fh); + if(fileIsEmpty) { + Error(_("The file is empty. It may be corrupt.")); + NewFile(); + } + if(fileLoadError) { Error(_("Unrecognized data in file. This file may be corrupt, or " "from a newer version of the program.")); @@ -702,6 +713,22 @@ void SolveSpaceUI::UpgradeLegacyData() { bool SolveSpaceUI::LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le, SMesh *m, SShell *sh) +{ + if(strcmp(filename.Extension().c_str(), "emn")==0) { + 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); + } +} + +bool SolveSpaceUI::LoadEntitiesFromSlvs(const Platform::Path &filename, EntityList *le, + SMesh *m, SShell *sh) { SSurface srf = {}; SCurve crv = {}; @@ -891,13 +918,18 @@ 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. - switch(LocateImportedFile(g.linkFile.RelativeTo(saveFile), canCancel)) { + const auto linkFileRelative = g.linkFile.RelativeTo(saveFile); + switch(LocateImportedFile(linkFileRelative, canCancel)) { case Platform::MessageDialog::Response::YES: { Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window); - dialog->AddFilters(Platform::SolveSpaceModelFileFilters); + dialog->AddFilters(Platform::SolveSpaceLinkFileFilters); dialog->ThawChoices(settings, "LinkSketch"); + dialog->SuggestFilename(linkFileRelative); if(dialog->RunModal()) { dialog->FreezeChoices(settings, "LinkSketch"); linkMap[g.linkFile] = dialog->GetFilename(); @@ -974,6 +1006,7 @@ bool SolveSpaceUI::ReloadLinkedImage(const Platform::Path &saveFile, Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window); dialog->AddFilters(Platform::RasterFileFilters); dialog->ThawChoices(settings, "LinkImage"); + dialog->SuggestFilename(filename->RelativeTo(saveFile)); if(dialog->RunModal()) { dialog->FreezeChoices(settings, "LinkImage"); *filename = dialog->GetFilename(); diff --git a/src/generate.cpp b/src/generate.cpp index a4016443f..fdb19b803 100644 --- a/src/generate.cpp +++ b/src/generate.cpp @@ -216,31 +216,33 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) // Not using range-for because we're using the index inside the loop. for(i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder[i]); + hGroup hg = SK.groupOrder[i]; // The group may depend on entities or other groups, to define its // workplane geometry or for its operands. Those must already exist // in a previous group, so check them before generating. - if(PruneGroups(g->h)) + if(PruneGroups(hg)) goto pruned; + int groupRequestIndex = 0; for(auto &req : SK.request) { Request *r = &req; - if(r->group != g->h) continue; + if(r->group != hg) continue; + r->groupRequestIndex = groupRequestIndex++; r->Generate(&(SK.entity), &(SK.param)); } for(auto &con : SK.constraint) { Constraint *c = &con; - if(c->group != g->h) continue; + if(c->group != hg) continue; c->Generate(&(SK.param)); } - g->Generate(&(SK.entity), &(SK.param)); + SK.GetGroup(hg)->Generate(&(SK.entity), &(SK.param)); // The requests and constraints depend on stuff in this or the // previous group, so check them after generating. - if(PruneRequests(g->h) || PruneConstraints(g->h)) + if(PruneRequests(hg) || PruneConstraints(hg)) goto pruned; // Use the previous values for params that we've seen before, as @@ -256,8 +258,9 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) } } - if(g->h == Group::HGROUP_REFERENCES) { + if(hg == Group::HGROUP_REFERENCES) { ForceReferences(); + Group *g = SK.GetGroup(hg); g->solved.how = SolveResult::OKAY; g->clean = true; } else { @@ -265,8 +268,9 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) if(i >= first && i <= last) { // The group falls inside the range, so really solve it, // and then regenerate the mesh based on the solved stuff. + Group *g = SK.GetGroup(hg); if(genForBBox) { - SolveGroupAndReport(g->h, andFindFree); + SolveGroupAndReport(hg, andFindFree); g->GenerateLoops(); } else { g->GenerateShellAndMesh(); @@ -531,6 +535,7 @@ void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) { WriteEqSystemForGroup(hg); Group *g = SK.GetGroup(hg); g->solved.remove.Clear(); + g->solved.findToFixTimeout = SS.timeoutRedundantConstr; SolveResult how = sys.Solve(g, NULL, &(g->solved.dof), &(g->solved.remove), @@ -545,8 +550,11 @@ void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) { } 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(); return result; diff --git a/src/graphicswin.cpp b/src/graphicswin.cpp index 0d520dd22..07d24f675 100644 --- a/src/graphicswin.cpp +++ b/src/graphicswin.cpp @@ -43,7 +43,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 }, @@ -74,6 +74,12 @@ const MenuEntry Menu[] = { { 1, N_("Select &Edge Chain"), Command::SELECT_CHAIN, C|'e', KN, mEdit }, { 1, N_("Select &All"), Command::SELECT_ALL, C|'a', KN, mEdit }, { 1, N_("&Unselect All"), Command::UNSELECT_ALL, '\x1b', KN, mEdit }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("&Line Styles..."), Command::EDIT_LINE_STYLES, 0, KN, mEdit }, +{ 1, N_("&View Projection..."), Command::VIEW_PROJECTION, 0, KN, mEdit }, +#ifndef __APPLE__ +{ 1, N_("Con&figuration..."), Command::CONFIGURATION, 0, KN, mEdit }, +#endif { 0, N_("&View"), Command::NONE, 0, KN, mView }, { 1, N_("Zoom &In"), Command::ZOOM_IN, '+', KN, mView }, @@ -86,13 +92,16 @@ const MenuEntry Menu[] = { { 1, N_("&Center View At Point"), Command::CENTER_VIEW, F|4, KN, mView }, { 1, NULL, Command::NONE, 0, KN, NULL }, { 1, N_("Show Snap &Grid"), Command::SHOW_GRID, '>', KC, mView }, +{ 1, N_("Darken Inactive Solids"), Command::DIM_SOLID_MODEL, 0, KC, mView }, { 1, N_("Use &Perspective Projection"), Command::PERSPECTIVE_PROJ, '`', KC, mView }, +{ 1, N_("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 }, @@ -117,7 +126,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 }, @@ -127,16 +136,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 }, @@ -145,9 +154,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 }, @@ -174,6 +183,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 @@ -290,7 +300,7 @@ void GraphicsWindow::PopulateMainMenu() { SS.UpdateWindowTitles(); PopulateMainMenu(); - EnsureValidActives(); + SS.GW.EnsureValidActives(); }); } } else if(Menu[i].fn == NULL) { @@ -306,8 +316,12 @@ void GraphicsWindow::PopulateMainMenu() { if(Menu[i].cmd == Command::SHOW_GRID) { showGridMenuItem = menuItem; + } else if(Menu[i].cmd == Command::DIM_SOLID_MODEL) { + dimSolidModelMenuItem = menuItem; } else if(Menu[i].cmd == Command::PERSPECTIVE_PROJ) { perspectiveProjMenuItem = menuItem; + } else if(Menu[i].cmd == Command::EXPLODE_SKETCH) { + explodeMenuItem = menuItem; } else if(Menu[i].cmd == Command::SHOW_TOOLBAR) { showToolbarMenuItem = menuItem; } else if(Menu[i].cmd == Command::SHOW_TEXT_WND) { @@ -320,6 +334,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) { @@ -360,8 +376,11 @@ 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 + // reference into the recentFiles vector), so take a copy of it here. + Platform::Path pathCopy(path); if(!SS.OkayToStartNewFile()) return; - SS.Load(path); + SS.Load(pathCopy); }); PopulateMenuWithPathnames(linkRecentMenu, SS.recentFiles, [](const Platform::Path &path) { @@ -390,16 +409,19 @@ 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; showSnapGrid = false; + dimSolidModel = true; context.active = false; toolbarHovered = Command::NONE; @@ -409,8 +431,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); @@ -476,12 +503,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) { @@ -565,7 +598,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); } } @@ -690,16 +723,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: @@ -716,6 +780,12 @@ void GraphicsWindow::MenuView(Command id) { } break; + case Command::DIM_SOLID_MODEL: + SS.GW.dimSolidModel = !SS.GW.dimSolidModel; + SS.GW.EnsureValidActives(); + SS.GW.Invalidate(/*clearPersistent=*/true); + break; + case Command::PERSPECTIVE_PROJ: SS.usePerspectiveProj = !SS.usePerspectiveProj; SS.GW.EnsureValidActives(); @@ -729,6 +799,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(); @@ -747,13 +823,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); @@ -822,6 +903,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(); @@ -904,6 +991,7 @@ void GraphicsWindow::EnsureValidActives() { case Unit::MM: case Unit::METERS: case Unit::INCHES: + case Unit::FEET_INCHES: break; default: SS.viewUnits = Unit::MM; @@ -912,12 +1000,15 @@ 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); 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()); @@ -948,12 +1039,19 @@ void GraphicsWindow::ForceTextWindowShown() { } void GraphicsWindow::DeleteTaggedRequests() { + // Delete any requests that were affected by this deletion. + for(Request &r : SK.request) { + 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. - Request *r; - 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(); @@ -1010,6 +1108,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(); @@ -1017,9 +1124,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; @@ -1029,13 +1135,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(); @@ -1043,24 +1148,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; } @@ -1077,7 +1181,7 @@ void GraphicsWindow::MenuEdit(Command id) { } } if(onChain && !alreadySelected) { - SS.GW.MakeSelected(e->h); + SS.GW.MakeSelected(e.h); newlySelected++; didSomething = true; } @@ -1161,10 +1265,7 @@ void GraphicsWindow::MenuEdit(Command id) { } // Regenerate, with these points marked as dragged so that they // get placed as close as possible to our snap grid. - SS.GW.ClearPending(); - SS.GW.ClearSelection(); - SS.GW.Invalidate(); break; } @@ -1183,6 +1284,22 @@ void GraphicsWindow::MenuEdit(Command id) { SS.ScheduleShowTW(); break; + case Command::EDIT_LINE_STYLES: + SS.TW.GoToScreen(TextWindow::Screen::LIST_OF_STYLES); + SS.GW.ForceTextWindowShown(); + SS.ScheduleShowTW(); + break; + case Command::VIEW_PROJECTION: + SS.TW.GoToScreen(TextWindow::Screen::EDIT_VIEW); + SS.GW.ForceTextWindowShown(); + SS.ScheduleShowTW(); + break; + case Command::CONFIGURATION: + SS.TW.GoToScreen(TextWindow::Screen::CONFIGURATION); + SS.GW.ForceTextWindowShown(); + SS.ScheduleShowTW(); + break; + default: ssassert(false, "Unexpected menu ID"); } } @@ -1197,6 +1314,8 @@ void GraphicsWindow::MenuRequest(Command id) { if(SS.GW.gs.n == 1 && SS.GW.gs.workplanes == 1) { // A user-selected workplane g->activeWorkplane = SS.GW.gs.entity[0]; + SS.GW.EnsureValidActives(); + SS.ScheduleShowTW(); } else if(g->type == Group::Type::DRAWING_WORKPLANE) { // The group's default workplane g->activeWorkplane = g->h.entity(0); @@ -1211,6 +1330,8 @@ void GraphicsWindow::MenuRequest(Command id) { "not have a default workplane. Try selecting a " "workplane, or activating a sketch-in-new-workplane " "group.")); + //update checkboxes in the menus + SS.GW.EnsureValidActives(); } break; } @@ -1262,6 +1383,20 @@ void GraphicsWindow::MenuRequest(Command id) { break; case Command::CONSTRUCTION: { + // if we are drawing + 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_CUBIC_POINT || + SS.GW.pending.operation == Pending::DRAGGING_NEW_RADIUS) { + for(auto &hr : SS.GW.pending.requests) { + Request* r = SK.GetRequest(hr); + r->construction = !(r->construction); + SS.MarkGroupDirty(r->group); + } + SS.GW.Invalidate(); + break; + } SS.GW.GroupSelection(); if(SS.GW.gs.entities == 0) { Error(_("No entities are selected. Select entities before " @@ -1311,6 +1446,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(); } diff --git a/src/group.cpp b/src/group.cpp index ad00efe1d..1b00943e2 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -136,14 +136,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; } @@ -287,7 +303,7 @@ void Group::MenuGroup(Command id, Platform::Path linkFile) { g.meshCombine = CombineAs::ASSEMBLE; if(g.linkFile.IsEmpty()) { Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window); - dialog->AddFilters(Platform::SolveSpaceModelFileFilters); + dialog->AddFilters(Platform::SolveSpaceLinkFileFilters); dialog->ThawChoices(settings, "LinkSketch"); if(!dialog->RunModal()) return; dialog->FreezeChoices(settings, "LinkSketch"); @@ -392,7 +408,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,11 +426,10 @@ 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(); @@ -443,11 +464,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 +500,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"); @@ -516,9 +540,6 @@ void Group::Generate(IdList *entity, Vector axis_pos = SK.GetEntity(predef.origin)->PointGetNum(); Vector axis_dir = SK.GetEntity(predef.entityB)->VectorGetNum(); - // Remapped entity index. - int ai = 1; - // Not using range-for here because we're changing the size of entity in the loop. for(i = 0; i < entity->n; i++) { Entity *e = &(entity->Get(i)); @@ -529,24 +550,23 @@ void Group::Generate(IdList *entity, // As soon as I call CopyEntity, e may become invalid! That // adds entities, which may cause a realloc. - CopyEntity(entity, SK.GetEntity(predef.origin), 0, ai, - NO_PARAM, NO_PARAM, NO_PARAM, - NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, - CopyAs::NUMERIC); + // this is the regular copy of all entities CopyEntity(entity, SK.GetEntity(he), 0, REMAP_LATHE_START, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, CopyAs::NUMERIC); - CopyEntity(entity, SK.GetEntity(he), 0, REMAP_LATHE_END, - NO_PARAM, NO_PARAM, NO_PARAM, - NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, - CopyAs::NUMERIC); - - MakeLatheCircles(entity, param, he, axis_pos, axis_dir, ai); + e = &(entity->Get(i)); // because we copied. + if (e->IsPoint()) { + // for points this copy is used for the circle centers + CopyEntity(entity, SK.GetEntity(he), 0, REMAP_LATHE_ARC_CENTER, + NO_PARAM, NO_PARAM, NO_PARAM, + NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, + CopyAs::NUMERIC); + MakeLatheCircles(entity, param, he, axis_pos, axis_dir); + }; MakeLatheSurfacesSelectable(entity, he, axis_dir); - ai++; } return; } @@ -566,39 +586,42 @@ void Group::Generate(IdList *entity, AddParam(param, h.param(5), axis_dir.y); AddParam(param, h.param(6), axis_dir.z); - int ai = 1; + // Get some arbitrary point in the sketch, that will be used + // as a reference when defining end faces. + hEntity pt = { 0 }; + int ai = 0, af = 2; + if (subtype == Subtype::TWO_SIDED) + { + ai = -1; + af = 1; + } // Not using range-for here because we're changing the size of entity in the loop. for(i = 0; i < entity->n; i++) { Entity *e = &(entity->Get(i)); if(e->group != opA) continue; + if(e->IsPoint()) pt = e->h; + e->CalculateNumerical(/*forExport=*/false); hEntity he = e->h; + // one copy for each end of the revolved surface + CopyEntity(entity, e, ai, REMAP_LATHE_START, h.param(0), + h.param(1), h.param(2), h.param(3), h.param(4), h.param(5), + h.param(6), NO_PARAM, CopyAs::N_ROT_AA); - CopyEntity(entity, SK.GetEntity(predef.origin), 0, ai, NO_PARAM, NO_PARAM, - NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, CopyAs::NUMERIC); + e = &(entity->Get(i)); // because we copied. + CopyEntity(entity, e, af, REMAP_LATHE_END, h.param(0), + h.param(1), h.param(2), h.param(3), h.param(4), h.param(5), + h.param(6), NO_PARAM, CopyAs::N_ROT_AA); - for(a = 0; a < 2; a++) { - //! @todo is this check redundant? - Entity *e = &(entity->Get(i)); - if(e->group != opA) - continue; - - e->CalculateNumerical(false); - CopyEntity(entity, e, a * 2 - (subtype == Subtype::ONE_SIDED ? 0 : 1), - (a == 1) ? REMAP_LATHE_END : REMAP_LATHE_START, h.param(0), - h.param(1), h.param(2), h.param(3), h.param(4), h.param(5), - h.param(6), NO_PARAM, CopyAs::N_ROT_AA); - } // Arcs are not generated for revolve groups, for now, because our current arc // entity is not chiral, and dragging a revolve may break the arc by inverting it. - // MakeLatheCircles(entity, param, he, axis_pos, axis_dir, ai); + // MakeLatheCircles(entity, param, he, axis_pos, axis_dir); MakeLatheSurfacesSelectable(entity, he, axis_dir); - ai++; } - + MakeRevolveEndFaces(entity, pt, ai, af); return; } @@ -618,7 +641,16 @@ void Group::Generate(IdList *entity, // distance to translate along the rotation axis AddParam(param, h.param(7), 20); - int ai = 1; + // Get some arbitrary point in the sketch, that will be used + // as a reference when defining end faces. + hEntity pt = { 0 }; + + int ai = 0, af = 2; // initial and final number of transformations + if (subtype != Subtype::ONE_SIDED) + { + ai = -1; + af = 1; + } // Not using range-for here because we're changing the size of entity in the loop. for(i = 0; i < entity->n; i++) { @@ -626,19 +658,20 @@ void Group::Generate(IdList *entity, if((e->group.v != opA.v) && !(e->h == predef.origin)) continue; + if(e->IsPoint()) pt = e->h; + e->CalculateNumerical(/*forExport=*/false); - CopyEntity(entity, SK.GetEntity(predef.origin), 0, ai, NO_PARAM, NO_PARAM, - NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, CopyAs::NUMERIC); + // one copy for each end of the helix + CopyEntity(entity, e, ai, REMAP_LATHE_START, h.param(0), + h.param(1), h.param(2), h.param(3), h.param(4), h.param(5), + h.param(6), h.param(7), CopyAs::N_ROT_AXIS_TRANS); + + e = &(entity->Get(i)); // because we copied. + CopyEntity(entity, e, af, REMAP_LATHE_END, h.param(0), + h.param(1), h.param(2), h.param(3), h.param(4), h.param(5), + h.param(6), h.param(7), CopyAs::N_ROT_AXIS_TRANS); - for(a = 0; a < 2; a++) { - Entity *e = &(entity->Get(i)); - e->CalculateNumerical(false); - CopyEntity(entity, e, a * 2 - (subtype == Subtype::ONE_SIDED ? 0 : 1), - (a == 1) ? REMAP_LATHE_END : REMAP_LATHE_START, h.param(0), - h.param(1), h.param(2), h.param(3), h.param(4), h.param(5), - h.param(6), h.param(7), CopyAs::N_ROT_AXIS_TRANS); - } // For point entities on the axis, create a construction line e = &(entity->Get(i)); if(e->IsPoint()) { @@ -658,8 +691,8 @@ void Group::Generate(IdList *entity, entity->Add(&en); } } - ai++; } + MakeRevolveEndFaces(entity, pt, ai, af); return; } @@ -791,10 +824,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(); @@ -864,15 +904,15 @@ void Group::MakeExtrusionLines(IdList *el, hEntity in) { } } -void Group::MakeLatheCircles(IdList *el, IdList *param, hEntity in, Vector pt, Vector axis, int ai) { +void Group::MakeLatheCircles(IdList *el, IdList *param, hEntity in, Vector pt, Vector axis) { Entity *ep = SK.GetEntity(in); Entity en = {}; if(ep->IsPoint()) { // A point gets revolved to form an arc. - en.point[0] = Remap(predef.origin, ai); + en.point[0] = Remap(ep->h, REMAP_LATHE_ARC_CENTER); en.point[1] = Remap(ep->h, REMAP_LATHE_START); - en.point[2] = Remap(ep->h, REMAP_LATHE_END); + en.point[2] = en.point[1]; //Remap(ep->h, REMAP_LATHE_END); // Get arc center and point on arc. Entity *pc = SK.GetEntity(en.point[0]); @@ -943,6 +983,46 @@ 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) +{ + if(pt.v == 0) return; + Group *src = SK.GetGroup(opA); + Vector n = src->polyLoops.normal; + + // When there is no loop normal (e.g. if the loop is broken), use normal of workplane + // as fallback, to avoid breaking constraints depending on the faces. + if(n.Equals(Vector::From(0.0, 0.0, 0.0)) && src->type == Group::Type::DRAWING_WORKPLANE) { + n = SK.GetEntity(src->h.entity(0))->Normal()->NormalN(); + } + + Entity en = {}; + en.type = Entity::Type::FACE_ROT_NORMAL_PT; + en.group = h; + // The center of rotation + en.param[0] = h.param(0); + en.param[1] = h.param(1); + en.param[2] = h.param(2); + // The rotation quaternion + en.param[3] = h.param(3); + en.param[4] = h.param(4); + en.param[5] = h.param(5); + en.param[6] = h.param(6); + + en.numNormal = Quaternion::From(0, n.x, n.y, n.z); + en.point[0] = Remap(pt, REMAP_LATHE_START); + en.timesApplied = ai; + en.h = Remap(Entity::NO_ENTITY, REMAP_LATHE_START); + el->Add(&en); + + en.point[0] = Remap(pt, REMAP_LATHE_END); + en.timesApplied = af; + en.h = Remap(Entity::NO_ENTITY, REMAP_LATHE_END); + el->Add(&en); +} + void Group::MakeExtrusionTopBottomFaces(IdList *el, hEntity pt) { if(pt.v == 0) return; @@ -1063,6 +1143,8 @@ void Group::CopyEntity(IdList *el, case Entity::Type::FACE_N_ROT_TRANS: case Entity::Type::FACE_N_TRANS: case Entity::Type::FACE_N_ROT_AA: + case Entity::Type::FACE_ROT_NORMAL_PT: + case Entity::Type::FACE_N_ROT_AXIS_TRANS: if(as == CopyAs::N_TRANS) { en.type = Entity::Type::FACE_N_TRANS; en.param[0] = dx; @@ -1070,8 +1152,18 @@ void Group::CopyEntity(IdList *el, en.param[2] = dz; } else if (as == CopyAs::NUMERIC) { en.type = Entity::Type::FACE_NORMAL_PT; + } else if (as == CopyAs::N_ROT_AXIS_TRANS) { + en.type = Entity::Type::FACE_N_ROT_AXIS_TRANS; + en.param[0] = dx; + en.param[1] = dy; + en.param[2] = dz; + en.param[3] = qw; + en.param[4] = qvx; + en.param[5] = qvy; + en.param[6] = qvz; + en.param[7] = dist; } else { - if(as == CopyAs::N_ROT_AA || as == CopyAs::N_ROT_AXIS_TRANS) { + if(as == CopyAs::N_ROT_AA) { en.type = Entity::Type::FACE_N_ROT_AA; } else { en.type = Entity::Type::FACE_N_ROT_TRANS; @@ -1089,6 +1181,11 @@ 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, @@ -1111,3 +1208,6 @@ 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; +} diff --git a/src/groupmesh.cpp b/src/groupmesh.cpp index b2e390e14..b539702eb 100644 --- a/src/groupmesh.cpp +++ b/src/groupmesh.cpp @@ -83,13 +83,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; } } @@ -106,25 +105,28 @@ void SMesh::RemapFaces(Group *g, int remap) { template void Group::GenerateForStepAndRepeat(T *steps, T *outs, Group::CombineAs forWhat) { - T workA, workB; - workA = {}; - workB = {}; - T *soFar = &workA, *scratch = &workB; int n = (int)valA, a0 = 0; if(subtype == Subtype::ONE_SIDED && skipFirst) { a0++; n++; } + int a; + // create all the transformed copies + std::vector transd(n); + std::vector workA(n); + workA[0] = {}; + // first generate a shell/mesh with each transformed copy +#pragma omp parallel for for(a = a0; a < n; a++) { + transd[a] = {}; + workA[a] = {}; int ap = a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1)); - int remap = (a == (n - 1)) ? REMAP_LAST : a; - T transd = {}; if(type == Type::TRANSLATE) { Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); trans = trans.ScaledBy(ap); - transd.MakeFromTransformationOf(steps, + transd[a].MakeFromTransformationOf(steps, trans, Quaternion::IDENTITY, 1.0); } else { Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); @@ -133,29 +135,45 @@ void Group::GenerateForStepAndRepeat(T *steps, T *outs, Group::CombineAs forWhat Vector axis = Vector::From(h.param(4), h.param(5), h.param(6)); Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z); // Rotation is centered at t; so A(x - t) + t = Ax + (t - At) - transd.MakeFromTransformationOf(steps, + transd[a].MakeFromTransformationOf(steps, trans.Minus(q.Rotate(trans)), q, 1.0); } - + } + for(a = a0; a < n; a++) { // We need to rewrite any plane face entities to the transformed ones. - transd.RemapFaces(this, remap); + int remap = (a == (n - 1)) ? REMAP_LAST : a; + transd[a].RemapFaces(this, remap); + } - // And tack this transformed copy on to the return. - if(soFar->IsEmpty()) { - scratch->MakeFromCopyOf(&transd); - } else if (forWhat == CombineAs::ASSEMBLE) { - scratch->MakeFromAssemblyOf(soFar, &transd); - } else { - scratch->MakeFromUnionOf(soFar, &transd); + std::vector *soFar = &transd; + std::vector *scratch = &workA; + // do the boolean operations on pairs of equal size + while(n > 1) { + for(a = 0; a < n; a+=2) { + scratch->at(a/2).Clear(); + // combine a pair of shells + if((a==0) && (a0==1)) { // if the first was skipped just copy the 2nd + scratch->at(a/2).MakeFromCopyOf(&(soFar->at(a+1))); + (soFar->at(a+1)).Clear(); + a0 = 0; + } else if (a == n-1) { // for an odd number just copy the last one + scratch->at(a/2).MakeFromCopyOf(&(soFar->at(a))); + (soFar->at(a)).Clear(); + } else if(forWhat == CombineAs::ASSEMBLE) { + scratch->at(a/2).MakeFromAssemblyOf(&(soFar->at(a)), &(soFar->at(a+1))); + (soFar->at(a)).Clear(); + (soFar->at(a+1)).Clear(); + } else { + scratch->at(a/2).MakeFromUnionOf(&(soFar->at(a)), &(soFar->at(a+1))); + (soFar->at(a)).Clear(); + (soFar->at(a+1)).Clear(); + } } - swap(scratch, soFar); - scratch->Clear(); - transd.Clear(); + n = (n+1)/2; } - outs->Clear(); - *outs = *soFar; + *outs = soFar->at(0); } template @@ -170,12 +188,22 @@ void Group::GenerateForBoolean(T *prevs, T *thiss, T *outs, Group::CombineAs how // So our group's shell appears in thisShell. Combine this with the // previous group's shell, using the requested operation. - if(how == CombineAs::UNION) { - outs->MakeFromUnionOf(prevs, thiss); - } else if(how == CombineAs::DIFFERENCE) { - outs->MakeFromDifferenceOf(prevs, thiss); - } else { - outs->MakeFromAssemblyOf(prevs, thiss); + switch(how) { + case CombineAs::UNION: + outs->MakeFromUnionOf(prevs, thiss); + break; + + case CombineAs::DIFFERENCE: + outs->MakeFromDifferenceOf(prevs, thiss); + break; + + case CombineAs::INTERSECTION: + outs->MakeFromIntersectionOf(prevs, thiss); + break; + + case CombineAs::ASSEMBLE: + outs->MakeFromAssemblyOf(prevs, thiss); + break; } } @@ -220,7 +248,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); @@ -263,13 +291,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. @@ -278,7 +305,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; } @@ -540,7 +567,8 @@ void Group::DrawMesh(DrawMeshAs how, Canvas *canvas) { if(!SS.GW.showShaded) { fillFront.layer = Canvas::Layer::DEPTH_ONLY; } - if(type == Type::DRAWING_3D || type == Type::DRAWING_WORKPLANE) { + if((type == Type::DRAWING_3D || type == Type::DRAWING_WORKPLANE) + && SS.GW.dimSolidModel) { fillFront.color = Style::Color(Style::DIM_SOLID); } Canvas::hFill hcfFront = canvas->GetFill(fillFront); @@ -607,8 +635,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; } @@ -757,11 +788,7 @@ void Group::DrawContourAreaLabels(Canvas *canvas) { Canvas::Stroke stroke = Style::Stroke(hs); stroke.layer = Canvas::Layer::FRONT; - double scale = SS.MmPerUnit(); - std::string label = ssprintf("%.3f %s²", - fabs(sbls.SignedArea() / (scale * scale)), - SS.UnitName()); - + std::string label = SS.MmToStringSI(fabs(sbls.SignedArea()), /*dim=*/2); double fontHeight = Style::TextHeight(hs); double textWidth = VectorFont::Builtin()->GetWidth(fontHeight, label), textHeight = VectorFont::Builtin()->GetCapHeight(fontHeight); diff --git a/src/importdxf.cpp b/src/importdxf.cpp index f5c44086e..ce80280a8 100644 --- a/src/importdxf.cpp +++ b/src/importdxf.cpp @@ -9,6 +9,21 @@ static std::string ToUpper(std::string str) { return str; } +static Quaternion NormalFromExtPoint(Vector extPoint) { + // DXF arbitrary axis algorithm for transforming a Z-vector into a rotated + // coordinate system + Vector ax, ay; + Vector az = extPoint.WithMagnitude(1.0); + + if ((fabs(az.x) < 1/64.) && (fabs(az.y) < 1/64.)) { + ax = Vector::From(0, 1, 0).Cross(az).WithMagnitude(1.0); + } else { + ax = Vector::From(0, 0, 1).Cross(az).WithMagnitude(1.0); + } + ay = az.Cross(ax).WithMagnitude(1.0); + return Quaternion::From(ax, ay); +} + class DxfImport : public DRW_Interface { public: Vector blockX; @@ -516,12 +531,41 @@ class DxfImport : public DRW_Interface { return hr.entity(0); } - hEntity createCircle(const Vector &c, double r, hStyle style) { + hEntity createWorkplane(const Vector &p, const Quaternion &q) { + hRequest hr = SS.GW.AddRequest(Request::Type::WORKPLANE, /*rememberForUndo=*/false); + SK.GetEntity(hr.entity(1))->PointForceTo(p); + processPoint(hr.entity(1)); + SK.GetEntity(hr.entity(32))->NormalForceTo(q); + return hr.entity(0); + } + + hEntity findOrCreateWorkplane(const Vector &p, const Quaternion &q) { + Vector z = q.RotationN(); + for(auto &r : SK.request) { + if((r.type == Request::Type::WORKPLANE) && (r.group == SS.GW.activeGroup)) { + Vector wp = SK.GetEntity(r.h.entity(1))->PointGetNum(); + Vector wz = SK.GetEntity(r.h.entity(32))->NormalN(); + + if ((p.DistanceToPlane(wz, wp) < LENGTH_EPS) && z.Equals(wz)) { + return r.h.entity(0); + } + } + } + + return createWorkplane(p, q); + } + + static void activateWorkplane(hEntity he) { + Group *g = SK.GetGroup(SS.GW.activeGroup); + g->activeWorkplane = he; + } + + hEntity createCircle(const Vector &c, const Quaternion &q, double r, hStyle style) { hRequest hr = SS.GW.AddRequest(Request::Type::CIRCLE, /*rememberForUndo=*/false); SK.GetEntity(hr.entity(1))->PointForceTo(c); processPoint(hr.entity(1)); + SK.GetEntity(hr.entity(32))->NormalForceTo(q); SK.GetEntity(hr.entity(64))->DistanceForceTo(r); - configureRequest(hr, style); return hr.entity(0); } @@ -560,13 +604,25 @@ class DxfImport : public DRW_Interface { if(data.space != DRW::ModelSpace) return; if(addPendingBlockEntity(data)) return; - hRequest hr = SS.GW.AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false); double r = data.radious; double sa = data.staangle; double ea = data.endangle; - Vector c = Vector::From(data.basePoint.x, data.basePoint.y, data.basePoint.z); - Vector rvs = Vector::From(r * cos(sa), r * sin(sa), data.basePoint.z).Plus(c); - Vector rve = Vector::From(r * cos(ea), r * sin(ea), data.basePoint.z).Plus(c); + Vector c = toVector(data.basePoint); + Vector nz = toVector(data.extPoint); + Quaternion q = NormalFromExtPoint(nz); + + bool planar = q.RotationN().Equals(Vector::From(0, 0, 1)); + bool onPlane = c.z < LENGTH_EPS; + + hEntity oldWorkplane = SS.GW.ActiveWorkplane(); + if (!planar || !onPlane) { + activateWorkplane(findOrCreateWorkplane(c, q)); + } + + hRequest hr = SS.GW.AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false); + Vector u = q.RotationU(), v = q.RotationV(); + Vector rvs = c.Plus(u.ScaledBy(r * cos(sa))).Plus(v.ScaledBy(r * sin(sa))); + Vector rve = c.Plus(u.ScaledBy(r * cos(ea))).Plus(v.ScaledBy(r * sin(ea))); if(data.extPoint.z == -1.0) { c.x = -c.x; @@ -584,13 +640,16 @@ class DxfImport : public DRW_Interface { processPoint(hr.entity(2)); processPoint(hr.entity(3)); configureRequest(hr, styleFor(&data)); + activateWorkplane(oldWorkplane); } void addCircle(const DRW_Circle &data) override { if(data.space != DRW::ModelSpace) return; if(addPendingBlockEntity(data)) return; - createCircle(toVector(data.basePoint), data.radious, styleFor(&data)); + Vector nz = toVector(data.extPoint); + Quaternion normal = NormalFromExtPoint(nz); + createCircle(toVector(data.basePoint), normal, data.radious, styleFor(&data)); } void addLWPolyline(const DRW_LWPolyline &data) override { @@ -835,9 +894,9 @@ class DxfImport : public DRW_Interface { } } - hConstraint createDiametric(Vector cp, double r, Vector tp, double actual, - bool asRadius = false) { - hEntity he = createCircle(cp, r, invisibleStyle()); + hConstraint createDiametric(Vector cp, Quaternion q, double r, Vector tp, + double actual, bool asRadius = false) { + hEntity he = createCircle(cp, q, r, invisibleStyle()); hConstraint hc = Constraint::Constrain( Constraint::Type::DIAMETER, @@ -869,7 +928,9 @@ class DxfImport : public DRW_Interface { actual = data->getActualMeasurement(); } - createDiametric(cp, cp.Minus(dp).Magnitude(), tp, actual, /*asRadius=*/true); + Vector nz = toVector(data->getExtrusion()); + Quaternion q = NormalFromExtPoint(nz); + createDiametric(cp, q, cp.Minus(dp).Magnitude(), tp, actual, /*asRadius=*/true); } void addDimDiametric(const DRW_DimDiametric *data) override { @@ -886,7 +947,9 @@ class DxfImport : public DRW_Interface { actual = data->getActualMeasurement(); } - createDiametric(cp, cp.Minus(dp1).Magnitude(), tp, actual, /*asRadius=*/false); + Vector nz = toVector(data->getExtrusion()); + Quaternion q = NormalFromExtPoint(nz); + createDiametric(cp, q, cp.Minus(dp1).Magnitude(), tp, actual, /*asRadius=*/false); } void addDimAngular3P(const DRW_DimAngular3p *data) override { @@ -972,11 +1035,13 @@ class DxfCheck3D : public DRW_Interface { void addArc(const DRW_Arc &data) override { if(data.space != DRW::ModelSpace) return; checkCoord(data.basePoint); + checkExt(data.extPoint); } void addCircle(const DRW_Circle &data) override { if(data.space != DRW::ModelSpace) return; checkCoord(data.basePoint); + checkExt(data.extPoint); } void addPolyline(const DRW_Polyline &data) override { @@ -1041,6 +1106,7 @@ class DxfCheck3D : public DRW_Interface { checkCoord(data->getCenterPoint()); checkCoord(data->getDiameterPoint()); checkCoord(data->getTextPoint()); + checkExt(data->getExtrusion()); } void addDimDiametric(const DRW_DimDiametric *data) override { @@ -1048,6 +1114,7 @@ class DxfCheck3D : public DRW_Interface { checkCoord(data->getDiameter1Point()); checkCoord(data->getDiameter2Point()); checkCoord(data->getTextPoint()); + checkExt(data->getExtrusion()); } void addDimAngular3P(const DRW_DimAngular3p *data) override { @@ -1066,6 +1133,12 @@ class DxfCheck3D : public DRW_Interface { is3d = true; } } + + void checkExt(const DRW_Coord &coord) { + if ((fabs(coord.x) > 1/64.) || (fabs(coord.y) > 1/64.)) { + is3d = true; + } + } }; static void @@ -1112,14 +1185,14 @@ ImportDwgDxf(const Platform::Path &filename, void ImportDxf(const Platform::Path &filename) { ImportDwgDxf(filename, [](const std::string &data, DRW_Interface *intf) { std::stringstream stream(data); - return dxfRW().read(stream, intf, /*ext=*/false); + return dxfRW().read(stream, intf, /*ext=*/true); }); } void ImportDwg(const Platform::Path &filename) { ImportDwgDxf(filename, [](const std::string &data, DRW_Interface *intf) { std::stringstream stream(data); - return dwgR().read(stream, intf, /*ext=*/false); + return dwgR().read(stream, intf, /*ext=*/true); }); } diff --git a/src/importidf.cpp b/src/importidf.cpp new file mode 100644 index 000000000..095075d8f --- /dev/null +++ b/src/importidf.cpp @@ -0,0 +1,538 @@ +//----------------------------------------------------------------------------- +// Intermediate Data Format (IDF) file reader. Reads an IDF file for PCB outlines and creates +// an equivalent SovleSpace sketch/extrusion. Supports only Linking, not import. +// Part placement is not currently supported. +// +// Copyright 2020 Paul Kahler. +//----------------------------------------------------------------------------- +#include "solvespace.h" +#include "sketch.h" + +// Split a string into substrings separated by spaces. +// Allow quotes to enclose spaces within a string +static std::vector splitString(const std::string line) { + std::vector v = {}; + + if(line.length() == 0) return v; + + std::string s = ""; + bool inString = false; + bool inQuotes = false; + + for (size_t i=0; i 0) + v.push_back(s); + + 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 entities that +// get remapped into a linked group similar to linking .slvs files +////////////////////////////////////////////////////////////////////////////// + +// Make a new point - type doesn't matter since we will make a copy later +static hEntity newPoint(EntityList *el, int *id, Vector p, bool visible = true) { + Entity en = {}; + en.type = Entity::Type::POINT_N_COPY; + en.extraPoints = 0; + en.timesApplied = 0; + en.group.v = 462; + en.actPoint = p; + en.construction = false; + en.style.v = Style::DATUM; + en.actVisible = visible; + 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, bool keepout) { + 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 = keepout; + en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP; + 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 newNormal(EntityList *el, int *id, Quaternion normal) { + // 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::ACTIVE_GRP; + // to be visible we need to add a point. + en.point[0] = newPoint(el, id, Vector::From(0,0,3), /*visible=*/ true); + 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 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; + en.point[1] = p0; + en.point[2] = p1; + en.normal = hnorm; + en.extraPoints = 0; + en.timesApplied = 0; + en.group.v = 403; + en.construction = keepout; + en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP; + en.actVisible = true; + en.forceHidden = false; *id = *id+1; + + *id = *id + 1; + en.h.v = *id + en.group.v*65536; + el->Add(&en); + return en.h; +} + +static hEntity newDistance(EntityList *el, int *id, double distance) { + // normals have parameters, but we don't need them to make a NORMAL_N_COPY from this + Entity en = {}; + en.type = Entity::Type::DISTANCE; + en.extraPoints = 0; + en.timesApplied = 0; + en.group.v = 472; + en.actDistance = distance; + en.construction = false; + en.style.v = Style::ACTIVE_GRP; + // to be visible we'll need to add a point? + en.actVisible = false; + en.forceHidden = false; + + *id = *id+1; + en.h.v = *id + en.group.v*65536; + el->Add(&en); + return en.h; +} + +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; + en.normal = hnorm; + en.distance = hdist; + en.extraPoints = 0; + en.timesApplied = 0; + en.group.v = 399; + en.construction = keepout; + en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP; + en.actVisible = true; + en.forceHidden = false; + + *id = *id+1; + en.h.v = *id + en.group.v*65536; + el->Add(&en); + return en.h; +} + +static Vector ArcCenter(Vector p0, Vector p1, double angle) { + // locate the center of an 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; + } + Vector c = m.Minus(perp.ScaledBy(dist)); + return c; +} + +// Add an IDF line or arc to the entity list. According to spec, zero angle indicates a line. +// 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, bool keepout) { + if (angle == 0.0) { + //line + if(p0.Equals(p1)) return; + + 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, keepout); + + } else { + // arc + if(angle < 0.0) { + swap(p0,p1); + swap(h0,h1); + } + // 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; + } + Vector c = m.Minus(perp.ScaledBy(dist)); + hEntity hc = newPoint(el, id, c, /*visible=*/false); + newArc(el, id, h0, h1, hc, hnorm, keepout); + } +} + +// borrowed from Entity::GenerateBezierCurves because we don't have parameters. +static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vector pb, + Quaternion q, double angle) { + + Vector u = q.RotationU(), v = q.RotationV(); + double r = pa.Minus(center).Magnitude(); + double theta, dtheta; + + if(angle == 360.0) { + theta = 0; + } else { + Point2d c2 = center.Project2d(u, v); + Point2d pa2 = (pa.Project2d(u, v)).Minus(c2); + + theta = atan2(pa2.y, pa2.x); + } + dtheta = angle * PI/180; + + int i, n; + if(dtheta > (3*PI/2 + 0.01)) { + n = 4; + } else if(dtheta > (PI + 0.01)) { + n = 3; + } else if(dtheta > (PI/2 + 0.01)) { + n = 2; + } else { + n = 1; + } + dtheta /= n; + + for(i = 0; i < n; i++) { + double s, c; + + c = cos(theta); + s = sin(theta); + // The start point of the curve, and the tangent vector at + // that start point. + Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)), + t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c)); + + theta += dtheta; + + c = cos(theta); + s = sin(theta); + Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)), + t2 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c)); + + // The control point must lie on both tangents. + Vector p1 = Vector::AtIntersectionOfLines(p0, p0.Plus(t0), + p2, p2.Plus(t2), + NULL); + + SBezier sb = SBezier::From(p0, p1, p2); + sb.weight[1] = cos(dtheta/2); + sbl->l.Add(&sb); + } +} + +namespace SolveSpace { + +// Here we read the important section of an IDF file. SolveSpace Entities are directly created by +// 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 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) { + dbp("\nLink IDF board outline."); + el->Clear(); + std::string data; + if(!ReadFile(filename, &data)) { + Error("Couldn't read from '%s'", filename.raw.c_str()); + return false; + } + + enum IDF_SECTION { + none, + header, + board_outline, + other_outline, + routing_outline, + placement_outline, + routing_keepout, + via_keepout, + placement_group, + drilled_holes, + notes, + component_placement + } section; + + section = IDF_SECTION::none; + int record_number = 0; + int curve = -1; + int entityCount = 0; + + hEntity hprev; + hEntity hprevTop; + Vector pprev = Vector::From(0,0,0); + Vector pprevTop = Vector::From(0,0,0); + + double board_thickness = 10.0; + double scale = 1.0; //mm + 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); + + // to create the extursion we will need to collect a set of bezier curves defined + // by the perimeter, cutouts, and holes. + SBezierList sbl = {}; + + std::stringstream stream(data); + for(std::string line; getline( stream, line ); ) { + if (line.find(".END_") == 0) { + section = none; + curve = -1; + } + switch (section) { + case none: + if(line.find(".HEADER") == 0) { + section = header; + record_number = 1; + } else if (line.find(".BOARD_OUTLINE") == 0) { + section = board_outline; + 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; + } + break; + + case header: + if(record_number == 3) { + if(line.find("MM") != std::string::npos) { + dbp("IDF units are MM"); + scale = 1.0; + } else if(line.find("THOU") != std::string::npos) { + dbp("IDF units are thousandths of an inch"); + scale = 0.0254; + } else { + dbp("IDF import, no units found in file."); + } + } + break; + + case routing_keepout: + case board_outline: + if (record_number == 2) { + if(section == board_outline) { + topEntities = true; + bottomEntities = true; + board_thickness = std::stod(line) * scale; + dbp("IDF board thickness: %lf", board_thickness); + } else if (section == routing_keepout) { + topEntities = false; + bottomEntities = false; + if(line.find("TOP") == 0 || line.find("BOTH") == 0) + topEntities = true; + if(line.find("BOTTOM") == 0 || line.find("BOTH") == 0) + bottomEntities = true; + } + } else { // records 3+ are lines, arcs, and circles + std::vector values = splitString(line); + if(values.size() != 4) continue; + int c = stoi(values[0]); + double x = stof(values[1]); + double y = stof(values[2]); + double ang = stof(values[3]); + Vector point = Vector::From(x,y,0.0); + Vector pTop = Vector::From(x,y,board_thickness); + if(c != curve) { // start a new curve + curve = c; + if (bottomEntities) + hprev = newPoint(el, &entityCount, point, /*visible=*/false); + if (topEntities) + hprevTop = newPoint(el, &entityCount, pTop, /*visible=*/false); + pprev = point; + pprevTop = pTop; + } else { + if(section == board_outline) { + // create a bezier for the extrusion + if (ang == 0) { + // straight lines + SBezier sb = SBezier::From(pprev, point); + sbl.l.Add(&sb); + } else if (ang != 360.0) { + // Arcs + Vector c = ArcCenter(pprev, point, ang); + MakeBeziersForArcs(&sbl, c, pprev, point, normal, ang); + } else { + // circles + MakeBeziersForArcs(&sbl, point, pprev, pprev, normal, ang); + } + } + // next create the entities + // only curves and points at circle centers will be visible + bool vis = (ang == 360.0); + if (bottomEntities) { + hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis); + 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, (section == routing_keepout) ); + pprevTop = pTop; + hprevTop = hp; + } + } + } + break; + + case other_outline: + case routing_outline: + case placement_outline: + case via_keepout: + case placement_group: + break; + + 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]); + 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)) && !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, 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, 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); + } + + break; + } + case notes: + case component_placement: + break; + + default: + section = none; + break; + } + record_number++; + } + // now we can create an extrusion from all the Bezier curves. We can skip things + // like checking for a coplanar sketch because everything is at z=0. + SPolygon polyLoops = {}; + bool allClosed; + bool allCoplanar; + Vector errorPointAt = Vector::From(0,0,0); + SEdge errorAt = {}; + + SBezierLoopSetSet sblss = {}; + sblss.FindOuterFacesFrom(&sbl, &polyLoops, NULL, + 100.0, &allClosed, &errorAt, + &allCoplanar, &errorPointAt, NULL); + + //hack for when there is no sketch yet and the first group is a linked IDF + double ctc = SS.chordTolCalculated; + if(ctc == 0.0) SS.chordTolCalculated = 0.1; //mm + // there should only by one sbls in the sblss unless a board has disjointed parts... + sh->MakeFromExtrusionOf(sblss.l.First(), Vector::From(0.0, 0.0, 0.0), + Vector::From(0.0, 0.0, board_thickness), + RgbaColor::From(0, 180, 0) ); + SS.chordTolCalculated = ctc; + sblss.Clear(); + sbl.Clear(); + sh->booleanFailed = false; + + return true; +} + +} diff --git a/src/importmesh.cpp b/src/importmesh.cpp new file mode 100644 index 000000000..f06a8f1b9 --- /dev/null +++ b/src/importmesh.cpp @@ -0,0 +1,256 @@ +//----------------------------------------------------------------------------- +// 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 + +#define MIN_POINT_DISTANCE 0.001 + +// 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; +} + +namespace SolveSpace { + +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 + +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", &Slvs_SolveResult::result) + .field("dof", &Slvs_SolveResult::dof) + .field("rank", &Slvs_SolveResult::rank) + .field("bad", &Slvs_SolveResult::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("solveSketch", &Slvs_SolveSketch); + emscripten::function("clearSketch", &Slvs_ClearSketch); +} \ No newline at end of file diff --git a/src/lib.cpp b/src/lib.cpp index c16206b1b..4fcafbf21 100644 --- a/src/lib.cpp +++ b/src/lib.cpp @@ -5,14 +5,12 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" -#define EXPORT_DLL #include +#include Sketch SolveSpace::SK = {}; static System SYS; -static int IsInit = 0; - void SolveSpace::Platform::FatalError(const std::string &message) { fprintf(stderr, "%s", message.c_str()); abort(); @@ -24,6 +22,713 @@ void Group::GenerateEquations(IdList *) { extern "C" { +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: SolveSpace::Platform::FatalError("bad constraint type " + std::to_string(type)); + } +} + +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: SolveSpace::Platform::FatalError("bad entity type " + std::to_string(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)) { + SolveSpace::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)) { + SolveSpace::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)) { + SolveSpace::Platform::FatalError("workplane argument is not a workplane"); + } else if(!Slvs_IsPoint2D(ptA)) { + SolveSpace::Platform::FatalError("ptA argument is not a 2d point"); + } else if(!Slvs_IsPoint2D(ptB)) { + SolveSpace::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)) { + SolveSpace::Platform::FatalError("ptA argument is not a 3d point"); + } else if(!Slvs_IsPoint3D(ptB)) { + SolveSpace::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)) { + SolveSpace::Platform::FatalError("workplane argument is not a workplane"); + } else if(!Slvs_IsPoint2D(ptA)) { + SolveSpace::Platform::FatalError("ptA argument is not a 2d point"); + } else if(!Slvs_IsPoint2D(ptB)) { + SolveSpace::Platform::FatalError("ptB argument is not a 2d point"); + } else if(!Slvs_IsPoint2D(ptC)) { + SolveSpace::Platform::FatalError("ptC argument is not a 2d point"); + } else if(!Slvs_IsPoint2D(ptD)) { + SolveSpace::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)) { + SolveSpace::Platform::FatalError("workplane argument is not a workplane"); + } else if(!Slvs_IsNormal3D(normal)) { + SolveSpace::Platform::FatalError("normal argument is not a 3d normal"); + } else if(!Slvs_IsPoint2D(center)) { + SolveSpace::Platform::FatalError("center argument is not a 2d point"); + } else if(!Slvs_IsPoint2D(start)) { + SolveSpace::Platform::FatalError("start argument is not a 2d point"); + } else if(!Slvs_IsPoint2D(end)) { + SolveSpace::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)) { + SolveSpace::Platform::FatalError("workplane argument is not a workplane"); + } else if(!Slvs_IsNormal3D(normal)) { + SolveSpace::Platform::FatalError("normal argument is not a 3d normal"); + } else if(!Slvs_IsPoint2D(center)) { + SolveSpace::Platform::FatalError("center argument is not a 2d point"); + } else if(!Slvs_IsDistance(radius)) { + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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)) { + SolveSpace::Platform::FatalError("3d workplane given for a 2d constraint"); + } + return Slvs_AddConstraint(grouph, SLVS_C_SYMMETRIC_LINE, workplane, 0., entityA, entityB, entityC); + } + SolveSpace::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)) { + SolveSpace::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); + } + SolveSpace::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)) { + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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)) { + SolveSpace::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); + } + SolveSpace::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)) { + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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)) { + SolveSpace::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 { + SolveSpace::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 { + SolveSpace::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)) { + SolveSpace::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 { + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::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); + } + SolveSpace::Platform::FatalError("Invalid arguments for dragged constraint"); +} + void Slvs_QuaternionU(double qw, double qx, double qy, double qz, double *x, double *y, double *z) { @@ -67,13 +772,138 @@ void Slvs_MakeQuaternion(double ux, double uy, double uz, *qz = q.vz; } -void Slvs_Solve(Slvs_System *ssys, Slvs_hGroup shg) +void Slvs_ClearSketch() { - if(!IsInit) { - InitPlatform(0, NULL); - IsInit = 1; + SYS.Clear(); + SK.param.Clear(); + SK.entity.Clear(); + SK.constraint.Clear(); +} + +Slvs_SolveResult Slvs_SolveSketch(uint32_t shg, int calculateFaileds = 0) +{ + 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 + IdList constraintParams = {}; + for(ConstraintBase &con : SK.constraint) { + ConstraintBase *c = &con; + if(c->group.v != shg) + continue; + c->Generate(&constraintParams); + if(!constraintParams.IsEmpty()) { + for(Param &p : constraintParams) { + p.h = SK.param.AddAndAssignId(&p); + c->valP = p.h; + SYS.param.Add(&p); + } + constraintParams.Clear(); + c->ModifyToSatisfy(); + } + } + + // mark dragged params + for(ConstraintBase &con : SK.constraint) { + ConstraintBase *c = &con; + if(c->type == ConstraintBase::Type::WHERE_DRAGGED) { + EntityBase *e = SK.GetEntity(c->ptA); + SYS.dragged.Add(&(e->param[0])); + SYS.dragged.Add(&(e->param[1])); + if (e->type == EntityBase::Type::POINT_IN_3D) { + SYS.dragged.Add(&(e->param[2])); + } + } + } + + // 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 = calculateFaileds ? true : false; + + int rank = 0, dof = 0; + SolveResult status = SYS.Solve(&g, &rank, &dof, &badList, andFindBad, false, false); + Slvs_SolveResult sr = {}; + sr.rank = rank; + sr.dof = dof; + sr.bad = badList.n; + 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]); @@ -90,21 +920,7 @@ void Slvs_Solve(Slvs_System *ssys, Slvs_hGroup shg) 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.type = Slvs_CTypeToEntityBaseType(se->type); e.h.v = se->h; e.group.v = se->group; e.workplane.v = se->wrkpl; @@ -125,49 +941,7 @@ default: dbp("bad entity type %d", se->type); return; 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.type = Slvs_CTypeToConstraintBaseType(sc->type); c.h.v = sc->h; c.group.v = sc->group; c.workplane.v = sc->wrkpl; @@ -221,10 +995,13 @@ default: dbp("bad constraint type %d", sc->type); return; break; case SolveResult::REDUNDANT_DIDNT_CONVERGE: - case SolveResult::REDUNDANT_OKAY: 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; @@ -246,11 +1023,7 @@ default: dbp("bad constraint type %d", sc->type); return; } bad.Clear(); - SYS.param.Clear(); - SYS.entity.Clear(); - SYS.eq.Clear(); - SYS.dragged.Clear(); - + SYS.Clear(); SK.param.Clear(); SK.entity.Clear(); SK.constraint.Clear(); diff --git a/src/lib.pyx b/src/lib.pyx new file mode 100644 index 000000000..87e19ead9 --- /dev/null +++ b/src/lib.pyx @@ -0,0 +1,376 @@ +#cython: language_level=3 +from enum import IntEnum, auto + +cdef extern from "slvs.h" nogil: + ctypedef int Slvs_hEntity + ctypedef int Slvs_hGroup + ctypedef int Slvs_hConstraint + ctypedef int 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 rank + int bad + + 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) + + Slvs_SolveResult Slvs_SolveSketch(Slvs_hGroup hg, int calculateFaileds) 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 solve_sketch(grouph: int, calculateFaileds: bool): + return Slvs_SolveSketch(grouph, calculateFaileds) + +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/mesh.cpp b/src/mesh.cpp index 57aa87b47..1280a11a4 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -16,7 +16,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) { @@ -110,11 +110,11 @@ void SMesh::Simplify(int start) { STriMeta meta = l[start].meta; - STriangle *tout = (STriangle *)MemAlloc(maxTriangles*sizeof(*tout)); + STriangle *tout = new STriangle[maxTriangles]; int toutc = 0; Vector n = Vector::From(0, 0, 0); - Vector *conv = (Vector *)MemAlloc(maxTriangles*3*sizeof(*conv)); + Vector *conv = new Vector[maxTriangles * 3]; int convc = 0; int start0 = start; @@ -234,8 +234,8 @@ void SMesh::Simplify(int start) { for(i = 0; i < toutc; i++) { AddTriangle(&(tout[i])); } - MemFree(tout); - MemFree(conv); + delete[] tout; + delete[] conv; } void SMesh::AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3) { @@ -265,11 +265,12 @@ void SMesh::MakeFromUnionOf(SMesh *a, SMesh *b) { SBsp3 *bspb = SBsp3::FromMesh(b); flipNormal = false; - keepCoplanar = false; - AddAgainstBsp(b, bspa); + keepInsideOtherShell = false; - flipNormal = false; keepCoplanar = true; + AddAgainstBsp(b, bspa); + + keepCoplanar = false; AddAgainstBsp(a, bspb); } @@ -279,13 +280,29 @@ void SMesh::MakeFromDifferenceOf(SMesh *a, SMesh *b) { flipNormal = true; keepCoplanar = true; + keepInsideOtherShell = true; AddAgainstBsp(b, bspa); flipNormal = false; keepCoplanar = false; + keepInsideOtherShell = false; AddAgainstBsp(a, bspb); } +void SMesh::MakeFromIntersectionOf(SMesh *a, SMesh *b) { + SBsp3 *bspa = SBsp3::FromMesh(a); + SBsp3 *bspb = SBsp3::FromMesh(b); + + keepInsideOtherShell = true; + flipNormal = false; + + keepCoplanar = false; + AddAgainstBsp(a, bspb); + + keepCoplanar = true; + AddAgainstBsp(b, bspa); +} + void SMesh::MakeFromCopyOf(SMesh *a) { ssassert(this != a, "Can't make from copy of self"); for(int i = 0; i < a->l.n; i++) { @@ -954,9 +971,15 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, EdgeKind how, bool coplanarIs switch(how) { case EdgeKind::NAKED_OR_SELF_INTER: + // there should be one anti-parllel edge if(info.count != 1) { - sel->AddEdge(a, b, auxA); - if(leaky) *leaky = true; + // but there may be multiple parallel coincident edges + SKdNode::EdgeOnInfo parallelInfo = {}; + FindEdgeOn(b, a, -cnt, coplanarIsInter, ¶llelInfo); + if (info.count != parallelInfo.count) { + sel->AddEdge(a, b, auxA); + if(leaky) *leaky = true; + } } if(info.intersectsMesh) { sel->AddEdge(a, b, auxA); diff --git a/src/modify.cpp b/src/modify.cpp index 48c10957a..99cb48bbe 100644 --- a/src/modify.cpp +++ b/src/modify.cpp @@ -50,38 +50,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 +223,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; } } @@ -383,7 +381,7 @@ void GraphicsWindow::MakeTangentArc() { if(fabs(tp[0] - t[0]) > 1e-3 || fabs(tp[1] - t[1]) > 1e-3 || t[0] < 0.01 || t[1] < 0.01 || t[0] > 0.99 || t[1] > 0.99 || - isnan(t[0]) || isnan(t[1])) + IsReasonable(t[0]) || IsReasonable(t[1])) { Error(_("Couldn't round this corner. Try a smaller radius, or try " "creating the desired geometry by hand with tangency " diff --git a/src/mouse.cpp b/src/mouse.cpp index c0d1798e3..0826713d0 100644 --- a/src/mouse.cpp +++ b/src/mouse.cpp @@ -10,6 +10,8 @@ void GraphicsWindow::UpdateDraggedPoint(hEntity hp, double mx, double my) { Vector pos = p->PointGetNum(); UpdateDraggedNum(&pos, mx, my); p->PointForceTo(pos); + + SS.ScheduleShowTW(); } void GraphicsWindow::UpdateDraggedNum(Vector *pos, double mx, double my) { @@ -101,7 +103,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 +139,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 +194,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; } @@ -270,15 +267,16 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, return; } + if(pending.operation == Pending::DRAGGING_POINTS && ctrlDown) { + SS.extraLine.ptA = UnProjectPoint(orig.mouseOnButtonDown); + SS.extraLine.ptB = UnProjectPoint(mp); + SS.extraLine.draw = true; + } + // We're currently dragging something; so do that. But if we haven't // painted since the last time we solved, do nothing, because there's // no sense solving a frame and not displaying it. if(!havePainted) { - if(pending.operation == Pending::DRAGGING_POINTS && ctrlDown) { - SS.extraLine.ptA = UnProjectPoint(orig.mouseOnButtonDown); - SS.extraLine.ptB = UnProjectPoint(mp); - SS.extraLine.draw = true; - } return; } @@ -305,7 +303,6 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, HitTestMakeSelection(mp); SS.MarkGroupDirtyByEntity(pending.point); orig.mouse = mp; - Invalidate(); break; case Pending::DRAGGING_POINTS: @@ -320,20 +317,16 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, // Don't start dragging the position about the normal // until we're a little ways out, to get a reasonable // reference pos - orig.mouse = mp; - break; + qt = Quaternion::IDENTITY; + } else { + double theta = atan2(orig.mouse.y-orig.mouseOnButtonDown.y, + orig.mouse.x-orig.mouseOnButtonDown.x); + theta -= atan2(y-orig.mouseOnButtonDown.y, + x-orig.mouseOnButtonDown.x); + + Vector gn = projRight.Cross(projUp); + qt = Quaternion::From(gn, -theta); } - double theta = atan2(orig.mouse.y-orig.mouseOnButtonDown.y, - orig.mouse.x-orig.mouseOnButtonDown.x); - theta -= atan2(y-orig.mouseOnButtonDown.y, - x-orig.mouseOnButtonDown.x); - - Vector gn = projRight.Cross(projUp); - qt = Quaternion::From(gn, -theta); - - SS.extraLine.draw = true; - SS.extraLine.ptA = UnProjectPoint(orig.mouseOnButtonDown); - SS.extraLine.ptB = UnProjectPoint(mp); } else { double dx = -(x - orig.mouse.x); double dy = -(y - orig.mouse.y); @@ -341,7 +334,6 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, qt = Quaternion::From(projUp, -s*dx).Times( Quaternion::From(projRight, s*dy)); } - orig.mouse = mp; // Now apply this rotation to the points being dragged. List *lhe = &(pending.points); @@ -354,18 +346,18 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, p = qt.Rotate(p); p = p.Plus(SS.extraLine.ptA); e->PointForceTo(p); - SS.MarkGroupDirtyByEntity(e->h); + } else { + UpdateDraggedPoint(*he, x, y); } - continue; + } else { + Quaternion q = e->PointGetQuaternion(); + Vector p = e->PointGetNum(); + q = qt.Times(q); + e->PointForceQuaternionTo(q); + // Let's rotate about the selected point; so fix up the + // translation so that that point didn't move. + e->PointForceTo(p); } - - Quaternion q = e->PointGetQuaternion(); - Vector p = e->PointGetNum(); - q = qt.Times(q); - e->PointForceQuaternionTo(q); - // Let's rotate about the selected point; so fix up the - // translation so that that point didn't move. - e->PointForceTo(p); SS.MarkGroupDirtyByEntity(e->h); } } else { @@ -374,8 +366,8 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, UpdateDraggedPoint(*he, x, y); SS.MarkGroupDirtyByEntity(*he); } - orig.mouse = mp; } + orig.mouse = mp; break; case Pending::DRAGGING_NEW_CUBIC_POINT: { @@ -430,6 +422,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, SK.GetEntity(circle->distance)->DistanceForceTo(r); SS.MarkGroupDirtyByEntity(pending.circle); + SS.ScheduleShowTW(); break; } @@ -531,11 +524,16 @@ void GraphicsWindow::MouseRightUp(double x, double y) { } if(pending.operation == Pending::DRAGGING_NEW_LINE_POINT || - pending.operation == Pending::DRAGGING_NEW_CUBIC_POINT) + pending.operation == Pending::DRAGGING_NEW_CUBIC_POINT || + pending.operation == Pending::DRAGGING_NEW_ARC_POINT || + pending.operation == Pending::DRAGGING_NEW_RADIUS || + pending.operation == Pending::DRAGGING_NEW_POINT + ) { // Special case; use a right click to stop drawing lines, since // a left click would draw another one. This is quicker and more - // intuitive than hitting escape. Likewise for new cubic segments. + // intuitive than hitting escape. Likewise for other entities + // for consistency. ClearPending(); return; } @@ -563,25 +561,27 @@ void GraphicsWindow::MouseRightUp(double x, double y) { for(const Style &s : SK.style) { if(s.h.v < Style::FIRST_CUSTOM) continue; - styleMenu->AddItem(s.DescriptionString(), [&]() { - Style::AssignSelectionToStyle(s.h.v); + uint32_t v = s.h.v; + + styleMenu->AddItem(s.DescriptionString(), [v]() { + Style::AssignSelectionToStyle(v); }); empty = false; } if(!empty) styleMenu->AddSeparator(); - styleMenu->AddItem(_("No Style"), [&]() { + styleMenu->AddItem(_("No Style"), []() { Style::AssignSelectionToStyle(0); }); - styleMenu->AddItem(_("Newly Created Custom Style..."), [&]() { + styleMenu->AddItem(_("Newly Created Custom Style..."), [this]() { uint32_t vs = Style::CreateCustomStyle(); Style::AssignSelectionToStyle(vs); ForceTextWindowShown(); }); } if(gs.n + gs.constraints == 1) { - menu->AddItem(_("Group Info"), [&]() { + menu->AddItem(_("Group Info"), [this]() { hGroup hg; if(gs.entities == 1) { hg = SK.GetEntity(gs.entity[0])->group; @@ -601,7 +601,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { }); } if(gs.n + gs.constraints == 1 && gs.stylables == 1) { - menu->AddItem(_("Style Info"), [&]() { + menu->AddItem(_("Style Info"), [this]() { hStyle hs; if(gs.entities == 1) { hs = Style::ForEntity(gs.entity[0]); @@ -622,24 +622,24 @@ void GraphicsWindow::MouseRightUp(double x, double y) { } if(gs.withEndpoints > 0) { menu->AddItem(_("Select Edge Chain"), - [&]() { MenuEdit(Command::SELECT_CHAIN); }); + []() { MenuEdit(Command::SELECT_CHAIN); }); } if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->HasLabel() && c->type != Constraint::Type::COMMENT) { menu->AddItem(_("Toggle Reference Dimension"), - [&]() { Constraint::MenuConstrain(Command::REFERENCE); }); + []() { Constraint::MenuConstrain(Command::REFERENCE); }); } if(c->type == Constraint::Type::ANGLE || - c->type == Constraint::Type::EQUAL_ANGLE) + c->type == Constraint::Type::EQUAL_ANGLE) { menu->AddItem(_("Other Supplementary Angle"), - [&]() { Constraint::MenuConstrain(Command::OTHER_ANGLE); }); + []() { Constraint::MenuConstrain(Command::OTHER_ANGLE); }); } } if(gs.constraintLabels > 0 || gs.points > 0) { menu->AddItem(_("Snap to Grid"), - [&]() { MenuEdit(Command::SNAP_TO_GRID); }); + []() { MenuEdit(Command::SNAP_TO_GRID); }); } if(gs.points == 1 && gs.point[0].isFromRequest()) { @@ -647,7 +647,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { int index = r->IndexOfPoint(gs.point[0]); if((r->type == Request::Type::CUBIC && (index > 1 && index < r->extraPoints + 2)) || r->type == Request::Type::CUBIC_PERIODIC) { - menu->AddItem(_("Remove Spline Point"), [&]() { + menu->AddItem(_("Remove Spline Point"), [this, r]() { int index = r->IndexOfPoint(gs.point[0]); ssassert(r->extraPoints != 0, "Expected a bezier with interior control points"); @@ -682,7 +682,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { ssassert(addAfterPoint != -1, "Expected a nearest bezier point to be located"); // Skip derivative point. if(r->type == Request::Type::CUBIC) addAfterPoint++; - menu->AddItem(_("Add Spline Point"), [&]() { + menu->AddItem(_("Add Spline Point"), [this, r, addAfterPoint, v]() { int pointCount = r->extraPoints + ((r->type == Request::Type::CUBIC_PERIODIC) ? 3 : 4); if(pointCount >= MAX_POINTS_IN_ENTITY) { @@ -712,30 +712,30 @@ void GraphicsWindow::MouseRightUp(double x, double y) { } if(gs.entities == gs.n) { menu->AddItem(_("Toggle Construction"), - [&]() { MenuRequest(Command::CONSTRUCTION); }); + []() { MenuRequest(Command::CONSTRUCTION); }); } 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; } } if(c) { - menu->AddItem(_("Delete Point-Coincident Constraint"), [&]() { + menu->AddItem(_("Delete Point-Coincident Constraint"), [this, p]() { if(!p->IsPoint()) return; 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(); @@ -746,34 +746,34 @@ void GraphicsWindow::MouseRightUp(double x, double y) { menu->AddSeparator(); if(LockedInWorkplane()) { menu->AddItem(_("Cut"), - [&]() { MenuClipboard(Command::CUT); }); + []() { MenuClipboard(Command::CUT); }); menu->AddItem(_("Copy"), - [&]() { MenuClipboard(Command::COPY); }); + []() { MenuClipboard(Command::COPY); }); } } else { menu->AddItem(_("Select All"), - [&]() { MenuEdit(Command::SELECT_ALL); }); + []() { MenuEdit(Command::SELECT_ALL); }); } if((!SS.clipboard.r.IsEmpty() || !SS.clipboard.c.IsEmpty()) && LockedInWorkplane()) { menu->AddItem(_("Paste"), - [&]() { MenuClipboard(Command::PASTE); }); + []() { MenuClipboard(Command::PASTE); }); menu->AddItem(_("Paste Transformed..."), - [&]() { MenuClipboard(Command::PASTE_TRANSFORM); }); + []() { MenuClipboard(Command::PASTE_TRANSFORM); }); } if(itemsSelected) { menu->AddItem(_("Delete"), - [&]() { MenuClipboard(Command::DELETE); }); + []() { MenuClipboard(Command::DELETE); }); menu->AddSeparator(); menu->AddItem(_("Unselect All"), - [&]() { MenuEdit(Command::UNSELECT_ALL); }); + []() { MenuEdit(Command::UNSELECT_ALL); }); } // If only one item is selected, then it must be the one that we just // selected from the hovered item; in which case unselect all and hovered // are equivalent. if(!hover.IsEmpty() && selection.n > 1) { - menu->AddItem(_("Unselect Hovered"), [&] { + menu->AddItem(_("Unselect Hovered"), [this] { if(!hover.IsEmpty()) { MakeUnselected(&hover, /*coincidentPointTrick=*/true); } @@ -783,7 +783,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { if(itemsSelected) { menu->AddSeparator(); menu->AddItem(_("Zoom to Fit"), - [&]() { MenuView(Command::ZOOM_TO_FIT); }); + []() { MenuView(Command::ZOOM_TO_FIT); }); } menu->PopUp(); @@ -825,7 +825,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); @@ -875,7 +875,6 @@ bool GraphicsWindow::ConstrainPointByHovered(hEntity pt, const Point2d *projecte bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) { using Platform::MouseEvent; - double width, height; window->GetContentSize(&width, &height); @@ -916,7 +915,7 @@ bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) { break; case MouseEvent::Type::SCROLL_VERT: - this->MouseScroll(event.x, event.y, (int)event.scrollDelta); + this->MouseScroll(event.shiftDown ? event.scrollDelta / 10 : event.scrollDelta); break; case MouseEvent::Type::LEAVE: @@ -1042,6 +1041,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my, bool shiftDown, bool ct ConstrainPointByHovered(hr.entity(1), &mouse); ClearSuper(); + AddToPending(hr); pending.operation = Pending::DRAGGING_NEW_RADIUS; pending.circle = hr.entity(0); @@ -1115,7 +1115,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my, bool shiftDown, bool ct AddToPending(hr); Request *r = SK.GetRequest(hr); r->str = "Abc"; - r->font = "BitstreamVeraSans-Roman-builtin.ttf"; + r->font = Platform::embeddedFont; for(int i = 1; i <= 4; i++) { SK.GetEntity(hr.entity(i))->PointForceTo(v); @@ -1138,6 +1138,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); @@ -1309,15 +1310,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; @@ -1372,12 +1378,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 @@ -1433,7 +1439,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. @@ -1447,6 +1455,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()); @@ -1470,32 +1480,18 @@ void GraphicsWindow::EditControlDone(const std::string &s) { } } -void GraphicsWindow::MouseScroll(double x, double y, int delta) { - double offsetRight = offset.Dot(projRight); - double offsetUp = offset.Dot(projUp); - - double righti = x/scale - offsetRight; - double upi = y/scale - offsetUp; - - if(delta > 0) { - scale *= 1.2; - } else if(delta < 0) { - scale /= 1.2; - } else return; - - double rightf = x/scale - offsetRight; - double upf = y/scale - offsetUp; - - offset = offset.Plus(projRight.ScaledBy(rightf - righti)); - offset = offset.Plus(projUp.ScaledBy(upf - upi)); - - if(SS.TW.shown.screen == TextWindow::Screen::EDIT_VIEW) { - if(havePainted) { - SS.ScheduleShowTW(); - } - } - havePainted = false; - Invalidate(); +void GraphicsWindow::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) * 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 + // scale * e^a * e^b == scale * e^(a+b) + // while + // scale * a * b != scale * (a+b) + // So this constant is ln(1.2) = 0.1823216 to make the default zoom 1.2x + ZoomToMouse(zoomMultiplyer); } void GraphicsWindow::MouseLeave() { diff --git a/src/platform/entrycli.cpp b/src/platform/entrycli.cpp index c9efbf1ae..70b6919b0 100644 --- a/src/platform/entrycli.cpp +++ b/src/platform/entrycli.cpp @@ -4,6 +4,7 @@ // Copyright 2016 whitequark //----------------------------------------------------------------------------- #include "solvespace.h" +#include "config.h" static void ShowUsage(const std::string &cmd) { fprintf(stderr, "Usage: %s [filename...]", cmd.c_str()); @@ -25,14 +26,19 @@ Common options: piecewise linear, and exact surfaces into triangle meshes. For export commands, the unit is mm, and the default is 1.0 mm. For non-export commands, the unit is %%, and the default is 1.0 %%. + -b, --bg-color + Whether to export the background colour in vector formats. Defaults to off. Commands: + version + Prints the current solvespace version. thumbnail --output --size --view [--chord-tol ] Outputs a rendered view of the sketch, like the SolveSpace GUI would. is x, in pixels. Graphics acceleration is not used, and the output may look slightly different from the GUI. export-view --output --view [--chord-tol ] + [--bg-color ] Exports a view of the sketch, in a 2d vector format. export-wireframe --output [--chord-tol ] Exports a wireframe of the sketch, in a 3d vector format. @@ -118,22 +124,22 @@ static bool RunCommand(const std::vector args) { argn++; if(args[argn] == "top") { projRight = Vector::From(1, 0, 0); - projUp = Vector::From(0, 1, 0); + projUp = Vector::From(0, 0, -1); } else if(args[argn] == "bottom") { - projRight = Vector::From(-1, 0, 0); - projUp = Vector::From(0, 1, 0); - } else if(args[argn] == "left") { - projRight = Vector::From(0, 1, 0); + projRight = Vector::From(1, 0, 0); projUp = Vector::From(0, 0, 1); + } else if(args[argn] == "left") { + projRight = Vector::From(0, 0, 1); + projUp = Vector::From(0, 1, 0); } else if(args[argn] == "right") { - projRight = Vector::From(0, -1, 0); - projUp = Vector::From(0, 0, 1); + projRight = Vector::From(0, 0, -1); + projUp = Vector::From(0, 1, 0); } else if(args[argn] == "front") { - projRight = Vector::From(-1, 0, 0); - projUp = Vector::From(0, 0, 1); - } else if(args[argn] == "back") { projRight = Vector::From(1, 0, 0); - projUp = Vector::From(0, 0, 1); + projUp = Vector::From(0, 1, 0); + } else if(args[argn] == "back") { + projRight = Vector::From(-1, 0, 0); + projUp = Vector::From(0, 1, 0); } else if(args[argn] == "isometric") { projRight = Vector::From(0.707, 0.000, -0.707); projUp = Vector::From(-0.408, 0.816, -0.408); @@ -155,8 +161,26 @@ static bool RunCommand(const std::vector args) { } else return false; }; + bool bg_color = false; + auto ParseBgColor = [&](size_t &argn) { + if(argn + 1 < args.size() && (args[argn] == "--bg-color" || + args[argn] == "-b")) { + argn++; + if(args[argn] == "on") { + bg_color = true; + return true; + } else if(args[argn] == "off") { + bg_color = false; + return true; + } else return false; + } else return false; + }; + unsigned width = 0, height = 0; - if(args[1] == "thumbnail") { + if(args[1] == "version") { + fprintf(stderr, "SolveSpace version %s \n\n", PACKAGE_VERSION); + return false; + } else if(args[1] == "thumbnail") { auto ParseSize = [&](size_t &argn) { if(argn + 1 < args.size() && args[argn] == "--size") { argn++; @@ -193,13 +217,14 @@ static bool RunCommand(const std::vector args) { camera.gridFit = true; camera.width = width; camera.height = height; - camera.projUp = SS.GW.projUp; - camera.projRight = SS.GW.projRight; + camera.projUp = projUp; + camera.projRight = projRight; SS.GW.projUp = projUp; SS.GW.projRight = projRight; SS.GW.scale = SS.GW.ZoomToFit(camera); camera.scale = SS.GW.scale; + camera.offset = SS.GW.offset; SS.GenerateAll(); CairoPixmapRenderer pixmapCanvas; @@ -221,7 +246,8 @@ static bool RunCommand(const std::vector args) { if(!(ParseInputFile(argn) || ParseOutputPattern(argn) || ParseViewDirection(argn) || - ParseChordTolerance(argn))) { + ParseChordTolerance(argn) || + ParseBgColor(argn))) { fprintf(stderr, "Unrecognized option '%s'.\n", args[argn].c_str()); return false; } @@ -233,9 +259,10 @@ static bool RunCommand(const std::vector args) { } runner = [&](const Platform::Path &output) { - SS.GW.projRight = projRight; - SS.GW.projUp = projUp; - SS.exportChordTol = chordTol; + SS.GW.projRight = projRight; + SS.GW.projUp = projUp; + SS.exportChordTol = chordTol; + SS.exportBackgroundColor = bg_color; SS.ExportViewOrWireframeTo(output, /*exportWireframe=*/false); }; @@ -351,7 +378,7 @@ static bool RunCommand(const std::vector args) { } int main(int argc, char **argv) { - std::vector args = InitPlatform(argc, argv); + std::vector args = Platform::InitCli(argc, argv); if(args.size() == 1) { ShowUsage(args[0]); diff --git a/src/platform/entrygui.cpp b/src/platform/entrygui.cpp index 654d25eac..397831acb 100644 --- a/src/platform/entrygui.cpp +++ b/src/platform/entrygui.cpp @@ -11,9 +11,8 @@ using namespace SolveSpace; int main(int argc, char** argv) { - std::vector args = InitPlatform(argc, argv); + std::vector args = Platform::InitGui(argc, argv); - Platform::InitGui(argc, argv); Platform::Open3DConnexion(); SS.Init(); @@ -22,7 +21,7 @@ int main(int argc, char** argv) { dbp("Only the first file passed on command line will be opened."); } - SS.Load(Platform::Path::From(args.back()).Expand(/*fromCurrentDirectory=*/true)); + SS.Load(Platform::Path::From(args.back())); } Platform::RunGui(); diff --git a/src/platform/gui.cpp b/src/platform/gui.cpp index b86d2ecd1..0c9637ff4 100644 --- a/src/platform/gui.cpp +++ b/src/platform/gui.cpp @@ -85,6 +85,13 @@ std::vector SolveSpaceModelFileFilters = { { CN_("file-type", "SolveSpace models"), { "slvs" } }, }; +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 = { { CN_("file-type", "PNG image"), { "png" } }, }; @@ -94,7 +101,6 @@ std::vector MeshFileFilters = { { CN_("file-type", "Wavefront OBJ mesh"), { "obj" } }, { CN_("file-type", "Three.js-compatible mesh, with viewer"), { "html" } }, { CN_("file-type", "Three.js-compatible mesh, mesh only"), { "js" } }, - { CN_("file-type", "Q3D Object file"), { "q3do" } }, { CN_("file-type", "VRML text file"), { "wrl" } }, }; diff --git a/src/platform/gui.h b/src/platform/gui.h index c5d082c17..4f3d1316b 100644 --- a/src/platform/gui.h +++ b/src/platform/gui.h @@ -7,6 +7,9 @@ #ifndef SOLVESPACE_GUI_H #define SOLVESPACE_GUI_H +namespace SolveSpace { +class RgbaColor; + namespace Platform { //----------------------------------------------------------------------------- @@ -99,9 +102,7 @@ std::string AcceleratorDescription(const KeyboardEvent &accel); //----------------------------------------------------------------------------- // Handling fatal errors. -#if defined(__GNUC__) -__attribute__((noreturn)) -#endif +[[noreturn]] void FatalError(const std::string &message); // A native settings store. @@ -220,6 +221,7 @@ class Window { std::function onKeyboardEvent; std::function onEditingDone; std::function onScrollbarAdjusted; + std::function onContextLost; std::function onRender; virtual ~Window() = default; @@ -228,7 +230,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; @@ -329,6 +331,8 @@ struct FileFilter { // SolveSpace's native file format extern std::vector SolveSpaceModelFileFilters; +// SolveSpace's linkable file formats +extern std::vector SolveSpaceLinkFileFilters; // Raster image extern std::vector RasterFileFilters; // Triangle mesh @@ -354,6 +358,7 @@ class FileDialog { virtual Platform::Path GetFilename() = 0; virtual void SetFilename(Platform::Path path) = 0; + virtual void SuggestFilename(Platform::Path path) = 0; virtual void AddFilter(std::string name, std::vector extensions) = 0; void AddFilter(const FileFilter &filter); @@ -377,11 +382,12 @@ FileDialogRef CreateSaveFileDialog(WindowRef parentWindow); std::vector GetFontFiles(); void OpenInBrowser(const std::string &url); -void InitGui(int argc, char **argv); +std::vector InitGui(int argc, char **argv); void RunGui(); void ExitGui(); void ClearGui(); } +} // namespace SolveSpace #endif diff --git a/src/platform/guigtk.cpp b/src/platform/guigtk.cpp index 73fb01ec1..8e2d38b7b 100644 --- a/src/platform/guigtk.cpp +++ b/src/platform/guigtk.cpp @@ -33,7 +33,18 @@ #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 +# include +# endif #endif #include "solvespace.h" @@ -217,6 +228,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); } }; @@ -452,6 +467,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); @@ -472,7 +489,7 @@ class GtkGLWidget : public Gtk::GLArea { } bool process_pointer_event(MouseEvent::Type type, double x, double y, - guint state, guint button = 0, int scroll_delta = 0) { + guint state, guint button = 0, double scroll_delta = 0) { MouseEvent event = {}; event.type = type; event.x = x; @@ -502,8 +519,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); @@ -511,51 +532,84 @@ 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); } - - if(process_pointer_event(type, 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(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 { - int 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; + double dx, dy; + GdkScrollDirection dir; + double delta; + +// 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); @@ -565,21 +619,28 @@ class GtkGLWidget : public Gtk::GLArea { KeyboardEvent event = {}; event.type = type; - if(gdk_event->state & ~(GDK_SHIFT_MASK|GDK_CONTROL_MASK)) { + GdkModifierType state; + gdk_event_get_state((GdkEvent*)gdk_event, &state); + + Gdk::ModifierType mod_mask = get_modifier_mask(Gdk::MODIFIER_INTENT_DEFAULT_MOD_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; } @@ -681,8 +742,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); @@ -825,7 +889,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); } @@ -873,7 +941,7 @@ class WindowImplGtk final : public Window { return gtkWindow.get_screen()->get_resolution(); } - int GetDevicePixelRatio() override { + double GetDevicePixelRatio() override { return gtkWindow.get_scale_factor(); } @@ -962,16 +1030,17 @@ class WindowImplGtk final : public Window { } void SetCursor(Cursor cursor) override { - Gdk::CursorType gdkCursorType; + std::string cursor_name; switch(cursor) { - case Cursor::POINTER: gdkCursorType = Gdk::ARROW; break; - case Cursor::HAND: gdkCursorType = Gdk::HAND1; break; + case Cursor::POINTER: cursor_name = "default"; break; + case Cursor::HAND: cursor_name = "pointer"; break; default: ssassert(false, "Unexpected cursor"); } auto gdkWindow = gtkWindow.get_gl_widget().get_window(); if(gdkWindow) { - gdkWindow->set_cursor(Gdk::Cursor::create(gdkCursorType)); + gdkWindow->set_cursor(Gdk::Cursor::create(gdkWindow->get_display(), cursor_name.c_str())); +// gdkWindow->get_display() } } @@ -1036,16 +1105,8 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { void Open3DConnexion() {} void Close3DConnexion() {} -#if defined(HAVE_SPACEWARE) && defined(GDK_WINDOWING_X11) -static GdkFilterReturn GdkSpnavFilter(GdkXEvent *gdkXEvent, GdkEvent *gdkEvent, gpointer data) { - XEvent *xEvent = (XEvent *)gdkXEvent; - WindowImplGtk *window = (WindowImplGtk *)data; - - spnav_event spnavEvent; - if(!spnav_x11_event(xEvent, &spnavEvent)) { - return GDK_FILTER_CONTINUE; - } - +#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: { SixDofEvent event = {}; @@ -1056,8 +1117,8 @@ static GdkFilterReturn GdkSpnavFilter(GdkXEvent *gdkXEvent, GdkEvent *gdkEvent, event.rotationX = (double)spnavEvent.motion.rx * 0.001; event.rotationY = (double)spnavEvent.motion.ry * 0.001; event.rotationZ = (double)spnavEvent.motion.rz * -0.001; - event.shiftDown = xEvent->xmotion.state & ShiftMask; - event.controlDown = xEvent->xmotion.state & ControlMask; + event.shiftDown = shiftDown; + event.controlDown = controlDown; if(window->onSixDofEvent) { window->onSixDofEvent(event); } @@ -1073,17 +1134,52 @@ static GdkFilterReturn GdkSpnavFilter(GdkXEvent *gdkXEvent, GdkEvent *gdkEvent, } switch(spnavEvent.button.bnum) { case 0: event.button = SixDofEvent::Button::FIT; break; - default: return GDK_FILTER_REMOVE; + default: return; } - event.shiftDown = xEvent->xmotion.state & ShiftMask; - event.controlDown = xEvent->xmotion.state & ControlMask; + event.shiftDown = shiftDown; + event.controlDown = controlDown; if(window->onSixDofEvent) { window->onSixDofEvent(event); } break; } +} + +static GdkFilterReturn GdkSpnavFilter(GdkXEvent *gdkXEvent, GdkEvent *gdkEvent, gpointer data) { + XEvent *xEvent = (XEvent *)gdkXEvent; + WindowImplGtk *window = (WindowImplGtk *)data; + bool shiftDown = (xEvent->xmotion.state & ShiftMask) != 0; + bool controlDown = (xEvent->xmotion.state & ControlMask) != 0; + + spnav_event spnavEvent; + if(spnav_x11_event(xEvent, &spnavEvent)) { + ProcessSpnavEvent(window, spnavEvent, shiftDown, controlDown); + return GDK_FILTER_REMOVE; + } + return GDK_FILTER_CONTINUE; +} + +static gboolean ConsumeSpnavQueue(GIOChannel *, GIOCondition, gpointer data) { + WindowImplGtk *window = (WindowImplGtk *)data; + Glib::RefPtr gdkWindow = window->gtkWindow.get_window(); + + // We don't get modifier state through the socket. + int x, y; + Gdk::ModifierType mask{}; +#if GTK_CHECK_VERSION(3, 20, 0) + Glib::RefPtr device = gdkWindow->get_display()->get_default_seat()->get_pointer(); +#else + Glib::RefPtr device = gdkWindow->get_display()->get_device_manager()->get_client_pointer(); +#endif + gdkWindow->get_device_position(device, x, y, mask); + bool shiftDown = (mask & Gdk::SHIFT_MASK) != 0; + bool controlDown = (mask & Gdk::CONTROL_MASK) != 0; - return GDK_FILTER_REMOVE; + spnav_event spnavEvent; + while(spnav_poll_event(&spnavEvent)) { + ProcessSpnavEvent(window, spnavEvent, shiftDown, controlDown); + } + return TRUE; } void Request3DConnexionEventsForWindow(WindowRef window) { @@ -1091,11 +1187,26 @@ void Request3DConnexionEventsForWindow(WindowRef window) { std::static_pointer_cast(window); Glib::RefPtr gdkWindow = windowImpl->gtkWindow.get_window(); +#if defined(GDK_WINDOWING_X11) if(GDK_IS_X11_DISPLAY(gdkWindow->get_display()->gobj())) { - gdkWindow->add_filter(GdkSpnavFilter, windowImpl.get()); - spnav_x11_open(gdk_x11_get_default_xdisplay(), - gdk_x11_window_get_xid(gdkWindow->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()); + } } +#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) {} @@ -1245,6 +1356,10 @@ class FileDialogImplGtk : public FileDialog { gtkChooser->set_filename(path.raw); } + void SuggestFilename(Platform::Path path) override { + gtkChooser->set_current_name(path.FileStem()+"."+GetExtension()); + } + void AddFilter(std::string name, std::vector extensions) override { Glib::RefPtr gtkFilter = Gtk::FileFilter::create(); Glib::ustring desc; @@ -1290,13 +1405,16 @@ class FileDialogImplGtk : public FileDialog { } } +//TODO: This is not getting called when the extension selection is changed. void FilterChanged() { std::string extension = GetExtension(); if(extension.empty()) return; Platform::Path path = GetFilename(); - SetCurrentName(path.WithExtension(extension).FileName()); + if(gtkChooser->get_action() != Gtk::FILE_CHOOSER_ACTION_OPEN) { + SetCurrentName(path.WithExtension(extension).FileName()); + } } void FreezeChoices(SettingsRef settings, const std::string &key) override { @@ -1332,6 +1450,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); } @@ -1365,6 +1486,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->()); } @@ -1427,12 +1551,13 @@ 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; -void InitGui(int argc, char **argv) { +std::vector InitGui(int argc, char **argv) { // It would in principle be possible to judiciously use Glib::filename_{from,to}_utf8, // but it's not really worth the effort. // The setlocale() call is necessary for Glib::get_charset() to detect the system @@ -1445,8 +1570,12 @@ void InitGui(int argc, char **argv) { } setlocale(LC_ALL, "C"); + // Let GTK parse arguments and update argc/argv. (They're passed by reference.) gtkMain = new Gtk::Main(argc, argv, /*set_locale=*/false); + // Now that GTK arguments are removed, grab arguments for ourselves. + std::vector args = InitCli(argc, argv); + // Add our application-specific styles, to override GTK defaults. Glib::RefPtr style_provider = Gtk::CssProvider::create(); style_provider->load_from_data(R"( @@ -1468,6 +1597,8 @@ void InitGui(int argc, char **argv) { if(!*langNames) { SetLocale("en_US"); } + + return args; } void RunGui() { diff --git a/src/platform/guigtk4.cpp b/src/platform/guigtk4.cpp new file mode 100644 index 000000000..43f2acb9c --- /dev/null +++ b/src/platform/guigtk4.cpp @@ -0,0 +1,1706 @@ +//----------------------------------------------------------------------------- +// +// Loosely based on guigtk.cpp by whitequark +// Commonwealth copyright Erkin Alp Güney 2025 +// Human involvement below copyrightability threshold outside the commonwealth +//----------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#if defined(HAVE_GTK_FILECHOOSERNATIVE) +# include +#endif + +#if defined(HAVE_SPACEWARE) +# include +# include +# if defined(GDK_WINDOWING_WAYLAND) +# include +# endif +# if GTK_CHECK_VERSION(3, 20, 0) +# include +# else +# include +# endif +#endif + +#include "solvespace.h" + +namespace SolveSpace { +namespace Platform { + +//----------------------------------------------------------------------------- +// Utility functions +//----------------------------------------------------------------------------- + +static std::string PrepareMnemonics(std::string label) { + std::replace(label.begin(), label.end(), '&', '_'); + return label; +} + +static std::string PrepareTitle(const std::string &title) { + return title + " — SolveSpace"; +} + +//----------------------------------------------------------------------------- +// Fatal errors +//----------------------------------------------------------------------------- + +void FatalError(const std::string &message) { + fprintf(stderr, "%s", message.c_str()); + abort(); +} + +//----------------------------------------------------------------------------- +// Settings +//----------------------------------------------------------------------------- + +class SettingsImplGtk final : public Settings { +public: + // Why aren't we using GSettings? Two reasons. It doesn't allow to easily see whether + // the setting had the default value, and it requires to install a schema globally. + Path _path; + json_object *_json = NULL; + + static Path GetConfigPath() { + Path configHome; + if(getenv("XDG_CONFIG_HOME")) { + configHome = Path::From(getenv("XDG_CONFIG_HOME")); + } else if(getenv("HOME")) { + configHome = Path::From(getenv("HOME")).Join(".config"); + } else { + dbp("neither XDG_CONFIG_HOME nor HOME are set"); + return Path::From(""); + } + if(!configHome.IsEmpty()) { + configHome = configHome.Join("solvespace"); + } + + const char *configHomeC = configHome.raw.c_str(); + struct stat st; + if(stat(configHomeC, &st)) { + if(errno == ENOENT) { + if(mkdir(configHomeC, 0777)) { + dbp("cannot mkdir %s: %s", configHomeC, strerror(errno)); + return Path::From(""); + } + } else { + dbp("cannot stat %s: %s", configHomeC, strerror(errno)); + return Path::From(""); + } + } else if(!S_ISDIR(st.st_mode)) { + dbp("%s is not a directory", configHomeC); + return Path::From(""); + } + + return configHome.Join("settings.json"); + } + + SettingsImplGtk() { + _path = GetConfigPath(); + if(_path.IsEmpty()) { + dbp("settings will not be saved"); + } else { + _json = json_object_from_file(_path.raw.c_str()); + if(!_json && errno != ENOENT) { + dbp("cannot load settings: %s", strerror(errno)); + } + } + + if(_json == NULL) { + _json = json_object_new_object(); + } + } + + ~SettingsImplGtk() override { + if(!_path.IsEmpty()) { + // json-c <0.12 has the first argument non-const + if(json_object_to_file_ext((char *)_path.raw.c_str(), _json, + JSON_C_TO_STRING_PRETTY)) { + dbp("cannot save settings: %s", strerror(errno)); + } + } + + json_object_put(_json); + } + + void FreezeInt(const std::string &key, uint32_t value) override { + struct json_object *jsonValue = json_object_new_int(value); + json_object_object_add(_json, key.c_str(), jsonValue); + } + + uint32_t ThawInt(const std::string &key, uint32_t defaultValue) override { + struct json_object *jsonValue; + if(json_object_object_get_ex(_json, key.c_str(), &jsonValue)) { + return json_object_get_int(jsonValue); + } + return defaultValue; + } + + void FreezeBool(const std::string &key, bool value) override { + struct json_object *jsonValue = json_object_new_boolean(value); + json_object_object_add(_json, key.c_str(), jsonValue); + } + + bool ThawBool(const std::string &key, bool defaultValue) override { + struct json_object *jsonValue; + if(json_object_object_get_ex(_json, key.c_str(), &jsonValue)) { + return json_object_get_boolean(jsonValue); + } + return defaultValue; + } + + void FreezeFloat(const std::string &key, double value) override { + struct json_object *jsonValue = json_object_new_double(value); + json_object_object_add(_json, key.c_str(), jsonValue); + } + + double ThawFloat(const std::string &key, double defaultValue) override { + struct json_object *jsonValue; + if(json_object_object_get_ex(_json, key.c_str(), &jsonValue)) { + return json_object_get_double(jsonValue); + } + return defaultValue; + } + + void FreezeString(const std::string &key, const std::string &value) override { + struct json_object *jsonValue = json_object_new_string(value.c_str()); + json_object_object_add(_json, key.c_str(), jsonValue); + } + + std::string ThawString(const std::string &key, + const std::string &defaultValue = "") override { + struct json_object *jsonValue; + if(json_object_object_get_ex(_json, key.c_str(), &jsonValue)) { + return json_object_get_string(jsonValue); + } + return defaultValue; + } +}; + +SettingsRef GetSettings() { + static std::shared_ptr settings; + if(!settings) { + settings = std::make_shared(); + } + return settings; +} + +//----------------------------------------------------------------------------- +// Timers +//----------------------------------------------------------------------------- + +class TimerImplGtk final : public Timer { +public: + sigc::connection _connection; + + void RunAfter(unsigned milliseconds) override { + if(!_connection.empty()) { + _connection.disconnect(); + } + + auto handler = [this]() -> bool { + if(this->onTimeout) { + this->onTimeout(); + } + 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); + } +}; + +TimerRef CreateTimer() { + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// GTK menu extensions +//----------------------------------------------------------------------------- + +class GtkMenuItem : public Gtk::CheckButton { + Platform::MenuItem *_receiver; + bool _has_indicator; + bool _synthetic_event; + sigc::connection _activate_connection; + +public: + GtkMenuItem(Platform::MenuItem *receiver) : + _receiver(receiver), _has_indicator(false), _synthetic_event(false) { + + _activate_connection = signal_toggled().connect( + [this]() { + if(!_synthetic_event && _receiver->onTrigger) { + _receiver->onTrigger(); + } + }, false); + } + + void set_accel_key(const Gtk::AccelKey &accel_key) { + } + + bool has_indicator() const { + return _has_indicator; + } + + void set_has_indicator(bool has_indicator) { + _has_indicator = has_indicator; + } + + void set_active(bool active) { + if(get_active() == active) + return; + + _synthetic_event = true; + Gtk::CheckButton::set_active(active); + _synthetic_event = false; + } +}; + +//----------------------------------------------------------------------------- +// Menus +//----------------------------------------------------------------------------- + +class MenuItemImplGtk final : public MenuItem { +public: + GtkMenuItem gtkMenuItem; + std::string actionName; // Add actionName member for GTK4 compatibility + std::function onTrigger; + + MenuItemImplGtk() : gtkMenuItem(this) {} + + void SetAccelerator(KeyboardEvent accel) override { + guint accelKey = 0; + if(accel.key == KeyboardEvent::Key::CHARACTER) { + if(accel.chr == '\t') { + accelKey = GDK_KEY_Tab; + } else if(accel.chr == '\x1b') { + accelKey = GDK_KEY_Escape; + } else if(accel.chr == '\x7f') { + accelKey = GDK_KEY_Delete; + } else { + accelKey = gdk_unicode_to_keyval(accel.chr); + } + } else if(accel.key == KeyboardEvent::Key::FUNCTION) { + accelKey = GDK_KEY_F1 + accel.num - 1; + } + + Gdk::ModifierType accelMods = {}; + if(accel.shiftDown) { + accelMods |= Gdk::ModifierType::SHIFT_MASK; + } + if(accel.controlDown) { + accelMods |= Gdk::ModifierType::CONTROL_MASK; + } + + gtkMenuItem.set_accel_key(Gtk::AccelKey(accelKey, accelMods)); + } + + void SetIndicator(Indicator type) override { + switch(type) { + case Indicator::NONE: + gtkMenuItem.set_has_indicator(false); + break; + + case Indicator::CHECK_MARK: + gtkMenuItem.set_has_indicator(true); + break; + + case Indicator::RADIO_MARK: + gtkMenuItem.set_has_indicator(true); + break; + } + } + + void SetActive(bool active) override { + ssassert(gtkMenuItem.has_indicator(), + "Cannot change state of a menu item without indicator"); + gtkMenuItem.set_active(active); + } + + void SetEnabled(bool enabled) override { + gtkMenuItem.set_sensitive(enabled); + } +}; + +class MenuImplGtk final : public Menu { +public: + Glib::RefPtr gioMenu; + Gtk::Popover gtkMenu; + std::vector> menuItems; + std::vector> subMenus; + + MenuImplGtk() { + gioMenu = Gio::Menu::create(); + auto menuBox = Gtk::make_managed(Gtk::Orientation::VERTICAL); + gtkMenu.set_child(*menuBox); + } + + MenuItemRef AddItem(const std::string &label, + std::function onTrigger = NULL, + bool mnemonics = true) override { + auto menuItem = std::make_shared(); + menuItems.push_back(menuItem); + + std::string itemLabel = mnemonics ? PrepareMnemonics(label) : label; + + std::string actionName = "app.action" + std::to_string(menuItems.size()); + + auto gioMenuItem = Gio::MenuItem::create(itemLabel, actionName); + gioMenu->append_item(gioMenuItem); + + menuItem->actionName = actionName; + menuItem->onTrigger = onTrigger; + + return menuItem; + } + + MenuRef AddSubMenu(const std::string &label) override { + auto subMenu = std::make_shared(); + subMenus.push_back(subMenu); + + std::string itemLabel = PrepareMnemonics(label); + + gioMenu->append_submenu(itemLabel, subMenu->gioMenu); + + return subMenu; + } + + void AddSeparator() override { + auto section = Gio::Menu::create(); + gioMenu->append_section("", section); + } + + void PopUp() override { + gtkMenu.set_visible(true); + + Glib::RefPtr loop = Glib::MainLoop::create(); + auto signal = gtkMenu.signal_closed().connect([&]() { + loop->quit(); + }); + + loop->run(); + signal.disconnect(); + } + + void Clear() override { + while (gioMenu->get_n_items() > 0) { + gioMenu->remove(0); + } + + menuItems.clear(); + subMenus.clear(); + } +}; + +MenuRef CreateMenu() { + return std::make_shared(); +} + +class MenuBarImplGtk final : public MenuBar { +public: + Glib::RefPtr gioMenuBar; + Gtk::Box gtkMenuBar; + std::vector> subMenus; + + MenuBarImplGtk() : gtkMenuBar(Gtk::Orientation::HORIZONTAL) { + gioMenuBar = Gio::Menu::create(); + } + + MenuRef AddSubMenu(const std::string &label) override { + auto subMenu = std::make_shared(); + subMenus.push_back(subMenu); + + std::string itemLabel = PrepareMnemonics(label); + + gioMenuBar->append_submenu(itemLabel, subMenu->gioMenu); + + return subMenu; + } + + void Clear() override { + while (gioMenuBar->get_n_items() > 0) { + gioMenuBar->remove(0); + } + + subMenus.clear(); + } +}; + +MenuBarRef GetOrCreateMainMenu(bool *unique) { + *unique = false; + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// GTK GL and window extensions +//----------------------------------------------------------------------------- + +class GtkGLWidget : public Gtk::GLArea { + Window *_receiver; + +public: + GtkGLWidget(Platform::Window *receiver) : _receiver(receiver) { + set_has_depth_buffer(true); + set_can_focus(true); + setup_event_controllers(); + } + +protected: + // Work around a bug fixed in GTKMM 3.22: + // https://mail.gnome.org/archives/gtkmm-list/2016-April/msg00020.html + Glib::RefPtr on_create_context() override { + return get_native()->get_surface()->create_gl_context(); + } + + bool on_render(const Glib::RefPtr &context) override { + if(_receiver->onRender) { + _receiver->onRender(); + } + return true; + } + + bool process_pointer_event(MouseEvent::Type type, double x, double y, + GdkModifierType state, guint button = 0, double scroll_delta = 0) { + MouseEvent event = {}; + event.type = type; + event.x = x; + event.y = y; + if(button == 1 || (state & GDK_BUTTON1_MASK) != 0) { + event.button = MouseEvent::Button::LEFT; + } else if(button == 2 || (state & GDK_BUTTON2_MASK) != 0) { + event.button = MouseEvent::Button::MIDDLE; + } else if(button == 3 || (state & GDK_BUTTON3_MASK) != 0) { + event.button = MouseEvent::Button::RIGHT; + } + if((state & GDK_SHIFT_MASK) != 0) { + event.shiftDown = true; + } + if((state & GDK_CONTROL_MASK) != 0) { + event.controlDown = true; + } + if(scroll_delta != 0) { + event.scrollDelta = scroll_delta; + } + + if(_receiver->onMouseEvent) { + return _receiver->onMouseEvent(event); + } + + return false; + } + + bool process_key_event(KeyboardEvent::Type type, guint keyval, GdkModifierType state) { + KeyboardEvent event = {}; + event.type = type; + + if((state & (GDK_MODIFIER_MASK)) & ~(GDK_SHIFT_MASK|GDK_CONTROL_MASK)) { + return false; + } + + event.shiftDown = (state & GDK_SHIFT_MASK) != 0; + event.controlDown = (state & GDK_CONTROL_MASK) != 0; + + 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(keyval >= GDK_KEY_F1 && + keyval <= GDK_KEY_F12) { + event.key = KeyboardEvent::Key::FUNCTION; + event.num = keyval - GDK_KEY_F1 + 1; + } else { + return false; + } + + if(_receiver->onKeyboardEvent) { + return _receiver->onKeyboardEvent(event); + } + + return false; + } + + void setup_event_controllers() { + auto motion_controller = Gtk::EventControllerMotion::create(); + motion_controller->signal_motion().connect( + [this](double x, double y) { + GdkModifierType state = GdkModifierType(0); + process_pointer_event(MouseEvent::Type::MOTION, x, y, state); + return true; + }, false); + motion_controller->signal_leave().connect( + [this]() { + double x, y; + get_pointer_position(x, y); + process_pointer_event(MouseEvent::Type::LEAVE, x, y, GdkModifierType(0)); + return true; + }, false); + add_controller(motion_controller); + + auto gesture_click = Gtk::GestureClick::create(); + gesture_click->set_button(0); // Listen for any button + gesture_click->signal_pressed().connect( + [this, gesture_click](int n_press, double x, double y) { + GdkModifierType state = GdkModifierType(0); + guint button = gesture_click->get_current_button(); + process_pointer_event( + n_press > 1 ? MouseEvent::Type::DBL_PRESS : MouseEvent::Type::PRESS, + x, y, state, button); + return true; + }, false); + gesture_click->signal_released().connect( + [this, gesture_click](int n_press, double x, double y) { + GdkModifierType state = GdkModifierType(0); + guint button = gesture_click->get_current_button(); + process_pointer_event(MouseEvent::Type::RELEASE, x, y, state, button); + return true; + }, false); + add_controller(gesture_click); + + auto scroll_controller = Gtk::EventControllerScroll::create(); + scroll_controller->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL); + scroll_controller->signal_scroll().connect( + [this](double dx, double dy) { + double x, y; + get_pointer_position(x, y); + GdkModifierType state = GdkModifierType(0); + process_pointer_event(MouseEvent::Type::SCROLL_VERT, x, y, state, 0, -dy); + return true; + }, false); + add_controller(scroll_controller); + + auto key_controller = Gtk::EventControllerKey::create(); + key_controller->signal_key_pressed().connect( + [this](guint keyval, guint keycode, Gdk::ModifierType state) -> bool { + GdkModifierType gdk_state = static_cast(state); + return process_key_event(KeyboardEvent::Type::PRESS, keyval, gdk_state); + }, false); + key_controller->signal_key_released().connect( + [this](guint keyval, guint keycode, Gdk::ModifierType state) -> bool { + GdkModifierType gdk_state = static_cast(state); + return process_key_event(KeyboardEvent::Type::RELEASE, keyval, gdk_state); + }, false); + add_controller(key_controller); + } + + void get_pointer_position(double &x, double &y) { + auto display = get_display(); + auto seat = display->get_default_seat(); + auto device = seat->get_pointer(); + + auto surface = get_native()->get_surface(); + double root_x, root_y; + Gdk::ModifierType mask; + surface->get_device_position(device, root_x, root_y, mask); + + x = root_x; + y = root_y; + } +}; + +class GtkEditorOverlay : public Gtk::Fixed { + Window *_receiver; + GtkGLWidget _gl_widget; + Gtk::Entry _entry; + Glib::RefPtr _key_controller; + +public: + GtkEditorOverlay(Platform::Window *receiver) : _receiver(receiver), _gl_widget(receiver) { + put(_gl_widget, 0, 0); + + _entry.set_visible(false); + _entry.set_has_frame(false); + put(_entry, 0, 0); // We'll position it properly later + + _entry.signal_activate(). + connect(sigc::mem_fun(*this, &GtkEditorOverlay::on_activate)); + + _key_controller = Gtk::EventControllerKey::create(); + _key_controller->signal_key_pressed().connect( + [this](guint keyval, guint keycode, Gdk::ModifierType state) -> bool { + GdkModifierType gdk_state = static_cast(state); + return on_key_pressed(keyval, keycode, gdk_state); + }, false); + _key_controller->signal_key_released().connect( + [this](guint keyval, guint keycode, Gdk::ModifierType state) -> bool { + GdkModifierType gdk_state = static_cast(state); + return on_key_released(keyval, keycode, gdk_state); + }, false); + add_controller(_key_controller); + + auto size_controller = Gtk::EventControllerMotion::create(); + add_controller(size_controller); + + on_size_allocate(); + } + + bool is_editing() const { + return _entry.get_visible(); + } + + void start_editing(int x, int y, int font_height, int min_width, bool is_monospace, + const std::string &val) { + Pango::FontDescription font_desc; + font_desc.set_family(is_monospace ? "monospace" : "normal"); + font_desc.set_absolute_size(font_height * Pango::SCALE); + auto css_provider = Gtk::CssProvider::create(); + std::string css_data = "entry { font-family: "; + css_data += (is_monospace ? "monospace" : "normal"); + css_data += "; font-size: "; + css_data += std::to_string(font_height); + css_data += "px; }"; + css_provider->load_from_data(css_data); + _entry.get_style_context()->add_provider(css_provider, + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + // The y coordinate denotes baseline. + Pango::FontMetrics font_metrics = get_pango_context()->get_metrics(font_desc); + y -= font_metrics.get_ascent() / Pango::SCALE; + + Glib::RefPtr layout = Pango::Layout::create(get_pango_context()); + layout->set_font_description(font_desc); + // Add one extra char width to avoid scrolling. + layout->set_text(val + " "); + int width = layout->get_logical_extents().get_width(); + + Gtk::Border margin; + margin.set_left(0); + margin.set_right(0); + margin.set_top(0); + margin.set_bottom(0); + + Gtk::Border border; + border.set_left(1); + border.set_right(1); + border.set_top(1); + border.set_bottom(1); + + Gtk::Border padding; + padding.set_left(2); + padding.set_right(2); + padding.set_top(2); + padding.set_bottom(2); + + put(_entry, + x - margin.get_left() - border.get_left() - padding.get_left(), + y - margin.get_top() - border.get_top() - padding.get_top()); + + int fitWidth = width / Pango::SCALE + padding.get_left() + padding.get_right(); + _entry.set_size_request(max(fitWidth, min_width), -1); + queue_resize(); + + _entry.set_text(val); + + if(!_entry.get_visible()) { + _entry.set_visible(true); + _entry.grab_focus(); + + _entry.grab_focus(); + } + } + + void stop_editing() { + if(_entry.get_visible()) { + _entry.set_visible(false); + _gl_widget.grab_focus(); + } + } + + GtkGLWidget &get_gl_widget() { + return _gl_widget; + } + +protected: + bool on_key_pressed(guint keyval, guint keycode, GdkModifierType state) { + if(is_editing()) { + if(keyval == GDK_KEY_Escape) { + stop_editing(); + return true; + } + return false; // Let the entry handle it + } + return false; + } + + bool on_key_released(guint keyval, guint keycode, GdkModifierType state) { + if(is_editing()) { + return false; // Let the entry handle it + } + return false; + } + + void on_size_allocate() { + Gtk::Allocation allocation = get_allocation(); + int baseline = -1; // Default baseline value + + _gl_widget.size_allocate(allocation, baseline); + + if(_entry.get_visible()) { + int entry_width, entry_height, min_height, natural_height; + _entry.get_size_request(entry_width, entry_height); + int min_baseline, natural_baseline; + _entry.measure(Gtk::Orientation::VERTICAL, -1, min_height, natural_height, min_baseline, natural_baseline); + + Gtk::Allocation entry_allocation = _entry.get_allocation(); + int x = entry_allocation.get_x(); + int y = entry_allocation.get_y(); + + _entry.size_allocate( + Gdk::Rectangle(x, y, entry_width > 0 ? entry_width : 100, natural_height), + -1); + } + } + + void on_activate() { + if(_receiver->onEditingDone) { + _receiver->onEditingDone(_entry.get_text()); + } + } +}; + +class GtkWindow : public Gtk::Window { + Platform::Window *_receiver; + Gtk::Box _vbox; + Gtk::HeaderBar *menu_bar = NULL; + Gtk::Box _hbox; + GtkEditorOverlay _editor_overlay; + Gtk::Scrollbar _scrollbar; + bool _is_under_cursor = false; + bool _is_fullscreen = false; + std::string _tooltip_text; + Gdk::Rectangle _tooltip_area; + Glib::RefPtr _motion_controller; + +public: + GtkWindow(Platform::Window *receiver) : + _receiver(receiver), + _vbox(Gtk::Orientation::VERTICAL), + _hbox(Gtk::Orientation::HORIZONTAL), + _editor_overlay(receiver), + _scrollbar() { + _scrollbar.set_orientation(Gtk::Orientation::VERTICAL); + + _hbox.set_hexpand(true); + _hbox.set_vexpand(true); + _editor_overlay.set_hexpand(true); + _editor_overlay.set_vexpand(true); + + _hbox.append(_editor_overlay); + _hbox.append(_scrollbar); + _vbox.append(_hbox); + set_child(_vbox); + + _vbox.set_visible(true); + _hbox.set_visible(true); + _editor_overlay.set_visible(true); + get_gl_widget().set_visible(true); + + auto adjustment = Gtk::Adjustment::create(0.0, 0.0, 100.0, 1.0, 10.0, 10.0); + _scrollbar.set_adjustment(adjustment); + adjustment->signal_value_changed(). + connect(sigc::mem_fun(*this, &GtkWindow::on_scrollbar_value_changed), false); + + get_gl_widget().set_has_tooltip(true); + get_gl_widget().signal_query_tooltip(). + connect(sigc::mem_fun(*this, &GtkWindow::on_query_tooltip), false); + + setup_event_controllers(); + } + + bool is_full_screen() const { + return _is_fullscreen; + } + + Gtk::HeaderBar *get_menu_bar() const { + return menu_bar; + } + + void set_menu_bar(Gtk::HeaderBar *menu_bar_ptr) { + if(menu_bar) { + _vbox.remove(*menu_bar); + } + menu_bar = menu_bar_ptr; + if(menu_bar) { + menu_bar->set_visible(true); + _vbox.prepend(*menu_bar); + } + } + + GtkEditorOverlay &get_editor_overlay() { + return _editor_overlay; + } + + GtkGLWidget &get_gl_widget() { + return _editor_overlay.get_gl_widget(); + } + + Gtk::Scrollbar &get_scrollbar() { + return _scrollbar; + } + + void set_tooltip(const std::string &text, const Gdk::Rectangle &rect) { + if(_tooltip_text != text) { + _tooltip_text = text; + _tooltip_area = rect; + get_gl_widget().trigger_tooltip_query(); + } + } + +protected: + void setup_event_controllers() { + _motion_controller = Gtk::EventControllerMotion::create(); + _motion_controller->signal_enter().connect( + [this](double x, double y) -> void { + _is_under_cursor = true; + }); + _motion_controller->signal_leave().connect( + [this]() -> void { + _is_under_cursor = false; + }); + add_controller(_motion_controller); + + signal_close_request().connect( + [this]() -> bool { + if(_receiver->onClose) { + _receiver->onClose(); + return true; // Prevent default close behavior + } + return false; + }, false); + } + + bool on_query_tooltip(int x, int y, bool keyboard_tooltip, + const Glib::RefPtr &tooltip) { + tooltip->set_text(_tooltip_text); + tooltip->set_tip_area(_tooltip_area); + return !_tooltip_text.empty() && (keyboard_tooltip || _is_under_cursor); + } + + void on_fullscreen_changed() { + _is_fullscreen = is_fullscreen(); + if(_receiver->onFullScreen) { + _receiver->onFullScreen(_is_fullscreen); + } + } + + void on_scrollbar_value_changed() { + if(_receiver->onScrollbarAdjusted) { + _receiver->onScrollbarAdjusted(_scrollbar.get_adjustment()->get_value()); + } + } +}; + +//----------------------------------------------------------------------------- +// Windows +//----------------------------------------------------------------------------- + +class WindowImplGtk final : public Window { +public: + GtkWindow gtkWindow; + MenuBarRef menuBar; + + WindowImplGtk(Window::Kind kind) : gtkWindow(this) { + switch(kind) { + case Kind::TOPLEVEL: + break; + + case Kind::TOOL: + gtkWindow.set_modal(true); + gtkWindow.set_deletable(false); + break; + } + + auto icon = LoadPng("freedesktop/solvespace-48x48.png"); + gtkWindow.set_icon_name("solvespace"); + } + + double GetPixelDensity() override { + return gtkWindow.get_scale_factor(); + } + + double GetDevicePixelRatio() override { + return gtkWindow.get_scale_factor(); + } + + bool IsVisible() override { + return gtkWindow.is_visible(); + } + + void SetVisible(bool visible) override { + if(visible) { + gtkWindow.show(); + } else { + gtkWindow.hide(); + } + } + + void Focus() override { + gtkWindow.present(); + } + + bool IsFullScreen() override { + return gtkWindow.is_full_screen(); + } + + void SetFullScreen(bool fullScreen) override { + if(fullScreen) { + gtkWindow.fullscreen(); + } else { + gtkWindow.unfullscreen(); + } + } + + void SetTitle(const std::string &title) override { + gtkWindow.set_title(PrepareTitle(title)); + } + + void SetMenuBar(MenuBarRef newMenuBar) override { + if(newMenuBar) { + auto headerBar = Gtk::make_managed(); + gtkWindow.set_titlebar(*headerBar); + } else { + auto headerBar = Gtk::make_managed(); + gtkWindow.set_titlebar(*headerBar); + } + menuBar = newMenuBar; + } + + void GetContentSize(double *width, double *height) override { + *width = gtkWindow.get_gl_widget().get_allocated_width(); + *height = gtkWindow.get_gl_widget().get_allocated_height(); + } + + void SetMinContentSize(double width, double height) override { + gtkWindow.get_gl_widget().set_size_request((int)width, (int)height); + } + + void FreezePosition(SettingsRef settings, const std::string &key) override { + if(!gtkWindow.is_visible()) return; + + int left = 0, top = 0; + Gtk::Allocation allocation = gtkWindow.get_allocation(); + left = allocation.get_x(); + top = allocation.get_y(); + + int width = gtkWindow.get_width(); + int height = gtkWindow.get_height(); + bool isMaximized = gtkWindow.is_maximized(); + + settings->FreezeInt(key + "_Left", left); + settings->FreezeInt(key + "_Top", top); + settings->FreezeInt(key + "_Width", width); + settings->FreezeInt(key + "_Height", height); + settings->FreezeBool(key + "_Maximized", isMaximized); + } + + void ThawPosition(SettingsRef settings, const std::string &key) override { + int left = 0, top = 0; + int width = gtkWindow.get_width(); + int height = gtkWindow.get_height(); + + left = settings->ThawInt(key + "_Left", left); + top = settings->ThawInt(key + "_Top", top); + width = settings->ThawInt(key + "_Width", width); + height = settings->ThawInt(key + "_Height", height); + + gtkWindow.set_default_size(width, height); + + + if(settings->ThawBool(key + "_Maximized", false)) { + gtkWindow.maximize(); + } + } + + void SetCursor(Cursor cursorType) override { + std::string cursor_name; + switch(cursorType) { + case Cursor::POINTER: cursor_name = "default"; break; + case Cursor::HAND: cursor_name = "pointer"; break; + default: ssassert(false, "Unexpected cursor"); + } + + auto display = gtkWindow.get_display(); + auto gdk_cursor = Gdk::Cursor::create(cursor_name); + gtkWindow.get_gl_widget().set_cursor(gdk_cursor); + } + + void SetTooltip(const std::string &text, double x, double y, + double width, double height) override { + gtkWindow.set_tooltip(text, { (int)x, (int)y, (int)width, (int)height }); + } + + bool IsEditorVisible() override { + return gtkWindow.get_editor_overlay().is_editing(); + } + + void ShowEditor(double x, double y, double fontHeight, double minWidth, + bool isMonospace, const std::string &text) override { + gtkWindow.get_editor_overlay().start_editing( + (int)x, (int)y, (int)fontHeight, (int)minWidth, isMonospace, text); + } + + void HideEditor() override { + gtkWindow.get_editor_overlay().stop_editing(); + } + + void SetScrollbarVisible(bool visible) override { + if(visible) { + gtkWindow.get_scrollbar().show(); + } else { + gtkWindow.get_scrollbar().hide(); + } + } + + void ConfigureScrollbar(double min, double max, double pageSize) override { + auto adjustment = Gtk::Adjustment::create( + gtkWindow.get_scrollbar().get_adjustment()->get_value(), // value + min, // lower + max, // upper + 1, // step_increment + 4, // page_increment + pageSize // page_size + ); + gtkWindow.get_scrollbar().set_adjustment(adjustment); + } + + double GetScrollbarPosition() override { + return gtkWindow.get_scrollbar().get_adjustment()->get_value(); + } + + void SetScrollbarPosition(double pos) override { + gtkWindow.get_scrollbar().get_adjustment()->set_value(pos); + } + + void Invalidate() override { + gtkWindow.get_gl_widget().queue_render(); + } +}; + +WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { + auto window = std::make_shared(kind); + if(parentWindow) { + window->gtkWindow.set_transient_for( + std::static_pointer_cast(parentWindow)->gtkWindow); + } + return window; +} + +//----------------------------------------------------------------------------- +// 3DConnexion support +//----------------------------------------------------------------------------- + +void Open3DConnexion() {} +void Close3DConnexion() {} + +#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: { + SixDofEvent event = {}; + event.type = SixDofEvent::Type::MOTION; + event.translationX = (double)spnavEvent.motion.x; + event.translationY = (double)spnavEvent.motion.y; + event.translationZ = (double)spnavEvent.motion.z * -1.0; + event.rotationX = (double)spnavEvent.motion.rx * 0.001; + event.rotationY = (double)spnavEvent.motion.ry * 0.001; + event.rotationZ = (double)spnavEvent.motion.rz * -0.001; + event.shiftDown = shiftDown; + event.controlDown = controlDown; + if(window->onSixDofEvent) { + window->onSixDofEvent(event); + } + break; + } + + case SPNAV_EVENT_BUTTON: + SixDofEvent event = {}; + if(spnavEvent.button.press) { + event.type = SixDofEvent::Type::PRESS; + } else { + event.type = SixDofEvent::Type::RELEASE; + } + switch(spnavEvent.button.bnum) { + case 0: event.button = SixDofEvent::Button::FIT; break; + default: return; + } + event.shiftDown = shiftDown; + event.controlDown = controlDown; + if(window->onSixDofEvent) { + window->onSixDofEvent(event); + } + break; + } +} + +[[maybe_unused]] +static bool HandleSpnavXEvent(XEvent *xEvent, gpointer data) { + WindowImplGtk *window = (WindowImplGtk *)data; + bool shiftDown = (xEvent->xmotion.state & ShiftMask) != 0; + bool controlDown = (xEvent->xmotion.state & ControlMask) != 0; + + spnav_event spnavEvent; + if(spnav_x11_event(xEvent, &spnavEvent)) { + ProcessSpnavEvent(window, spnavEvent, shiftDown, controlDown); + return true; // Event handled + } + return false; // Event not handled +} + +static gboolean ConsumeSpnavQueue(GIOChannel *, GIOCondition, gpointer data) { + WindowImplGtk *window = (WindowImplGtk *)data; + + auto display = window->gtkWindow.get_display(); + + // We don't get modifier state through the socket. + Gdk::ModifierType mask{}; + + auto seat = display->get_default_seat(); + auto device = seat->get_pointer(); + + auto keyboard = seat->get_keyboard(); + if (keyboard) { + mask = keyboard->get_modifier_state(); + } + + bool shiftDown = ((static_cast(mask) & static_cast(Gdk::ModifierType::SHIFT_MASK)) != 0); + bool controlDown = ((static_cast(mask) & static_cast(Gdk::ModifierType::CONTROL_MASK)) != 0); + + spnav_event spnavEvent; + while(spnav_poll_event(&spnavEvent)) { + ProcessSpnavEvent(window, spnavEvent, shiftDown, controlDown); + } + return TRUE; +} + +void Request3DConnexionEventsForWindow(WindowRef window) { + std::shared_ptr windowImpl = + std::static_pointer_cast(window); + + if(spnav_open() != -1) { + g_io_add_watch(g_io_channel_unix_new(spnav_fd()), G_IO_IN, + ConsumeSpnavQueue, windowImpl.get()); + } +} +#endif // HAVE_SPACEWARE && (GDK_WINDOWING_X11 || GDK_WINDOWING_WAYLAND) + +//----------------------------------------------------------------------------- +// Message dialogs +//----------------------------------------------------------------------------- + +class MessageDialogImplGtk; + +static std::vector> shownMessageDialogs; + +class MessageDialogImplGtk final : public MessageDialog, + public std::enable_shared_from_this { +public: + Gtk::Image gtkImage; + Gtk::MessageDialog gtkDialog; + + MessageDialogImplGtk(Gtk::Window &parent) + : gtkDialog(parent, "", /*use_markup=*/false, Gtk::MessageType::INFO, + Gtk::ButtonsType::NONE, /*modal=*/true) + { + SetTitle("Message"); + } + + void SetType(Type type) override { + switch(type) { + case Type::INFORMATION: + gtkImage.set_from_icon_name("dialog-information"); + gtkImage.set_icon_size(Gtk::IconSize::LARGE); + break; + + case Type::QUESTION: + gtkImage.set_from_icon_name("dialog-question"); + gtkImage.set_icon_size(Gtk::IconSize::LARGE); + break; + + case Type::WARNING: + gtkImage.set_from_icon_name("dialog-warning"); + gtkImage.set_icon_size(Gtk::IconSize::LARGE); + break; + + case Type::ERROR: + gtkImage.set_from_icon_name("dialog-error"); + gtkImage.set_icon_size(Gtk::IconSize::LARGE); + break; + } + auto content_area = gtkDialog.get_content_area(); + content_area->append(gtkImage); + gtkImage.set_visible(true); + } + + void SetTitle(std::string title) override { + gtkDialog.set_title(PrepareTitle(title)); + } + + void SetMessage(std::string message) override { + gtkDialog.set_message(message); + } + + void SetDescription(std::string description) override { + gtkDialog.set_secondary_text(description); + } + + void AddButton(std::string label, Response response, bool isDefault) override { + int responseId = 0; + switch(response) { + case Response::NONE: ssassert(false, "Unexpected response"); + case Response::OK: responseId = Gtk::ResponseType::OK; break; + case Response::YES: responseId = Gtk::ResponseType::YES; break; + case Response::NO: responseId = Gtk::ResponseType::NO; break; + case Response::CANCEL: responseId = Gtk::ResponseType::CANCEL; break; + } + gtkDialog.add_button(PrepareMnemonics(label), responseId); + if(isDefault) { + gtkDialog.set_default_response(responseId); + } + } + + Response ProcessResponse(int gtkResponse) { + Response response; + switch(gtkResponse) { + case Gtk::ResponseType::OK: response = Response::OK; break; + case Gtk::ResponseType::YES: response = Response::YES; break; + case Gtk::ResponseType::NO: response = Response::NO; break; + case Gtk::ResponseType::CANCEL: response = Response::CANCEL; break; + + case Gtk::ResponseType::NONE: + case Gtk::ResponseType::CLOSE: + case Gtk::ResponseType::DELETE_EVENT: + response = Response::NONE; + break; + + default: ssassert(false, "Unexpected response"); + } + + if(onResponse) { + onResponse(response); + } + return response; + } + + void ShowModal() override { + gtkDialog.signal_hide().connect([this]() -> void { + auto it = std::remove(shownMessageDialogs.begin(), shownMessageDialogs.end(), + shared_from_this()); + shownMessageDialogs.erase(it); + }); + shownMessageDialogs.push_back(shared_from_this()); + + gtkDialog.signal_response().connect([this](int gtkResponse) -> void { + ProcessResponse(gtkResponse); + gtkDialog.hide(); + }); + gtkDialog.show(); + } + + Response RunModal() override { + gtkDialog.show(); + int response = Gtk::ResponseType::NONE; + + auto conn = gtkDialog.signal_response().connect( + [&response](int r) { + response = r; + }); + + auto loop = Glib::MainLoop::create(); + gtkDialog.signal_close_request().connect( + [&loop]() -> bool { + loop->quit(); + return true; + }, false); + + loop->run(); + + conn.disconnect(); + + return ProcessResponse(response); + } +}; + +MessageDialogRef CreateMessageDialog(WindowRef parentWindow) { + return std::make_shared( + std::static_pointer_cast(parentWindow)->gtkWindow); +} + +//----------------------------------------------------------------------------- +// File dialogs +//----------------------------------------------------------------------------- + +class FileDialogImplGtk : public FileDialog { +public: + Gtk::FileChooser *gtkChooser; + std::vector extensions; + std::vector> filterObjects; + + void InitFileChooser(Gtk::FileChooser &chooser) { + gtkChooser = &chooser; + if (auto dialog = dynamic_cast(gtkChooser)) { + dialog->signal_response().connect( + [this](int response) { + if (response == Gtk::ResponseType::OK) { + this->FilterChanged(); + } + }, false); + } + } + + void SetCurrentName(std::string name) override { + gtkChooser->set_current_name(name); + } + + Platform::Path GetFilename() override { + return Path::From(gtkChooser->get_file()->get_path()); + } + + void SetFilename(Platform::Path path) override { + gtkChooser->set_file(Gio::File::create_for_path(path.raw)); + } + + void SuggestFilename(Platform::Path path) override { + gtkChooser->set_current_name(path.FileStem()+"."+GetExtension()); + } + + void AddFilter(std::string name, std::vector extensions) override { + Glib::RefPtr gtkFilter = Gtk::FileFilter::create(); + Glib::ustring desc; + for(auto extension : extensions) { + Glib::ustring pattern = "*"; + if(!extension.empty()) { + pattern = "*." + extension; + gtkFilter->add_pattern(pattern); + gtkFilter->add_pattern(Glib::ustring(pattern).uppercase()); + } + if(!desc.empty()) { + desc += ", "; + } + desc += pattern; + } + gtkFilter->set_name(name + " (" + desc + ")"); + + this->extensions.push_back(extensions.front()); + this->filterObjects.push_back(gtkFilter); + gtkChooser->add_filter(gtkFilter); + } + + std::string GetExtension() { + auto currentFilter = gtkChooser->get_filter(); + for (size_t i = 0; i < extensions.size() && i < filterObjects.size(); i++) { + if (filterObjects[i] == currentFilter) { + return extensions[i]; + } + } + return extensions.empty() ? "" : extensions.front(); + } + + void SetExtension(std::string extension) { + size_t extensionIndex = + std::find(extensions.begin(), extensions.end(), extension) - + extensions.begin(); + if(extensionIndex < extensions.size() && extensionIndex < filterObjects.size()) { + gtkChooser->set_filter(filterObjects[extensionIndex]); + } else if (!filterObjects.empty()) { + gtkChooser->set_filter(filterObjects.front()); + } + } + + void FilterChanged() { + std::string extension = GetExtension(); + if(extension.empty()) + return; + + if(gtkChooser->get_file()) { + Platform::Path path = GetFilename(); + if(gtkChooser->get_action() != Gtk::FileChooser::Action::OPEN) { + SetCurrentName(path.WithExtension(extension).FileName()); + } + } + } + + void FreezeChoices(SettingsRef settings, const std::string &key) override { + auto folder = gtkChooser->get_current_folder(); + if(folder) { + settings->FreezeString("Dialog_" + key + "_Folder", folder->get_path()); + } + settings->FreezeString("Dialog_" + key + "_Filter", GetExtension()); + } + + void ThawChoices(SettingsRef settings, const std::string &key) override { + std::string folder_path = settings->ThawString("Dialog_" + key + "_Folder"); + if(!folder_path.empty()) { + gtkChooser->set_current_folder(Gio::File::create_for_path(folder_path)); + } + SetExtension(settings->ThawString("Dialog_" + key + "_Filter")); + } + + void CheckForUntitledFile() { + if(gtkChooser->get_action() == Gtk::FileChooser::Action::SAVE && + Path::From(gtkChooser->get_current_name()).FileStem().empty()) { + gtkChooser->set_current_name(std::string(_("untitled")) + "." + GetExtension()); + } + } +}; + +class FileDialogGtkImplGtk final : public FileDialogImplGtk { +public: + Gtk::FileChooserDialog gtkDialog; + + FileDialogGtkImplGtk(Gtk::Window >kParent, bool isSave) + : gtkDialog(isSave ? C_("title", "Save File") + : C_("title", "Open File"), + isSave ? Gtk::FileChooser::Action::SAVE + : Gtk::FileChooser::Action::OPEN) { + gtkDialog.set_transient_for(gtkParent); + gtkDialog.set_modal(true); + gtkDialog.add_button(C_("button", "_Cancel"), Gtk::ResponseType::CANCEL); + gtkDialog.add_button(isSave ? C_("button", "_Save") + : C_("button", "_Open"), Gtk::ResponseType::OK); + gtkDialog.set_default_response(Gtk::ResponseType::OK); + if(isSave) { + gtkDialog.set_current_name("untitled"); + } + InitFileChooser(gtkDialog); + } + + void SetTitle(std::string title) override { + gtkDialog.set_title(PrepareTitle(title)); + } + + bool RunModal() override { + CheckForUntitledFile(); + + bool result = false; + gtkDialog.signal_response().connect([this, &result](int response) { + if (response == Gtk::ResponseType::OK) { + result = true; + } + gtkDialog.hide(); + }); + + gtkDialog.show(); + + auto context = gtkDialog.get_display()->get_app_launch_context(); + while (gtkDialog.is_visible()) { + g_main_context_iteration(nullptr, TRUE); + } + + return result; + } +}; + +#if defined(HAVE_GTK_FILECHOOSERNATIVE) + +class FileDialogNativeImplGtk final : public FileDialogImplGtk { +public: + Glib::RefPtr gtkNative; + + FileDialogNativeImplGtk(Gtk::Window >kParent, bool isSave) { + gtkNative = Gtk::FileChooserNative::create( + isSave ? C_("title", "Save File") + : C_("title", "Open File"), + gtkParent, + isSave ? Gtk::FileChooser::Action::SAVE + : Gtk::FileChooser::Action::OPEN, + isSave ? C_("button", "_Save") + : C_("button", "_Open"), + C_("button", "_Cancel")); + if(isSave) { + gtkNative->set_current_name("untitled"); + } + InitFileChooser(*gtkNative); + } + + void SetTitle(std::string title) override { + gtkNative->set_title(PrepareTitle(title)); + } + + bool RunModal() override { + CheckForUntitledFile(); + + gtkNative->set_modal(true); + gtkNative->show(); + + auto loop = Glib::MainLoop::create(); + + auto response_id = Gtk::ResponseType::CANCEL; + auto response_handler = gtkNative->signal_response().connect( + [&](int response) { + response_id = (Gtk::ResponseType)response; + loop->quit(); + }); + + loop->run(); + + response_handler.disconnect(); + + return response_id == Gtk::ResponseType::ACCEPT; + } +}; + +#endif + +#if defined(HAVE_GTK_FILECHOOSERNATIVE) +# define FILE_DIALOG_IMPL FileDialogNativeImplGtk +#else +# define FILE_DIALOG_IMPL FileDialogGtkImplGtk +#endif + +FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) { + Gtk::Window >kParent = std::static_pointer_cast(parentWindow)->gtkWindow; + return std::make_shared(gtkParent, /*isSave=*/false); +} + +FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) { + Gtk::Window >kParent = std::static_pointer_cast(parentWindow)->gtkWindow; + return std::make_shared(gtkParent, /*isSave=*/true); +} + +//----------------------------------------------------------------------------- +// Application-wide APIs +//----------------------------------------------------------------------------- + +std::vector GetFontFiles() { + 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; +} + +void OpenInBrowser(const std::string &url) { + Gio::AppInfo::launch_default_for_uri(url); +} + +static Glib::RefPtr gtkApp; + +std::vector InitGui(int argc, char **argv) { + // It would in principle be possible to judiciously use Glib::filename_{from,to}_utf8, + // but it's not really worth the effort. + // The setlocale() call is necessary for Glib::get_charset() to detect the system + // character set; otherwise it thinks it is always ANSI_X3.4-1968. + // We set it back to C after all so that printf() and friends behave in a consistent way. + setlocale(LC_ALL, ""); + if(!Glib::get_charset()) { + dbp("Sorry, only UTF-8 locales are supported."); + exit(1); + } + setlocale(LC_ALL, "C"); + + gtkApp = Gtk::Application::create("org.solvespace.SolveSpace"); + + std::vector args; + gtkApp->signal_command_line().connect( + [&args, argc, argv](const Glib::RefPtr& command_line) -> int { + int app_argc; + char **app_argv = command_line->get_arguments(app_argc); + + args = InitCli(app_argc, app_argv); + + gtkApp->activate(); + return 0; + }, false); + + // Add our application-specific styles, to override GTK defaults. + Glib::RefPtr style_provider = Gtk::CssProvider::create(); + style_provider->load_from_data(R"( + entry { + background: white; + color: black; + } + )"); + + Gtk::StyleContext::add_provider_for_display( + Gdk::Display::get_default(), + style_provider, + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + // Set locale from user preferences. + // This apparently only consults the LANGUAGE environment variable. + const char* const* langNames = g_get_language_names(); + while(*langNames) { + if(SetLocale(*langNames++)) break; + } + if(!*langNames) { + SetLocale("en_US"); + } + + gtkApp->run(argc, argv); + + return args; +} + +void RunGui() { +} + +void ExitGui() { + if(gtkApp) { + gtkApp->quit(); + } +} + +void ClearGui() { + gtkApp.reset(); +} + +} +} diff --git a/src/platform/guihtml.cpp b/src/platform/guihtml.cpp new file mode 100644 index 000000000..25231f56f --- /dev/null +++ b/src/platform/guihtml.cpp @@ -0,0 +1,1464 @@ +//----------------------------------------------------------------------------- +// The Emscripten-based implementation of platform-dependent GUI functionality. +// +// Copyright 2018 whitequark +//----------------------------------------------------------------------------- +#include +#include +#include +#include "config.h" +#include "solvespace.h" + +using namespace emscripten; + +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::string& str) { + // FIXME(emscripten): a nicer way to do this? + EM_ASM($Wrap$ret = UTF8ToString($0), str.c_str()); + return val::global("window")["$Wrap$ret"]; +} + +static std::string Unwrap(val emStr) { + // FIXME(emscripten): a nicer way to do this? + val emArray = val::global("window").call("intArrayFromString", emStr, true) ; + val::global("window").set("$Wrap$input", emArray); + char *strC = (char *)EM_ASM_INT(return allocate($Wrap$input, ALLOC_NORMAL)); + std::string str(strC, emArray["length"].as()); + free(strC); + return str; +} + +static void CallStdFunction(void *data) { + std::function *func = (std::function *)data; + if(*func) { + (*func)(); + } +} + +static val Wrap(std::function *func) { + EM_ASM($Wrap$ret = Module.dynCall_vi.bind(null, $0, $1), CallStdFunction, func); + return val::global("window")["$Wrap$ret"]; +} + +//----------------------------------------------------------------------------- +// 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", Wrap(key), value); + } + + uint32_t ThawInt(const std::string &key, uint32_t defaultValue = 0) { + val value = val::global("localStorage").call("getItem", Wrap(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", Wrap(key), value); + } + + double ThawFloat(const std::string &key, double defaultValue = 0.0) { + val value = val::global("localStorage").call("getItem", Wrap(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", Wrap(key), value); + } + + std::string ThawString(const std::string &key, + const std::string &defaultValue = "") { + val value = val::global("localStorage").call("getItem", Wrap(key)); + if(value == val::null()) + return defaultValue; + return Unwrap(value); + } +}; + +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, + Wrap(label)); + } else { + val htmlLabel = val::global("document").call("createElement", val("span")); + htmlLabel["classList"].call("add", val("label")); + htmlLabel.set("innerText", Wrap(label)); + menuItem->htmlMenuItem.call("appendChild", htmlLabel); + } + menuItem->htmlMenuItem.call("addEventListener", val("trigger"), + Wrap(&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, Wrap(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, Wrap(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::function editingDoneFunc; + 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"); + editingDoneFunc = [this] { + if(onEditingDone) { + onEditingDone(Unwrap(htmlEditor["value"])); + } + }; + htmlEditor.call("addEventListener", val("trigger"), Wrap(&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", Wrap(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", Wrap(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; + + std::vector> responseFuncs; + + 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", Wrap(message)); + } + + void SetDescription(std::string description) { + htmlDescription.set("innerText", Wrap(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, Wrap(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; + }; + if (responseFuncs.size() == 0) { + //FIXME(emscripten): I don't know why but the item in the head of responseFuncs cannot call. + // So add dummy item + responseFuncs.push_back([]{ }); + } + responseFuncs.push_back(responseFunc); + std::function* callback = &responseFuncs.back(); + htmlButton.call("addEventListener", val("trigger"), Wrap(callback)); + + 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 { + + } + + 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", Wrap(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); + std::string filename_str = filename.raw; + EM_ASM(saveFileDone(UTF8ToString($0), $1, $2), filename_str.c_str(), is_saveAs, is_autosave); +} + +std::vector InitGui(int argc, char **argv) { + static std::function onBeforeUnload = std::bind(&SolveSpaceUI::Exit, &SS); + val::global("window").call("addEventListener", val("beforeunload"), + Wrap(&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 cb939cf74..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,25 +359,35 @@ - (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, NSOpenGLPFADepthSize, 24, 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]; @@ -386,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; } @@ -426,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; @@ -458,13 +484,25 @@ - (void)mouseMotionEvent:(NSEvent *)nsEvent { } } +- (void)mouseMotionEvent:(NSEvent *)nsEvent withButton:(Platform::MouseEvent::Button)button { + using Platform::MouseEvent; + + MouseEvent event = [self convertMouseEvent:nsEvent]; + event.type = MouseEvent::Type::MOTION; + event.button = button; + + if(receiver->onMouseEvent) { + receiver->onMouseEvent(event); + } +} + - (void)mouseMoved:(NSEvent *)nsEvent { [self mouseMotionEvent:nsEvent]; [super mouseMoved:nsEvent]; } - (void)mouseDragged:(NSEvent *)nsEvent { - [self mouseMotionEvent:nsEvent]; + [self mouseMotionEvent:nsEvent withButton:Platform::MouseEvent::Button::LEFT]; } - (void)otherMouseDragged:(NSEvent *)nsEvent { @@ -540,12 +578,78 @@ - (void)scrollWheel:(NSEvent *)nsEvent { using Platform::MouseEvent; MouseEvent event = [self convertMouseEvent:nsEvent]; - event.type = MouseEvent::Type::SCROLL_VERT; - event.scrollDelta = [nsEvent deltaY]; + 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 + } - if(receiver->onMouseEvent) { + 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); + + receiver->onMouseEvent(event); +} + +- (void)touchesBeganWithEvent:(NSEvent *)event { + activeTrackpadTouches++; +} + +- (void)touchesEndedWithEvent:(NSEvent *)event { + activeTrackpadTouches--; +} + +- (void)touchesCancelledWithEvent:(NSEvent *)event { + activeTrackpadTouches--; } - (void)mouseExited:(NSEvent *)nsEvent { @@ -623,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 @@ -683,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); } } @@ -754,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; @@ -823,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 { @@ -947,24 +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))]; - if(onScrollbarAdjusted) { - onScrollbarAdjusted(pos); - } + nsScroller.doubleValue = (pos - ssView.scrollerMin) / ( ssView.scrollerSize - ssView.pageSize); } void Invalidate() override { @@ -1261,9 +1423,15 @@ void SetFilename(Platform::Path path) override { nsPanel.nameFieldStringValue = Wrap(path.FileStem()); } + void SuggestFilename(Platform::Path path) override { + SetFilename(path.WithExtension("")); + } + void FreezeChoices(SettingsRef settings, const std::string &key) override { - settings->FreezeString("Dialog_" + key + "_Folder", - [nsPanel.directoryURL.absoluteString UTF8String]); + if (nsPanel.directoryURL != nil) { + settings->FreezeString("Dialog_" + key + "_Folder", + [nsPanel.directoryURL.absoluteString UTF8String]); + } } void ThawChoices(SettingsRef settings, const std::string &key) override { @@ -1410,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); @@ -1427,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 @@ -1441,10 +1637,26 @@ - (void)applicationTerminatePrompt { static SSApplicationDelegate *ssDelegate; -void InitGui(int argc, char **argv) { +std::vector InitGui(int argc, char **argv) { + std::vector args = InitCli(argc, argv); + if(args.size() >= 2 && args[1].find("-psn_") == 0) { + // For unknown reasons, Finder passes a Carbon PSN (Process Serial Number) argument + // when a freshly downloaded application is run for the first time. Remove it so + // that it isn't interpreted as a filename. + args.erase(args.begin() + 1); + } + 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; @@ -1454,6 +1666,8 @@ void InitGui(int argc, char **argv) { if(languages.count == 0) { SolveSpace::SetLocale("en_US"); } + + return args; } void RunGui() { @@ -1461,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/guinone.cpp b/src/platform/guinone.cpp index 85beae37f..4641b984b 100644 --- a/src/platform/guinone.cpp +++ b/src/platform/guinone.cpp @@ -24,26 +24,6 @@ namespace Platform { void FatalError(const std::string &message) { fprintf(stderr, "%s", message.c_str()); - -#if !defined(LIBRARY) && defined(HAVE_BACKTRACE) - static void *ptrs[1024] = {}; - size_t nptrs = backtrace(ptrs, sizeof(ptrs) / sizeof(ptrs[0])); - char **syms = backtrace_symbols(ptrs, nptrs); - - fprintf(stderr, "Backtrace:\n"); - if(syms != NULL) { - for(size_t i = 0; i < nptrs; i++) { - fprintf(stderr, "%2zu: %s\n", i, syms[i]); - } - } else { - for(size_t i = 0; i < nptrs; i++) { - fprintf(stderr, "%2zu: %p\n", i, ptrs[i]); - } - } -#else - fprintf(stderr, "Backtrace support not compiled in.\n"); -#endif - abort(); } @@ -148,7 +128,9 @@ std::vector GetFontFiles() { void OpenInBrowser(const std::string &url) {} -void InitGui(int argc, char **argv) {} +std::vector InitGui(int argc, char **argv) { + return {}; +} void RunGui() {} diff --git a/src/platform/guiwin.cpp b/src/platform/guiwin.cpp index 2f6ce523f..45ad78375 100644 --- a/src/platform/guiwin.cpp +++ b/src/platform/guiwin.cpp @@ -142,7 +142,18 @@ static std::string NegateMnemonics(const std::string &label) { return newLabel; } -static int Clamp(int x, int a, int b) { +static int Clamp(int x, int a, int b, int brda, int brdb) { + // If we are outside of an edge of the monitor + // and a "border" is requested "move in" from that edge + // by "b/brdX" (the "b" parameter is the resolution) + if((x <= a) && (brda)) { + a += b / brda; // yes "b/brda" since b is the size + } + + if(((x >= b) && brdb)) { + b -= b / brdb; + } + return max(a, min(x, b)); } @@ -183,7 +194,7 @@ class SettingsImplWin32 final : public Settings { HKEY GetKey() { if(hKey == NULL) { - sscheck(RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\SolveSpace", 0, NULL, 0, + sscheck(ERROR_SUCCESS == RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\SolveSpace", 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hKey, NULL)); } return hKey; @@ -191,12 +202,12 @@ class SettingsImplWin32 final : public Settings { ~SettingsImplWin32() { if(hKey != NULL) { - sscheck(RegCloseKey(hKey)); + sscheck(ERROR_SUCCESS == RegCloseKey(hKey)); } } void FreezeInt(const std::string &key, uint32_t value) { - sscheck(RegSetValueExW(GetKey(), &Widen(key)[0], 0, + sscheck(ERROR_SUCCESS == RegSetValueExW(GetKey(), &Widen(key)[0], 0, REG_DWORD, (const BYTE *)&value, sizeof(value))); } @@ -212,7 +223,7 @@ class SettingsImplWin32 final : public Settings { } void FreezeFloat(const std::string &key, double value) { - sscheck(RegSetValueExW(GetKey(), &Widen(key)[0], 0, + sscheck(ERROR_SUCCESS == RegSetValueExW(GetKey(), &Widen(key)[0], 0, REG_QWORD, (const BYTE *)&value, sizeof(value))); } @@ -231,7 +242,7 @@ class SettingsImplWin32 final : public Settings { ssassert(value.length() == strlen(value.c_str()), "illegal null byte in middle of a string setting"); std::wstring valueW = Widen(value); - sscheck(RegSetValueExW(GetKey(), &Widen(key)[0], 0, + sscheck(ERROR_SUCCESS == RegSetValueExW(GetKey(), &Widen(key)[0], 0, REG_SZ, (const BYTE *)&valueW[0], (valueW.length() + 1) * 2)); } @@ -242,7 +253,7 @@ class SettingsImplWin32 final : public Settings { if(result == ERROR_SUCCESS && type == REG_SZ) { std::wstring valueW; valueW.resize(length / 2 - 1); - sscheck(RegQueryValueExW(GetKey(), &Widen(key)[0], 0, + sscheck(ERROR_SUCCESS == RegQueryValueExW(GetKey(), &Widen(key)[0], 0, &type, (BYTE *)&valueW[0], &length)); return Narrow(valueW); } @@ -734,6 +745,11 @@ class WindowImplWin32 final : public Window { event.type = SixDofEvent::Type::RELEASE; event.button = SixDofEvent::Button::FIT; } + } else { + return 0; + } + if(window->onSixDofEvent) { + window->onSixDofEvent(event); } return 0; } @@ -777,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)); @@ -790,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: @@ -852,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; @@ -907,8 +923,8 @@ class WindowImplWin32 final : public Window { // Make the mousewheel work according to which window the mouse is // over, not according to which window is active. POINT pt; - pt.x = LOWORD(lParam); - pt.y = HIWORD(lParam); + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); HWND hWindowUnderMouse; sscheck(hWindowUnderMouse = WindowFromPoint(pt)); if(hWindowUnderMouse && hWindowUnderMouse != h) { @@ -917,8 +933,15 @@ class WindowImplWin32 final : public Window { break; } + // Convert the mouse coordinates from screen to client area so that + // scroll wheel zooming remains centered irrespective of the window + // position. + ScreenToClient(hWindowUnderMouse, &pt); + event.x = pt.x / pixelRatio; + event.y = pt.y / pixelRatio; + event.type = MouseEvent::Type::SCROLL_VERT; - event.scrollDelta = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? 1 : -1; + event.scrollDelta = GET_WHEEL_DELTA_WPARAM(wParam) / (double)WHEEL_DELTA; break; case WM_MOUSELEAVE: @@ -1058,7 +1081,7 @@ class WindowImplWin32 final : public Window { sscheck(window = (WindowImplWin32 *)GetWindowLongPtr(hWindow, 0)); switch(msg) { - case WM_KEYDOWN: + case WM_CHAR: if(wParam == VK_RETURN) { if(window->onEditingDone) { int length; @@ -1072,7 +1095,7 @@ class WindowImplWin32 final : public Window { return 0; } } else if(wParam == VK_ESCAPE) { - sscheck(SendMessageW(hWindow, msg, wParam, lParam)); + window->HideEditor(); return 0; } } @@ -1086,20 +1109,20 @@ 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 { BOOL isVisible; - sscheck(isVisible = IsWindowVisible(hWindow)); + isVisible = IsWindowVisible(hWindow); return isVisible == TRUE; } void SetVisible(bool visible) override { - sscheck(ShowWindow(hWindow, visible ? SW_SHOW : SW_HIDE)); + ShowWindow(hWindow, visible ? SW_SHOW : SW_HIDE); } void Focus() override { @@ -1123,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, @@ -1154,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); } } @@ -1206,11 +1229,14 @@ 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 screen + const LONG movein = 40; + RECT mrc = mi.rcMonitor; - rc.left = Clamp(rc.left, mrc.left, mrc.right); - rc.right = Clamp(rc.right, mrc.left, mrc.right); - rc.top = Clamp(rc.top, mrc.top, mrc.bottom); - rc.bottom = Clamp(rc.bottom, mrc.top, mrc.bottom); + rc.left = Clamp(rc.left, mrc.left, mrc.right, 0, movein); + rc.right = Clamp(rc.right, mrc.left, mrc.right, movein, 0); + rc.top = Clamp(rc.top, mrc.top, mrc.bottom, 0, movein); + rc.bottom = Clamp(rc.bottom, mrc.top, mrc.bottom, movein, 0); // And make sure the minimum size is respected. (We can freeze a size smaller // than minimum size if the DPI changed between runs.) @@ -1227,7 +1253,7 @@ class WindowImplWin32 final : public Window { } void SetCursor(Cursor cursor) override { - LPWSTR cursorName; + LPWSTR cursorName = IDC_ARROW; switch(cursor) { case Cursor::POINTER: cursorName = IDC_ARROW; break; case Cursor::HAND: cursorName = IDC_HAND; break; @@ -1244,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); @@ -1267,7 +1293,7 @@ class WindowImplWin32 final : public Window { bool IsEditorVisible() override { BOOL visible; - sscheck(visible = IsWindowVisible(hEditor)); + visible = IsWindowVisible(hEditor); return visible == TRUE; } @@ -1275,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) { @@ -1298,18 +1324,18 @@ 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))); sscheck(MoveWindow(hEditor, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, /*bRepaint=*/true)); - sscheck(ShowWindow(hEditor, SW_SHOW)); + ShowWindow(hEditor, SW_SHOW); if(!textW.empty()) { sscheck(SendMessageW(hEditor, WM_SETTEXT, 0, (LPARAM)textW.c_str())); sscheck(SendMessageW(hEditor, EM_SETSEL, 0, textW.length())); @@ -1320,7 +1346,7 @@ class WindowImplWin32 final : public Window { void HideEditor() override { if(!IsEditorVisible()) return; - sscheck(ShowWindow(hEditor, SW_HIDE)); + ShowWindow(hEditor, SW_HIDE); } void SetScrollbarVisible(bool visible) override { @@ -1335,7 +1361,7 @@ class WindowImplWin32 final : public Window { si.nMin = (UINT)(min * SCROLLBAR_UNIT); si.nMax = (UINT)(max * SCROLLBAR_UNIT); si.nPage = (UINT)(pageSize * SCROLLBAR_UNIT); - sscheck(SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE)); + SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE); // Returns scrollbar position } double GetScrollbarPosition() override { @@ -1359,7 +1385,7 @@ class WindowImplWin32 final : public Window { return; si.nPos = (int)(pos * SCROLLBAR_UNIT); - sscheck(SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE)); + SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE); // Returns scrollbar position // Windows won't synthesize a WM_VSCROLL for us here. if(onScrollbarAdjusted) { @@ -1389,7 +1415,7 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { static HWND hSpaceWareDriverClass; void Open3DConnexion() { - HWND hSpaceWareDriverClass = FindWindowW(L"SpaceWare Driver Class", NULL); + hSpaceWareDriverClass = FindWindowW(L"SpaceWare Driver Class", NULL); if(hSpaceWareDriverClass != NULL) { SiInitialize(); } @@ -1443,7 +1469,10 @@ class MessageDialogImplWin32 final : public MessageDialog { void SetType(Type type) override { switch(type) { case Type::INFORMATION: - style = MB_ICONINFORMATION; + style = MB_USERICON; // Avoid beep + mbp.hInstance = GetModuleHandle(NULL); + mbp.lpszIcon = MAKEINTRESOURCE(4000); // Use SolveSpace icon + // mbp.lpszIcon = IDI_INFORMATION; break; case Type::QUESTION: @@ -1455,7 +1484,10 @@ class MessageDialogImplWin32 final : public MessageDialog { break; case Type::ERROR: - style = MB_ICONERROR; + style = MB_USERICON; // Avoid beep + mbp.hInstance = GetModuleHandle(NULL); + mbp.lpszIcon = MAKEINTRESOURCE(4000); // Use SolveSpace icon + // mbp.lpszIcon = IDI_ERROR; break; } } @@ -1551,11 +1583,6 @@ class FileDialogImplWin32 final : public FileDialog { ofn.nMaxFile = sizeof(filenameWC) / sizeof(wchar_t); ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; - if(isSaveDialog) { - SetTitle(C_("title", "Save File")); - } else { - SetTitle(C_("title", "Open File")); - } } void SetTitle(std::string title) override { @@ -1575,9 +1602,13 @@ class FileDialogImplWin32 final : public FileDialog { wcsncpy(filenameWC, Widen(path.raw).c_str(), sizeof(filenameWC) / sizeof(wchar_t) - 1); } + void SuggestFilename(Platform::Path path) override { + SetFilename(Platform::Path::From(path.FileStem())); + } + void AddFilter(std::string name, std::vector extensions) override { std::string desc, patterns; - for(auto extension : extensions) { + for(auto &extension : extensions) { std::string pattern = "*." + extension; if(!desc.empty()) desc += ", "; desc += pattern; @@ -1604,13 +1635,14 @@ class FileDialogImplWin32 final : public FileDialog { } bool RunModal() override { - if(GetFilename().IsEmpty()) { - SetFilename(Path::From(_("untitled"))); - } - if(isSaveDialog) { + SetTitle(C_("title", "Save File")); + if(GetFilename().IsEmpty()) { + SetFilename(Path::From(_("untitled"))); + } return GetSaveFileNameW(&ofn) == TRUE; } else { + SetTitle(C_("title", "Open File")); return GetOpenFileNameW(&ofn) == TRUE; } } @@ -1656,7 +1688,9 @@ void OpenInBrowser(const std::string &url) { ShellExecuteW(NULL, L"open", Widen(url).c_str(), NULL, NULL, SW_SHOWNORMAL); } -void InitGui(int argc, char **argv) { +std::vector InitGui(int argc, char **argv) { + std::vector args = InitCli(argc, argv); + INITCOMMONCONTROLSEX icc; icc.dwSize = sizeof(icc); icc.dwICC = ICC_STANDARD_CLASSES|ICC_BAR_CLASSES; @@ -1665,6 +1699,8 @@ void InitGui(int argc, char **argv) { if(!SetLocale((uint16_t)GetUserDefaultLCID())) { SetLocale("en_US"); } + + return args; } void RunGui() { 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 e2382d41b..c2cb542a4 100644 --- a/src/platform/platform.cpp +++ b/src/platform/platform.cpp @@ -10,10 +10,12 @@ # include #endif #include "solvespace.h" +#include "mimalloc.h" #include "config.h" #if defined(WIN32) // Conversely, include Microsoft headers after solvespace.h to avoid clashes. # include +# include #else # include # include @@ -91,9 +93,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; } @@ -183,8 +183,10 @@ Path Path::WithExtension(std::string ext) const { if(dot != std::string::npos) { withExt.raw.erase(dot); } - withExt.raw += "."; - withExt.raw += ext; + if(!ext.empty()) { + withExt.raw += "."; + withExt.raw += ext; + } return withExt; } @@ -235,7 +237,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"); @@ -243,13 +246,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; } @@ -399,7 +409,7 @@ FILE *OpenFile(const Platform::Path &filename, const char *mode) { ssassert(filename.raw.length() == strlen(filename.raw.c_str()), "Unexpected null byte in middle of a path"); #if defined(WIN32) - return _wfopen(Widen(filename.Expand().raw).c_str(), Widen(mode).c_str()); + return _wfopen(Widen(filename.Expand(/*fromCurrentDirectory=*/true).raw).c_str(), Widen(mode).c_str()); #else return fopen(filename.raw.c_str(), mode); #endif @@ -452,7 +462,7 @@ bool WriteFile(const Platform::Path &filename, const std::string &data) { } //----------------------------------------------------------------------------- -// Loading resources, on Windows +// Loading resources, on Windows. //----------------------------------------------------------------------------- #if defined(WIN32) @@ -470,7 +480,7 @@ const void *LoadResource(const std::string &name, size_t *size) { #endif //----------------------------------------------------------------------------- -// Loading resources, on *nix +// Loading resources, on *nix. //----------------------------------------------------------------------------- #if defined(__APPLE__) @@ -512,6 +522,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__) @@ -588,8 +604,130 @@ const void *LoadResource(const std::string &name, size_t *size) { #endif //----------------------------------------------------------------------------- -// Command-line argument handling +// Startup and command-line argument handling, on Windows. //----------------------------------------------------------------------------- +#if defined(WIN32) + +std::vector InitCli(int argc, char **argv) { +#if defined(_MSC_VER) + // We display our own message on abort; just call ReportFault. + _set_abort_behavior(_CALL_REPORTFAULT, _WRITE_ABORT_MSG|_CALL_REPORTFAULT); + int crtReportTypes[] = {_CRT_WARN, _CRT_ERROR, _CRT_ASSERT}; + for(int crtReportType : crtReportTypes) { + _CrtSetReportMode(crtReportType, _CRTDBG_MODE_FILE|_CRTDBG_MODE_DEBUG); + _CrtSetReportFile(crtReportType, _CRTDBG_FILE_STDERR); + } +#endif + + // Extract the command-line arguments; the ones from main() are ignored, + // since they are in the OEM encoding. + int argcW; + LPWSTR *argvW = CommandLineToArgvW(GetCommandLineW(), &argcW); + std::vector args; + for(int i = 0; i < argcW; i++) + args.push_back(Platform::Narrow(argvW[i])); + LocalFree(argvW); + return args; +} + +#endif + +//----------------------------------------------------------------------------- +// Startup and command-line argument handling, on *nix. +//----------------------------------------------------------------------------- + +#if !defined(WIN32) + +std::vector InitCli(int argc, char **argv) { + return {&argv[0], &argv[argc]}; +} + +#endif + +//----------------------------------------------------------------------------- +// 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/platform/platform.h b/src/platform/platform.h index 011dfcc4f..1ad5e1758 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -7,6 +7,7 @@ #ifndef SOLVESPACE_PLATFORM_H #define SOLVESPACE_PLATFORM_H +namespace SolveSpace { namespace Platform { // UTF-8 ⟷ UTF-16 conversion, for Windows. @@ -17,6 +18,12 @@ std::string Narrow(const std::wstring &s); std::wstring Widen(const std::string &s); #endif +#if defined(_WIN32) + const std::string embeddedFont = "res://fonts/BitstreamVeraSans-Roman-builtin.ttf"; +#else // Linux and macOS + const std::string embeddedFont = "BitstreamVeraSans-Roman-builtin.ttf"; +#endif + // A filesystem path, respecting the conventions of the current platform. // Transformation functions return an empty path on error. class Path { @@ -64,6 +71,17 @@ void RemoveFile(const Platform::Path &filename); // Resource loading function. 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..84a21d6dd --- /dev/null +++ b/src/platform/platformbase.cpp @@ -0,0 +1,50 @@ +#include "solvespace.h" +#include + +namespace SolveSpace { +namespace Platform { + +//----------------------------------------------------------------------------- +// Debug output +//----------------------------------------------------------------------------- + +void DebugPrint(const char *fmt, ...) { + va_list va; + va_start(va, fmt); + vfprintf(stderr, fmt, va); + fputc('\n', stderr); + va_end(va); +} + +//----------------------------------------------------------------------------- +// 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/utilunix.cpp b/src/platform/utilunix.cpp deleted file mode 100644 index 646affcca..000000000 --- a/src/platform/utilunix.cpp +++ /dev/null @@ -1,86 +0,0 @@ -//----------------------------------------------------------------------------- -// Utility functions used by the Unix port. Notably, our memory allocation; -// we use two separate allocators, one for long-lived stuff and one for -// stuff that gets freed after every regeneration of the model, to save us -// the trouble of freeing the latter explicitly. -// -// Copyright 2008-2013 Jonathan Westhues. -// Copyright 2013 Daniel Richard G. -//----------------------------------------------------------------------------- -#include "config.h" -#include "solvespace.h" -#if defined(HAVE_BACKTRACE) -# include BACKTRACE_HEADER -#endif - -namespace SolveSpace { - -void dbp(const char *fmt, ...) -{ - va_list va; - va_start(va, fmt); - vfprintf(stdout, fmt, va); - fputc('\n', stdout); - va_end(va); - - fflush(stdout); -} - -//----------------------------------------------------------------------------- -// A separate heap, on which we allocate expressions. Maybe a bit faster, -// since fragmentation is less of a concern, and it also makes it possible -// to be sloppy with our memory management, and just free everything at once -// at the end. -//----------------------------------------------------------------------------- - -typedef struct _AllocTempHeader AllocTempHeader; - -typedef struct _AllocTempHeader { - AllocTempHeader *prev; - AllocTempHeader *next; -} AllocTempHeader; - -static AllocTempHeader *Head = NULL; - -void *AllocTemporary(size_t n) -{ - AllocTempHeader *h = - (AllocTempHeader *)malloc(n + sizeof(AllocTempHeader)); - h->prev = NULL; - h->next = Head; - if(Head) Head->prev = h; - Head = h; - memset(&h[1], 0, n); - return (void *)&h[1]; -} - -void FreeAllTemporary() { - AllocTempHeader *h = Head; - while(h) { - AllocTempHeader *f = h; - h = h->next; - free(f); - } - Head = NULL; -} - -void *MemAlloc(size_t n) { - void *p = malloc(n); - ssassert(p != NULL, "Cannot allocate memory"); - return p; -} - -void MemFree(void *p) { - free(p); -} - -std::vector InitPlatform(int argc, char **argv) { - std::vector args; - args.reserve(argc); - for(int i = 0; i < argc; i++) { - args.emplace_back(argv[i]); - } - return args; -} - -}; diff --git a/src/platform/utilwin.cpp b/src/platform/utilwin.cpp deleted file mode 100644 index 4e452802e..000000000 --- a/src/platform/utilwin.cpp +++ /dev/null @@ -1,101 +0,0 @@ -//----------------------------------------------------------------------------- -// Utility functions that depend on Win32. Notably, our memory allocation; -// we use two separate allocators, one for long-lived stuff and one for -// stuff that gets freed after every regeneration of the model, to save us -// the trouble of freeing the latter explicitly. -// -// Copyright 2008-2013 Jonathan Westhues. -//----------------------------------------------------------------------------- -#include "solvespace.h" - -// Include after solvespace.h to avoid identifier clashes. -#include -#include - -namespace SolveSpace { -static HANDLE PermHeap, TempHeap; - -void dbp(const char *str, ...) -{ - va_list f; - static char buf[1024*50]; - va_start(f, str); - _vsnprintf(buf, sizeof(buf), str, f); - va_end(f); - - // 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 -} - -//----------------------------------------------------------------------------- -// A separate heap, on which we allocate expressions. Maybe a bit faster, -// since no fragmentation issues whatsoever, and it also makes it possible -// to be sloppy with our memory management, and just free everything at once -// at the end. -//----------------------------------------------------------------------------- -void *AllocTemporary(size_t n) -{ - void *v = HeapAlloc(TempHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, n); - ssassert(v != NULL, "Cannot allocate memory"); - return v; -} -void FreeAllTemporary() -{ - if(TempHeap) HeapDestroy(TempHeap); - TempHeap = HeapCreate(HEAP_NO_SERIALIZE, 1024*1024*20, 0); - // This is a good place to validate, because it gets called fairly - // often. - vl(); -} - -void *MemAlloc(size_t n) { - void *p = HeapAlloc(PermHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, n); - ssassert(p != NULL, "Cannot allocate memory"); - return p; -} -void MemFree(void *p) { - HeapFree(PermHeap, HEAP_NO_SERIALIZE, p); -} - -void vl() { - ssassert(HeapValidate(TempHeap, HEAP_NO_SERIALIZE, NULL), "Corrupted heap"); - ssassert(HeapValidate(PermHeap, HEAP_NO_SERIALIZE, NULL), "Corrupted heap"); -} - -std::vector InitPlatform(int argc, char **argv) { -#if !defined(LIBRARY) && defined(_MSC_VER) - // We display our own message on abort; just call ReportFault. - _set_abort_behavior(_CALL_REPORTFAULT, _WRITE_ABORT_MSG|_CALL_REPORTFAULT); - int crtReportTypes[] = {_CRT_WARN, _CRT_ERROR, _CRT_ASSERT}; - for(int crtReportType : crtReportTypes) { - _CrtSetReportMode(crtReportType, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); - _CrtSetReportFile(crtReportType, _CRTDBG_FILE_STDERR); - } -#endif - - // Create the heap used for long-lived stuff (that gets freed piecewise). - PermHeap = HeapCreate(HEAP_NO_SERIALIZE, 1024*1024*20, 0); - // Create the heap that we use to store Exprs and other temp stuff. - FreeAllTemporary(); - - // Extract the command-line arguments; the ones from main() are ignored, - // since they are in the OEM encoding. - int argcW; - LPWSTR *argvW = CommandLineToArgvW(GetCommandLineW(), &argcW); - std::vector args; - for(int i = 0; i < argcW; i++) { - args.push_back(Platform::Narrow(argvW[i])); - } - LocalFree(argvW); - return args; -} - -} diff --git a/src/polygon.cpp b/src/polygon.cpp index 4adee80a9..f325fd9b4 100644 --- a/src/polygon.cpp +++ b/src/polygon.cpp @@ -224,7 +224,7 @@ void SEdgeList::AddEdge(Vector a, Vector b, int auxA, int auxB, int tag) { } bool SEdgeList::AssembleContour(Vector first, Vector last, SContour *dest, - SEdge *errorAt, bool keepDir) const + SEdge *errorAt, bool keepDir, int start) const { int i; @@ -232,7 +232,7 @@ bool SEdgeList::AssembleContour(Vector first, Vector last, SContour *dest, dest->AddPoint(last); do { - for(i = 0; i < l.n; i++) { + for(i = start; i < l.n; i++) { /// @todo fix const! SEdge *se = const_cast(&(l[i])); if(se->tag) continue; @@ -269,31 +269,25 @@ bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir) co dest->Clear(); bool allClosed = true; - for(;;) { - Vector first = Vector::From(0, 0, 0); - Vector last = Vector::From(0, 0, 0); - int i; - for(i = 0; i < l.n; i++) { - if(!l[i].tag) { - first = l[i].a; - last = l[i].b; - /// @todo fix const! - const_cast(&(l[i]))->tag = 1; - break; + Vector first = Vector::From(0, 0, 0); + Vector last = Vector::From(0, 0, 0); + int i; + for(i = 0; i < l.n; i++) { + if(!l[i].tag) { + first = l[i].a; + last = l[i].b; + /// @todo fix const! + const_cast(&(l[i]))->tag = 1; + // Create a new empty contour in our polygon, and finish assembling + // into that contour. + dest->AddEmptyContour(); + if(!AssembleContour(first, last, dest->l.Last(), errorAt, keepDir, i+1)) { + allClosed = false; } + // But continue assembling, even if some of the contours are open } - if(i >= l.n) { - return allClosed; - } - - // Create a new empty contour in our polygon, and finish assembling - // into that contour. - dest->AddEmptyContour(); - if(!AssembleContour(first, last, dest->l.Last(), errorAt, keepDir)) { - allClosed = false; - } - // But continue assembling, even if some of the contours are open } + return allClosed; } //----------------------------------------------------------------------------- diff --git a/src/polygon.h b/src/polygon.h index a4be64bbe..cf0f32283 100644 --- a/src/polygon.h +++ b/src/polygon.h @@ -53,7 +53,7 @@ class SEdgeList { void AddEdge(Vector a, Vector b, int auxA=0, int auxB=0, int tag=0); bool AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir=false) const; bool AssembleContour(Vector first, Vector last, SContour *dest, - SEdge *errorAt, bool keepDir) const; + SEdge *errorAt, bool keepDir, int start) const; int AnyEdgeCrossings(Vector a, Vector b, Vector *pi=NULL, SPointList *spl=NULL) const; bool ContainsEdgeFrom(const SEdgeList *sel) const; @@ -131,6 +131,7 @@ class SContour { void FindPointWithMinX(); Vector AnyEdgeMidpoint() const; + bool IsEmptyTriangle(int ap, int bp, int cp, double scaledEPS) const; bool IsEar(int bp, double scaledEps) const; bool BridgeToContour(SContour *sc, SEdgeList *el, List *vl); void ClipEarInto(SMesh *m, int bp, double scaledEps); @@ -252,6 +253,7 @@ class SMesh { List l; bool flipNormal; + bool keepInsideOtherShell; bool keepCoplanar; bool atLeastOneDiscarded; bool isTransparent; @@ -269,6 +271,7 @@ class SMesh { void AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3); void MakeFromUnionOf(SMesh *a, SMesh *b); void MakeFromDifferenceOf(SMesh *a, SMesh *b); + void MakeFromIntersectionOf(SMesh *a, SMesh *b); void MakeFromCopyOf(SMesh *a); void MakeFromTransformationOf(SMesh *a, Vector trans, diff --git a/src/render/gl3shader.h b/src/render/gl3shader.h index 1831e7925..e13929dd4 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 diff --git a/src/render/render.cpp b/src/render/render.cpp index ca2a26eb9..89434c2d3 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -343,9 +343,10 @@ void UiCanvas::DrawBitmapText(const std::string &str, int x, int y, RgbaColor co // A canvas that performs picking against drawn geometry. //----------------------------------------------------------------------------- -void ObjectPicker::DoCompare(double distance, int zIndex, int comparePosition) { +void ObjectPicker::DoCompare(double depth, double distance, int zIndex, int comparePosition) { if(distance > selRadius) return; if((zIndex == maxZIndex && distance < minDistance) || (zIndex > maxZIndex)) { + minDepth = depth; minDistance = distance; maxZIndex = zIndex; position = comparePosition; @@ -372,10 +373,10 @@ void ObjectPicker::DoQuad(const Vector &a, const Vector &b, const Vector &c, con bool insideQuad = (minNegative == VERY_NEGATIVE || maxPositive == VERY_POSITIVE); if(insideQuad) { - DoCompare(0.0, zIndex, comparePosition); + DoCompare(0, 0.0, zIndex, comparePosition); } else { double distance = std::min(fabs(minNegative), fabs(maxPositive)); - DoCompare(distance, zIndex, comparePosition); + DoCompare(0, distance, zIndex, comparePosition); } } @@ -384,7 +385,8 @@ void ObjectPicker::DrawLine(const Vector &a, const Vector &b, hStroke hcs) { Point2d ap = camera.ProjectPoint(a); Point2d bp = camera.ProjectPoint(b); double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true); - DoCompare(distance - stroke->width / 2.0, stroke->zIndex); + double depth = 0.5 * (camera.ProjectPoint3(a).z + camera.ProjectPoint3(b).z) ; + DoCompare(depth, distance - stroke->width / 2.0, stroke->zIndex); } void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) { @@ -393,7 +395,8 @@ void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) { Point2d ap = camera.ProjectPoint(e.a); Point2d bp = camera.ProjectPoint(e.b); double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true); - DoCompare(distance - stroke->width / 2.0, stroke->zIndex, e.auxB); + double depth = 0.5 * (camera.ProjectPoint3(e.a).z + camera.ProjectPoint3(e.b).z) ; + DoCompare(depth, distance - stroke->width / 2.0, stroke->zIndex, e.auxB); } } @@ -423,7 +426,8 @@ void ObjectPicker::DrawQuad(const Vector &a, const Vector &b, const Vector &c, c void ObjectPicker::DrawPoint(const Vector &o, Canvas::hStroke hcs) { Stroke *stroke = strokes.FindById(hcs); double distance = point.DistanceTo(camera.ProjectPoint(o)) - stroke->width / 2; - DoCompare(distance, stroke->zIndex); + double depth = camera.ProjectPoint3(o).z; + DoCompare(depth, distance, stroke->zIndex); } void ObjectPicker::DrawPolygon(const SPolygon &p, hFill hcf) { @@ -445,6 +449,7 @@ void ObjectPicker::DrawPixmap(std::shared_ptr pm, } bool ObjectPicker::Pick(const std::function &drawFn) { + minDepth = VERY_POSITIVE; minDistance = VERY_POSITIVE; maxZIndex = INT_MIN; diff --git a/src/render/render.h b/src/render/render.h index 70d5863f6..9601a5e7d 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -232,6 +232,7 @@ class ObjectPicker : public Canvas { double selRadius = 0.0; // Picking state. double minDistance = 0.0; + double minDepth = 1e10; int maxZIndex = 0; uint32_t position = 0; @@ -257,7 +258,7 @@ class ObjectPicker : public Canvas { const Point2d &ta, const Point2d &tb, hFill hcf) override; void InvalidatePixmap(std::shared_ptr pm) override {} - void DoCompare(double distance, int zIndex, int comparePosition = 0); + void DoCompare(double depth, double distance, int zIndex, int comparePosition = 0); void DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, int zIndex, int comparePosition = 0); @@ -285,8 +286,8 @@ class SurfaceRenderer : public ViewportCanvas { const Camera &GetCamera() const override { return camera; } // ViewportCanvas interface. - void SetCamera(const Camera &camera) override { this->camera = camera; } - void SetLighting(const Lighting &lighting) override { this->lighting = lighting; } + void SetCamera(const Camera &cam) override { this->camera = cam; } + void SetLighting(const Lighting &light) override { this->lighting = light; } void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override; void DrawEdges(const SEdgeList &el, hStroke hcs) override; diff --git a/src/render/render2d.cpp b/src/render/render2d.cpp index 710e795d6..732e3ca33 100644 --- a/src/render/render2d.cpp +++ b/src/render/render2d.cpp @@ -190,11 +190,11 @@ void SurfaceRenderer::DrawFaces(const SMesh &m, const std::vector &fac void SurfaceRenderer::DrawPixmap(std::shared_ptr pm, const Vector &o, const Vector &u, const Vector &v, const Point2d &ta, const Point2d &tb, hFill hcf) { - ssassert(false, "Not implemented"); + dbp("Not implemented"); } void SurfaceRenderer::InvalidatePixmap(std::shared_ptr pm) { - ssassert(false, "Not implemented"); + dbp("Not implemented"); } //----------------------------------------------------------------------------- @@ -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/render/rendergl3.cpp b/src/render/rendergl3.cpp index b620aee0b..481649665 100644 --- a/src/render/rendergl3.cpp +++ b/src/render/rendergl3.cpp @@ -693,8 +693,8 @@ void OpenGl3Renderer::Clear() { } std::shared_ptr OpenGl3Renderer::ReadFrame() { - int width = camera.width * camera.pixelRatio; - int height = camera.height * camera.pixelRatio; + int width = (int)(camera.width * camera.pixelRatio); + int height = (int)(camera.height * camera.pixelRatio); std::shared_ptr pixmap = Pixmap::Create(Pixmap::Format::RGBA, (size_t)width, (size_t)height); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, &pixmap->data[0]); diff --git a/src/request.cpp b/src/request.cpp index 3614cec95..7613a0317 100644 --- a/src/request.cpp +++ b/src/request.cpp @@ -90,7 +90,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; 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 bb0e1c7a9..35ed28907 100644 --- a/src/resource.h +++ b/src/resource.h @@ -4,13 +4,26 @@ // Copyright 2016 whitequark //----------------------------------------------------------------------------- -#ifndef __RESOURCE_H -#define __RESOURCE_H +#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); @@ -25,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); @@ -108,4 +123,5 @@ class VectorFont { const std::function &traceEdge, const Camera &camera); }; +} #endif diff --git a/src/sketch.h b/src/sketch.h index 6cd4acaaa..7f8684ea4 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -175,6 +175,7 @@ class Group { bool suppress; bool relaxConstraints; bool allowRedundant; + bool suppressDofCalculation; bool allDimsReference; double scale; @@ -189,6 +190,8 @@ class Group { struct { SolveResult how; int dof; + int findToFixTimeout; + bool timeout; List remove; } solved; @@ -196,9 +199,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; @@ -239,7 +247,8 @@ class Group { enum class CombineAs : uint32_t { UNION = 0, DIFFERENCE = 1, - ASSEMBLE = 2 + ASSEMBLE = 2, + INTERSECTION = 3 }; CombineAs meshCombine; @@ -263,6 +272,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 @@ -279,11 +289,13 @@ class Group { REMAP_LATHE_END = 1007, REMAP_PT_TO_ARC = 1008, REMAP_PT_TO_NORMAL = 1009, + REMAP_LATHE_ARC_CENTER = 1010, }; hEntity Remap(hEntity in, int copyNumber); void MakeExtrusionLines(EntityList *el, hEntity in); - void MakeLatheCircles(IdList *el, IdList *param, hEntity in, Vector pt, Vector axis, int ai); + 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 MakeExtrusionTopBottomFaces(EntityList *el, hEntity pt); void CopyEntity(EntityList *el, Entity *ep, int timesApplied, int remap, @@ -318,6 +330,7 @@ class Group { void DrawPolyError(Canvas *canvas); void DrawFilledPaths(Canvas *canvas); void DrawContourAreaLabels(Canvas *canvas); + bool ShouldDrawExploded() const; SPolygon GetPolygon(); @@ -363,6 +376,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); @@ -405,6 +419,8 @@ class EntityBase { FACE_N_ROT_TRANS = 5002, FACE_N_TRANS = 5003, FACE_N_ROT_AA = 5004, + FACE_ROT_NORMAL_PT = 5005, + FACE_N_ROT_AXIS_TRANS = 5006, WORKPLANE = 10000, LINE_SEGMENT = 11000, @@ -561,7 +577,8 @@ class Entity : public EntityBase { bool IsStylable() const; bool IsVisible() const; - + bool CanBeDragged() const; + enum class DrawAs { DEFAULT, OVERLAY, HIDDEN, HOVERED, SELECTED }; void Draw(DrawAs how, Canvas *canvas); void GetReferencePoints(std::vector *refs); @@ -583,6 +600,10 @@ class Entity : public EntityBase { beziers.l.Clear(); edges.l.Clear(); } + + bool ShouldDrawExploded() const; + Vector ExplodeOffset() const; + Vector PointGetDrawNum() const; }; class EntReqTable { @@ -604,7 +625,7 @@ class Param { bool free; // Used only in the solver - hParam substd; + Param *substd; static const hParam NO_PARAM; @@ -665,7 +686,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 }; @@ -699,6 +723,7 @@ class ConstraintBase { } bool HasLabel() const; + bool IsProjectible() const; void Generate(IdList *param); @@ -748,7 +773,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, @@ -770,6 +795,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); @@ -781,6 +808,12 @@ 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, + 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); }; class hEquation { @@ -871,13 +904,18 @@ class Style { RgbaColor color; double width; int zIndex; + bool exportable; + StipplePattern stippleType; } Default; static const Default Defaults[]; static std::string CnfColor(const std::string &prefix); static std::string CnfWidth(const std::string &prefix); + static std::string CnfStippleType(const std::string &prefix); + static std::string CnfStippleScale(const std::string &prefix); static std::string CnfTextHeight(const std::string &prefix); static std::string CnfPrefixToName(const std::string &prefix); + static std::string CnfExportable(const std::string &prefix); static void CreateAllDefaultStyles(); static void CreateDefaultStyle(hStyle h); @@ -904,7 +942,11 @@ class Style { static bool Exportable(int hs); static hStyle ForEntity(hEntity he); static StipplePattern PatternType(hStyle hs); + 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; diff --git a/src/solvespace.cpp b/src/solvespace.cpp index 4d1913685..f4f606c99 100644 --- a/src/solvespace.cpp +++ b/src/solvespace.cpp @@ -19,6 +19,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 @@ -33,7 +34,7 @@ void SolveSpaceUI::Init() { // Light intensities lightIntensity[0] = settings->ThawFloat("LightIntensity_0", 1.0); lightIntensity[1] = settings->ThawFloat("LightIntensity_1", 0.5); - ambientIntensity = 0.3; // no setting for that yet + ambientIntensity = settings->ThawFloat("Light_Ambient", 0.3); // Light positions lightDir[0].x = settings->ThawFloat("LightDir_0_Right", -1.0); lightDir[0].y = settings->ThawFloat("LightDir_0_Up", 1.0); @@ -44,13 +45,17 @@ void SolveSpaceUI::Init() { exportMode = false; // Chord tolerance - chordTol = settings->ThawFloat("ChordTolerancePct", 0.5); + chordTol = settings->ThawFloat("ChordTolerancePct", 0.1); // Max pwl segments to generate - maxSegments = settings->ThawInt("MaxSegments", 10); + maxSegments = settings->ThawInt("MaxSegments", 20); // Chord tolerance exportChordTol = settings->ThawFloat("ExportChordTolerance", 0.1); // Max pwl segments to generate 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 @@ -66,14 +71,22 @@ 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 - immediatelyEditDimension = settings->ThawBool("ImmediatelyEditDimension", false); + immediatelyEditDimension = settings->ThawBool("ImmediatelyEditDimension", true); // Check that contours are closed and not self-intersecting checkClosedContour = settings->ThawBool("CheckClosedContour", true); // Enable automatic constrains for lines @@ -100,6 +113,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); @@ -119,12 +133,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); @@ -151,7 +161,7 @@ void SolveSpaceUI::Init() { } bool SolveSpaceUI::LoadAutosaveFor(const Platform::Path &filename) { - Platform::Path autosaveFile = filename.WithExtension(AUTOSAVE_EXT); + Platform::Path autosaveFile = filename.WithExtension(BACKUP_EXT); FILE *f = OpenFile(autosaveFile, "rb"); if(!f) @@ -213,6 +223,7 @@ void SolveSpaceUI::Exit() { // Light intensities settings->FreezeFloat("LightIntensity_0", (float)lightIntensity[0]); settings->FreezeFloat("LightIntensity_1", (float)lightIntensity[1]); + settings->FreezeFloat("Light_Ambient", (float)ambientIntensity); // Light directions settings->FreezeFloat("LightDir_0_Right", (float)lightDir[0].x); settings->FreezeFloat("LightDir_0_Up", (float)lightDir[0].y); @@ -228,6 +239,10 @@ void SolveSpaceUI::Exit() { settings->FreezeFloat("ExportChordTolerance", (float)exportChordTol); // Export Max pwl segments to generate 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 @@ -243,14 +258,22 @@ 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 + settings->FreezeBool("ExportBackgroundColor", exportBackgroundColor); // Draw back faces of triangles (when mesh is leaky/self-intersecting) settings->FreezeBool("DrawBackFaces", drawBackFaces); // Draw closed polygons areas 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 @@ -291,12 +314,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() { @@ -306,6 +345,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; } @@ -314,22 +354,54 @@ 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(); - switch(viewUnits) { - case Unit::INCHES: - return ssprintf("%.*f", afterDecimalInch, v); - case Unit::METERS: - case Unit::MM: - return ssprintf("%.*f", afterDecimalMm, v); + // 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(); } - return ""; + + int digits = UnitDigitsAfterDecimal(); + double minimum = 0.5 * pow(10,-digits); + while ((v < minimum) && (v > LENGTH_EPS)) { + digits++; + minimum *= 0.1; + } + return ssprintf("%.*f", digits, v); } static const char *DimToString(int dim) { switch(dim) { @@ -339,13 +411,39 @@ static const char *DimToString(int dim) { default: ssassert(false, "Unexpected dimension"); } } -static std::pair SelectSIPrefixMm(int deg) { - if(deg >= 3) return { 3, "km" }; - else if(deg >= 0) return { 0, "m" }; - else if(deg >= -2) return { -2, "cm" }; - else if(deg >= -3) return { -3, "mm" }; - else if(deg >= -6) return { -6, "µm" }; - else return { -9, "nm" }; +static std::pair SelectSIPrefixMm(int ord, int dim) { +// decide what units to use depending on the order of magnitude of the +// measure in meters and the dimension (1,2,3 lenear, area, volume) + switch(dim) { + case 0: + case 1: + if(ord >= 3) return { 3, "km" }; + else if(ord >= 0) return { 0, "m" }; + else if(ord >= -2) return { -2, "cm" }; + else if(ord >= -3) return { -3, "mm" }; + else if(ord >= -6) return { -6, "µm" }; + else return { -9, "nm" }; + break; + case 2: + if(ord >= 5) return { 3, "km" }; + else if(ord >= 0) return { 0, "m" }; + else if(ord >= -2) return { -2, "cm" }; + else if(ord >= -6) return { -3, "mm" }; + else if(ord >= -13) return { -6, "µm" }; + else return { -9, "nm" }; + break; + case 3: + if(ord >= 7) return { 3, "km" }; + else if(ord >= 0) return { 0, "m" }; + else if(ord >= -5) return { -2, "cm" }; + else if(ord >= -11) return { -3, "mm" }; + else return { -6, "µm" }; + break; + default: + dbp ("dimensions over 3 not supported"); + break; + } + return {0, "m"}; } static std::pair SelectSIPrefixInch(int deg) { if(deg >= 0) return { 0, "in" }; @@ -360,19 +458,24 @@ std::string SolveSpaceUI::MmToStringSI(double v, int dim) { dim = 1; } - v /= pow((viewUnits == Unit::INCHES) ? 25.4 : 1000, dim); - int vdeg = (int)((log10(fabs(v))) / 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) - ? SelectSIPrefixInch(vdeg) - : SelectSIPrefixMm(vdeg); + 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, + return ssprintf("%.*g%s%s%s", pdeg + UnitDigitsAfterDecimal(), v, compact ? "" : " ", unit.c_str(), DimToString(dim)); } std::string SolveSpaceUI::DegreeToString(double v) { @@ -400,10 +503,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; @@ -474,12 +578,18 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) { if(saveAs || saveFile.IsEmpty()) { Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(GW.window); - dialog->AddFilter(C_("file-type", "SolveSpace models"), { "slvs" }); + // 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 { @@ -492,6 +602,9 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) { RemoveAutosave(); saveFile = newSaveFile; unsaved = false; + if (this->OnSaveFinished) { + this->OnSaveFinished(newSaveFile, saveAs, false); + } return true; } else { return false; @@ -503,13 +616,17 @@ void SolveSpaceUI::Autosave() ScheduleAutosave(); if(!saveFile.IsEmpty() && unsaved) { - SaveToFile(saveFile.WithExtension(AUTOSAVE_EXT)); + Platform::Path saveFileName = saveFile.WithExtension(BACKUP_EXT); + SaveToFile(saveFileName); + if (this->OnSaveFinished) { + this->OnSaveFinished(saveFileName, false, true); + } } } void SolveSpaceUI::RemoveAutosave() { - Platform::Path autosaveFile = saveFile.WithExtension(AUTOSAVE_EXT); + Platform::Path autosaveFile = saveFile.WithExtension(BACKUP_EXT); RemoveFile(autosaveFile); } @@ -555,7 +672,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)); + } } } @@ -599,9 +720,13 @@ void SolveSpaceUI::MenuFile(Command id) { Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); dialog->AddFilters(Platform::RasterFileFilters); dialog->ThawChoices(settings, "ExportImage"); + dialog->SuggestFilename(SS.saveFile); if(dialog->RunModal()) { dialog->FreezeChoices(settings, "ExportImage"); SS.ExportAsPngTo(dialog->GetFilename()); + if (SS.OnSaveFinished) { + SS.OnSaveFinished(dialog->GetFilename(), false, false); + } } break; } @@ -610,15 +735,14 @@ void SolveSpaceUI::MenuFile(Command id) { Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); dialog->AddFilters(Platform::VectorFileFilters); dialog->ThawChoices(settings, "ExportView"); + dialog->SuggestFilename(SS.saveFile); if(!dialog->RunModal()) break; dialog->FreezeChoices(settings, "ExportView"); // 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 " @@ -626,6 +750,9 @@ void SolveSpaceUI::MenuFile(Command id) { } SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe=*/false); + if (SS.OnSaveFinished) { + SS.OnSaveFinished(dialog->GetFilename(), false, false); + } break; } @@ -633,10 +760,14 @@ void SolveSpaceUI::MenuFile(Command id) { Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); dialog->AddFilters(Platform::Vector3dFileFilters); dialog->ThawChoices(settings, "ExportWireframe"); + dialog->SuggestFilename(SS.saveFile); if(!dialog->RunModal()) break; dialog->FreezeChoices(settings, "ExportWireframe"); SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe*/true); + if (SS.OnSaveFinished) { + SS.OnSaveFinished(dialog->GetFilename(), false, false); + } break; } @@ -644,10 +775,14 @@ void SolveSpaceUI::MenuFile(Command id) { Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); dialog->AddFilters(Platform::VectorFileFilters); dialog->ThawChoices(settings, "ExportSection"); + dialog->SuggestFilename(SS.saveFile); if(!dialog->RunModal()) break; dialog->FreezeChoices(settings, "ExportSection"); SS.ExportSectionTo(dialog->GetFilename()); + if (SS.OnSaveFinished) { + SS.OnSaveFinished(dialog->GetFilename(), false, false); + } break; } @@ -655,10 +790,15 @@ void SolveSpaceUI::MenuFile(Command id) { Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); dialog->AddFilters(Platform::MeshFileFilters); dialog->ThawChoices(settings, "ExportMesh"); + dialog->SuggestFilename(SS.saveFile); if(!dialog->RunModal()) break; dialog->FreezeChoices(settings, "ExportMesh"); SS.ExportMeshTo(dialog->GetFilename()); + if (SS.OnSaveFinished) { + SS.OnSaveFinished(dialog->GetFilename(), false, false); + } + break; } @@ -666,11 +806,15 @@ void SolveSpaceUI::MenuFile(Command id) { Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); dialog->AddFilters(Platform::SurfaceFileFilters); dialog->ThawChoices(settings, "ExportSurfaces"); + dialog->SuggestFilename(SS.saveFile); if(!dialog->RunModal()) break; dialog->FreezeChoices(settings, "ExportSurfaces"); StepFileWriter sfw = {}; sfw.ExportSurfacesTo(dialog->GetFilename()); + if (SS.OnSaveFinished) { + SS.OnSaveFinished(dialog->GetFilename(), false, false); + } break; } @@ -724,7 +868,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; @@ -802,7 +950,6 @@ void SolveSpaceUI::MenuAnalyze(Command id) { case Command::AREA: { Group *g = SK.GetGroup(SS.GW.activeGroup); SS.GW.GroupSelection(); - auto const &gs = SS.GW.gs; if(gs.faces > 0) { std::vector faces; @@ -844,8 +991,8 @@ void SolveSpaceUI::MenuAnalyze(Command id) { if(gs.n > 0 && gs.n == gs.entities) { double perimeter = 0.0; for(int i = 0; i < gs.entities; i++) { - Entity *e = SK.entity.FindById(gs.entity[i]); - SEdgeList *el = e->GetOrGenerateEdges(); + Entity *en = SK.entity.FindById(gs.entity[i]); + SEdgeList *el = en->GetOrGenerateEdges(); for(const SEdge &e : el->l) { perimeter += e.b.Minus(e.a).Magnitude(); } @@ -877,9 +1024,13 @@ void SolveSpaceUI::MenuAnalyze(Command id) { break; case Command::STOP_TRACING: { + if (SS.traced.point == Entity::NO_ENTITY) { + break; + } Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); dialog->AddFilters(Platform::CsvFileFilters); dialog->ThawChoices(settings, "Trace"); + dialog->SetFilename(SS.saveFile); if(dialog->RunModal()) { dialog->FreezeChoices(settings, "Trace"); @@ -964,7 +1115,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, 2019); +PACKAGE_VERSION, 2025); + break; + + case Command::GITHUB: + Platform::OpenInBrowser(GIT_HASH_URL); break; default: ssassert(false, "Unexpected menu ID"); @@ -981,13 +1136,16 @@ void SolveSpaceUI::Clear() { GW.openRecentMenu = NULL; GW.linkRecentMenu = NULL; 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; diff --git a/src/solvespace.h b/src/solvespace.h index be0284cd2..9aee5211c 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -7,16 +7,20 @@ #ifndef SOLVESPACE_H #define SOLVESPACE_H -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "resource.h" +#include "platform/platform.h" +#include "platform/gui.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -30,6 +34,10 @@ #include #include +#define EIGEN_NO_DEBUG +#undef Success +#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 @@ -71,19 +79,19 @@ typedef struct _cairo_surface cairo_surface_t; } while(0) #endif -#ifndef isnan -# define isnan(x) (((x) != (x)) || (x > 1e11) || (x < -1e11)) -#endif +#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)) namespace SolveSpace { using std::min; using std::max; using std::swap; +using std::fabs; -#if defined(__GNUC__) -__attribute__((noreturn)) -#endif +[[noreturn]] void AssertFailure(const char *file, unsigned line, const char *function, const char *condition, const char *message); @@ -92,6 +100,10 @@ __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; @@ -111,19 +123,16 @@ inline double WRAP_SYMMETRIC(double v, double n) { return v; } -// Why is this faster than the library function? -inline double ffabs(double v) { return (v > 0) ? v : (-v); } - #define CO(v) (v).x, (v).y, (v).z -#define ANGLE_COS_EPS (1e-6) -#define LENGTH_EPS (1e-6) -#define VERY_POSITIVE (1e10) -#define VERY_NEGATIVE (-1e10) +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; -inline double Random(double vmax) { - return (vmax*rand()) / RAND_MAX; -} + +using Platform::AllocTemporary; +using Platform::FreeAllTemporary; class Expr; class ExprVector; @@ -131,38 +140,11 @@ class ExprQuaternion; class RgbaColor; enum class Command : uint32_t; -//================ -// From the platform-specific code. - -#include "platform/platform.h" -#include "platform/gui.h" - -const size_t MAX_RECENT = 8; - -#define AUTOSAVE_EXT "slvs~" - -void dbp(const char *str, ...); -#define DBPTRI(tri) \ - dbp("tri: (%.3f %.3f %.3f) (%.3f %.3f %.3f) (%.3f %.3f %.3f)", \ - CO((tri).a), CO((tri).b), CO((tri).c)) - -std::vector InitPlatform(int argc, char **argv); - -void *AllocTemporary(size_t n); -void FreeAllTemporary(); -void *MemAlloc(size_t n); -void MemFree(void *p); -void vl(); // debug function to validate heaps - -// End of platform-specific functions -//================ - -#include "resource.h" - enum class Unit : uint32_t { MM = 0, INCHES, - METERS + METERS, + FEET_INCHES }; template @@ -197,9 +179,15 @@ enum class SolveResult : uint32_t { // 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; } @@ -233,7 +221,7 @@ void Error(const char *fmt, ...); class System { public: - enum { MAX_UNKNOWNS = 1024 }; + enum { MAX_UNKNOWNS = 2048 }; EntityList entity; ParamList param; @@ -255,44 +243,42 @@ 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 scale; + 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); + static bool SolveLinearSystem(const Eigen::SparseMatrix &A, + const Eigen::VectorXd &B, Eigen::VectorXd *X); bool SolveLeastSquares(); bool WriteJacobian(int tag); void EvalJacobian(); void WriteEquationsExceptFor(hConstraint hc, Group *g); - void FindWhichToRemoveToFixJacobian(Group *g, List *bad, bool forceDofCheck); + void FindWhichToRemoveToFixJacobian(Group *g, List *bad, + bool forceDofCheck); void SolveBySubstitution(); bool IsDragged(hParam p); @@ -300,7 +286,6 @@ class System { bool NewtonSolve(int tag); void MarkParamsFree(bool findFree); - int CalculateDof(); SolveResult Solve(Group *g, int *rank = NULL, int *dof = NULL, List *bad = NULL, @@ -312,6 +297,9 @@ class System { bool andFindBad = false, bool andFindFree = false); void Clear(); + Param *GetLastParamSubstitution(Param *p); + void SubstituteParamsByLast(Expr *e); + void SortSubstitutionByDragged(Param *p); }; #include "ttf.h" @@ -363,6 +351,7 @@ class VectorFileWriter { virtual void Bezier(SBezier *sb) = 0; virtual void Triangle(STriangle *tr) = 0; virtual bool OutputConstraints(IdList *) { return false; } + virtual void Background(RgbaColor color) = 0; virtual void StartFile() = 0; virtual void FinishAndCloseFile() = 0; virtual bool HasCanvasSize() const = 0; @@ -387,6 +376,7 @@ class DxfFileWriter : public VectorFileWriter { bool filled, RgbaColor fillRgb, hStyle hs) override; void Triangle(STriangle *tr) override; void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; void StartFile() override; void FinishAndCloseFile() override; bool HasCanvasSize() const override { return false; } @@ -404,6 +394,7 @@ class EpsFileWriter : public VectorFileWriter { bool filled, RgbaColor fillRgb, hStyle hs) override; void Triangle(STriangle *tr) override; void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; void StartFile() override; void FinishAndCloseFile() override; bool HasCanvasSize() const override { return true; } @@ -422,6 +413,7 @@ class PdfFileWriter : public VectorFileWriter { bool filled, RgbaColor fillRgb, hStyle hs) override; void Triangle(STriangle *tr) override; void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; void StartFile() override; void FinishAndCloseFile() override; bool HasCanvasSize() const override { return true; } @@ -438,6 +430,7 @@ class SvgFileWriter : public VectorFileWriter { bool filled, RgbaColor fillRgb, hStyle hs) override; void Triangle(STriangle *tr) override; void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; void StartFile() override; void FinishAndCloseFile() override; bool HasCanvasSize() const override { return true; } @@ -452,6 +445,7 @@ class HpglFileWriter : public VectorFileWriter { bool filled, RgbaColor fillRgb, hStyle hs) override; void Triangle(STriangle *tr) override; void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; void StartFile() override; void FinishAndCloseFile() override; bool HasCanvasSize() const override { return false; } @@ -465,6 +459,7 @@ class Step2dFileWriter : public VectorFileWriter { bool filled, RgbaColor fillRgb, hStyle hs) override; void Triangle(STriangle *tr) override; void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; void StartFile() override; void FinishAndCloseFile() override; bool HasCanvasSize() const override { return false; } @@ -479,6 +474,7 @@ class GCodeFileWriter : public VectorFileWriter { bool filled, RgbaColor fillRgb, hStyle hs) override; void Triangle(STriangle *tr) override; void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; void StartFile() override; void FinishAndCloseFile() override; bool HasCanvasSize() const override { return false; } @@ -528,7 +524,7 @@ class SolveSpaceUI { GraphicsWindow GW; // The state for undo/redo - typedef struct { + typedef struct UndoState { IdList group; List
groupOrder; IdList request; @@ -545,7 +541,7 @@ class SolveSpaceUI { style.Clear(); } } UndoState; - enum { MAX_UNDO = 16 }; + enum { MAX_UNDO = 100 }; typedef struct { UndoState d[MAX_UNDO]; int cnt; @@ -578,14 +574,20 @@ class SolveSpaceUI { int maxSegments; 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; @@ -610,6 +612,7 @@ class SolveSpaceUI { } exportCanvas; struct { double depth; + double safeHeight; int passes; double feed; double plungeFeed; @@ -621,8 +624,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,7 +680,9 @@ class SolveSpaceUI { static void MenuFile(Command id); void Autosave(); void RemoveAutosave(); - static const size_t MAX_RECENT = 8; + static constexpr size_t MAX_RECENT = 8; + static constexpr const char *SKETCH_EXT = "slvs"; + static constexpr const char *BACKUP_EXT = "slvs~"; std::vector recentFiles; bool Load(const Platform::Path &filename); bool GetFilenameAndSave(bool saveAs); @@ -686,16 +693,18 @@ 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, SMesh *m, SShell *sh); + bool LoadEntitiesFromSlvs(const Platform::Path &filename, EntityList *le, + SMesh *m, SShell *sh); bool ReloadAllLinked(const Platform::Path &filename, bool canCancel = false); // And the various export options void ExportAsPngTo(const Platform::Path &filename); void ExportMeshTo(const Platform::Path &filename); void ExportMeshAsStlTo(FILE *f, SMesh *sm); - void ExportMeshAsQ3doTo(FILE *f, SMesh *sm); void ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm); void ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename, SMesh *sm, SOutlineList *sol); @@ -795,9 +804,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(); @@ -810,8 +821,8 @@ class SolveSpaceUI { // where it puts zero-initialized global data in the binary (~30M of zeroes) // in release builds. SolveSpaceUI() - : pTW(new TextWindow({})), TW(*pTW), - pSys(new System({})), sys(*pSys) {} + : pTW(new TextWindow()), TW(*pTW), + pSys(new System()), sys(*pSys) {} ~SolveSpaceUI() { delete pTW; @@ -821,6 +832,8 @@ 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; diff --git a/src/srf/boolean.cpp b/src/srf/boolean.cpp index f01ce625b..7b8c00c56 100644 --- a/src/srf/boolean.cpp +++ b/src/srf/boolean.cpp @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -// Top-level functions to compute the Boolean union or difference between -// two shells of rational polynomial surfaces. +// Top-level functions to compute the Boolean union, difference or intersection +// between two shells of rational polynomial surfaces. // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- @@ -16,6 +16,54 @@ void SShell::MakeFromDifferenceOf(SShell *a, SShell *b) { MakeFromBoolean(a, b, SSurface::CombineAs::DIFFERENCE); } +void SShell::MakeFromIntersectionOf(SShell *a, SShell *b) { + MakeFromBoolean(a, b, SSurface::CombineAs::INTERSECTION); +} + +void SCurve::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const { + *ptMax = {VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE}; + *ptMin = {VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE}; + + for(int i = 0; i <= exact.deg; i++) { + exact.ctrl[i].MakeMaxMin(ptMax, ptMin); + } +} + +// We will be inserting other curve 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 +// handles more buggy cases. It's not clear this is the best way but it works ok. +static void FindVertsOnCurve(List *l, const SCurve *curve, SShell *sh) { + + Vector amax, amin; + curve->GetAxisAlignedBounding(&amax, &amin); + + for(const auto &sc : sh->curve) { + if(!sc.isExact) continue; + + Vector cmax, cmin; + sc.GetAxisAlignedBounding(&cmax, &cmin); + + if(Vector::BoundingBoxesDisjoint(amax, amin, cmax, cmin)) { + // They cannot possibly intersect, no curves to generate + continue; + } + + for(int i=0; i<2; i++) { + Vector pt = sc.exact.ctrl[ i==0 ? 0 : sc.exact.deg ]; + double t; + curve->exact.ClosestPointTo(pt, &t, /*must converge=*/ false); + double d = pt.Minus(curve->exact.PointAt(t)).Magnitude(); + if((t>LENGTH_EPS) && (t<(1.0-LENGTH_EPS)) && (d < LENGTH_EPS)) { + SInter inter; + inter.p = pt; + l->Add(&inter); + } + } + } +} + //----------------------------------------------------------------------------- // Take our original pwl curve. Wherever an edge intersects a surface within // either agnstA or agnstB, split the piecewise linear element. Then refine @@ -31,12 +79,21 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, ret = *this; ret.pts = {}; + // First find any vertex that lies on our curve. + List vertpts = {}; + if(isExact) { + if(agnstA) + FindVertsOnCurve(&vertpts, this, agnstA); + if(agnstB) + FindVertsOnCurve(&vertpts, this, agnstB); + } + const SCurvePt *p = pts.First(); ssassert(p != NULL, "Cannot split an empty curve"); SCurvePt prev = *p; ret.pts.Add(p); p = pts.NextAfter(p); - + for(; p; p = pts.NextAfter(p)) { List il = {}; @@ -84,17 +141,34 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, } } - // We're keeping the intersection, so actually refine it. - (pi->srf)->PointOnSurfaces(srfA, srfB, &(puv.x), &(puv.y)); + // We're keeping the intersection, so actually refine it. Finding the intersection + // to within EPS is important to match the ends of different chopped trim curves. + // The general 3-surface intersection fails to refine for trims where surfaces + // are tangent at the curve, but those trims are usually exact, so… + if(isExact) { + (pi->srf)->PointOnCurve(&exact, &(puv.x), &(puv.y)); + } else { + (pi->srf)->PointOnSurfaces(srfA, srfB, &(puv.x), &(puv.y)); + } pi->p = (pi->srf)->PointAt(puv); } il.RemoveTagged(); + } + // Now add any vertex that is on this segment + const Vector lineStart = prev.p; + const Vector lineDirection = (p->p).Minus(prev.p); + for(const auto &vtx : vertpts) { + double t = (vtx.p.Minus(lineStart)).DivProjected(lineDirection); + if((0.0 < t) && (t < 1.0)) { + il.Add(&vtx); + } + } + if(!il.IsEmpty()) { + SInter *pi; // And now sort them in order along the line. Note that we must // do that after refining, in case the refining would make two // points switch places. - const Vector lineStart = prev.p; - const Vector lineDirection = (p->p).Minus(prev.p); std::sort(il.begin(), il.end(), [&](const SInter &a, const SInter &b) { double ta = (a.p.Minus(lineStart)).DivProjected(lineDirection); double tb = (b.p.Minus(lineStart)).DivProjected(lineDirection); @@ -122,20 +196,24 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, ret.pts.Add(p); prev = *p; } + vertpts.Clear(); return ret; } void SShell::CopyCurvesSplitAgainst(bool opA, SShell *agnst, SShell *into) { - SCurve *sc; - for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) { +#pragma omp parallel for + for(int i=0; iMakeCopySplitAgainst(agnst, NULL, surface.FindById(sc->surfA), surface.FindById(sc->surfB)); scn.source = opA ? SCurve::Source::A : SCurve::Source::B; - - hSCurve hsc = into->curve.AddAndAssignId(&scn); - // And note the new ID so that we can rewrite the trims appropriately - sc->newH = hsc; +#pragma omp critical + { + hSCurve hsc = into->curve.AddAndAssignId(&scn); + // And note the new ID so that we can rewrite the trims appropriately + sc->newH = hsc; + } } } @@ -192,25 +270,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; + } + + case SSurface::CombineAs::INTERSECTION: + if(opA) { + return inShell; } else { - return inShell || inSame; + return inShell || coincSame; } default: ssassert(false, "Unexpected combine type"); @@ -233,29 +321,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; } } @@ -353,13 +441,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); @@ -368,11 +455,17 @@ void SSurface::EdgeNormalsWithinSurface(Point2d auv, Point2d buv, enxyz = (ab.Cross(*surfn)).WithMagnitude(SS.ChordTolMm()); // And based on that, compute the edge's inner normal in uv space. This // vector is perpendicular to the edge in xyz, but not necessarily in uv. - Vector tu, tv; + Vector tu, tv, tx, ty; TangentsAt(muv.x, muv.y, &tu, &tv); + Vector n = tu.Cross(tv); + // since tu and tv may not be orthogonal, use y in place of v, x in place of u. + // |y| = |v|sin(theta) where theta is the angle between tu and tv. + ty = n.Cross(tu).ScaledBy(1.0/tu.MagSquared()); + tx = tv.Cross(n).ScaledBy(1.0/tv.MagSquared()); + Point2d enuv; - enuv.x = enxyz.Dot(tu) / tu.MagSquared(); - enuv.y = enxyz.Dot(tv) / tv.MagSquared(); + enuv.x = enxyz.Dot(tx) / tx.MagSquared(); + enuv.y = enxyz.Dot(ty) / ty.MagSquared(); // Compute the inner and outer normals of this edge (within the srf), // in xyz space. These are not necessarily antiparallel, if the @@ -381,6 +474,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; } //----------------------------------------------------------------------------- @@ -392,7 +488,8 @@ void SSurface::EdgeNormalsWithinSurface(Point2d auv, Point2d buv, SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, SShell *sha, SShell *shb, SShell *into, - SSurface::CombineAs type) + SSurface::CombineAs type, + int dbg_index) { bool opA = (parent == sha); SShell *agnst = opA ? shb : sha; @@ -429,46 +526,47 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, SEdgeList inter = {}; SSurface *ss; - for(ss = agnst->surface.First(); ss; ss = agnst->surface.NextAfter(ss)) { - SCurve *sc; - for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) { - if(sc->source != SCurve::Source::INTERSECTION) continue; - if(opA) { - if(sc->surfA != h || sc->surfB != ss->h) continue; - } else { - if(sc->surfB != h || sc->surfA != ss->h) continue; - } - - int i; - 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)); - ss->ClosestPointTo(b, &(buv.x), &(buv.y)); - - SBspUv::Class c = (ss->bsp) ? ss->bsp->ClassifyEdge(auv, buv, ss) : SBspUv::Class::OUTSIDE; - if(c != SBspUv::Class::OUTSIDE) { - Vector ta = Vector::From(0, 0, 0); - Vector tb = Vector::From(0, 0, 0); - ret.ClosestPointTo(a, &(ta.x), &(ta.y)); - ret.ClosestPointTo(b, &(tb.x), &(tb.y)); - - Vector tn = ret.NormalAt(ta.x, ta.y); - Vector sn = ss->NormalAt(auv.x, auv.y); - - // We are subtracting the portion of our surface that - // lies in the shell, so the in-plane edge normal should - // point opposite to the surface normal. - bool bkwds = true; - if((tn.Cross(b.Minus(a))).Dot(sn) < 0) bkwds = !bkwds; - if(type == SSurface::CombineAs::DIFFERENCE && !opA) bkwds = !bkwds; - if(bkwds) { - inter.AddEdge(tb, ta, sc->h.v, 1); - } else { - inter.AddEdge(ta, tb, sc->h.v, 0); - } + 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); + } else { + 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; + + Point2d auv, buv; + ss->ClosestPointTo(a, &(auv.x), &(auv.y)); + ss->ClosestPointTo(b, &(buv.x), &(buv.y)); + + SBspUv::Class c = (ss->bsp) ? ss->bsp->ClassifyEdge(auv, buv, ss) : SBspUv::Class::OUTSIDE; + if(c != SBspUv::Class::OUTSIDE) { + Vector ta = Vector::From(0, 0, 0); + Vector tb = Vector::From(0, 0, 0); + ret.ClosestPointTo(a, &(ta.x), &(ta.y)); + ret.ClosestPointTo(b, &(tb.x), &(tb.y)); + + Vector tn = ret.NormalAt(ta.x, ta.y); + Vector sn = ss->NormalAt(auv.x, auv.y); + + // We are subtracting the portion of our surface that + // lies in the shell, so the in-plane edge normal should + // point opposite to the surface normal. + bool bkwds = true; + if((tn.Cross(b.Minus(a))).Dot(sn) < 0) bkwds = !bkwds; + if((type == SSurface::CombineAs::DIFFERENCE && !opA) || + (type == SSurface::CombineAs::INTERSECTION)) { // Invert all newly created edges for intersection + bkwds = !bkwds; + } + if(bkwds) { + inter.AddEdge(tb, ta, sc.h.v, 1); + } else { + inter.AddEdge(ta, tb, sc.h.v, 0); } } } @@ -519,8 +617,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, @@ -573,16 +671,18 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, // we can get duplicate edges if our surface intersects the other shell // at an edge, so that both surfaces intersect coincident (and both // generate an intersection edge). - final.CullExtraneousEdges(); + final.CullExtraneousEdges(/*both=*/true); // Use our reassembled edges to trim the new surface. ret.TrimFromEdgeList(&final, /*asUv=*/true); SPolygon poly = {}; final.l.ClearTags(); - if(!final.AssemblePolygon(&poly, NULL, /*keepDir=*/true)) { + if(!final.AssemblePolygon(&poly, NULL, /*keepDir=*/true)) +#pragma omp critical + { into->booleanFailed = true; - dbp("failed: I=%d, avoid=%d", I, choosing.l.n); + dbp("failed: I=%d, avoid=%d", I+dbg_index, choosing.l.n); DEBUGEDGELIST(&final, &ret); } poly.Clear(); @@ -595,32 +695,38 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, } void SShell::CopySurfacesTrimAgainst(SShell *sha, SShell *shb, SShell *into, SSurface::CombineAs type) { - SSurface *ss; - for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { - SSurface ssn; - ssn = ss->MakeCopyTrimAgainst(this, sha, shb, into, type); - ss->newH = into->surface.AddAndAssignId(&ssn); - I++; + std::vector ssn(surface.n); +#pragma omp parallel for + for (int i = 0; i < surface.n; i++) + { + SSurface *ss = &surface[i]; + ssn[i] = ss->MakeCopyTrimAgainst(this, sha, shb, into, type, i); + } + + for (int i = 0; i < surface.n; i++) + { + surface[i].newH = into->surface.AddAndAssignId(&ssn[i]); } + I += surface.n; } void SShell::MakeIntersectionCurvesAgainst(SShell *agnst, SShell *into) { - SSurface *sa; - for(sa = surface.First(); sa; sa = surface.NextAfter(sa)) { - SSurface *sb; - for(sb = agnst->surface.First(); sb; sb = agnst->surface.NextAfter(sb)){ +#pragma omp parallel for + for(int i = 0; i< surface.n; i++) { + SSurface *sa = &surface[i]; + + 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(); } } @@ -630,10 +736,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; } } @@ -655,32 +760,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); } } @@ -696,7 +801,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); @@ -705,12 +810,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 @@ -744,9 +848,9 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type) { // All of the BSP routines that we use to perform and accelerate polygon ops. //----------------------------------------------------------------------------- void SShell::MakeClassifyingBsps(SShell *useCurvesFrom) { - SSurface *ss; - for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { - ss->MakeClassifyingBsp(this, useCurvesFrom); +#pragma omp parallel for + for(int i = 0; imore = more; more = m; } else if(fabs(dea) < LENGTH_EPS) { - // Point A lies on this lie, but point B does not + // Point A lies on this line, but point B does not if(deb > 0) { pos = InsertOrCreateEdge(pos, ea, eb, srf); } else { neg = InsertOrCreateEdge(neg, ea, eb, srf); } } else if(fabs(deb) < LENGTH_EPS) { - // Point B lies on this lie, but point A does not + // Point B lies on this line, but point A does not if(dea > 0) { pos = InsertOrCreateEdge(pos, ea, eb, srf); } else { diff --git a/src/srf/curve.cpp b/src/srf/curve.cpp index 9bdcca289..382415b17 100644 --- a/src/srf/curve.cpp +++ b/src/srf/curve.cpp @@ -799,20 +799,37 @@ SSurface *SCurve::GetSurfaceB(SShell *a, SShell *b) const { // stuff in the Booleans. So remove them. //----------------------------------------------------------------------------- void SCurve::RemoveShortSegments(SSurface *srfA, SSurface *srfB) { - // Three, not two; curves are pwl'd to at least two edges (three points) - // even if not necessary, to avoid square holes. - if(pts.n <= 3) return; + if(pts.n <= 2) return; pts.ClearTags(); Vector prev = pts[0].p; + double tprev = 0; + double t = 0; + double tnext = 0; + int i, a; for(i = 1; i < pts.n - 1; i++) { SCurvePt *sct = &(pts[i]), *scn = &(pts[i+1]); + if(sct->vertex) { prev = sct->p; continue; } + + // 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) { + exact.ClosestPointTo(prev, &tprev, /*mustconverge=*/ true); + exact.ClosestPointTo(sct->p, &t, /*mustconverge=*/ true); + exact.ClosestPointTo(scn->p, &tnext, /*mustconverge=*/ true); + } + if ( (t - tprev > 0.05) && (tnext - t > 0.05) ) { + prev = sct->p; + continue; + } + bool mustKeep = false; // We must check against both surfaces; the piecewise linear edge @@ -826,7 +843,7 @@ void SCurve::RemoveShortSegments(SSurface *srfA, SSurface *srfB) { srf->ClosestPointTo(prev, &(puv.x), &(puv.y)); srf->ClosestPointTo(scn->p, &(nuv.x), &(nuv.y)); - if(srf->ChordToleranceForEdge(nuv, puv) > SS.ChordTolMm()) { + if(srf->ChordToleranceForEdge(nuv, puv) > SS.ChordTolMm() ) { mustKeep = true; } } diff --git a/src/srf/merge.cpp b/src/srf/merge.cpp index a91a307fa..42ae55906 100644 --- a/src/srf/merge.cpp +++ b/src/srf/merge.cpp @@ -58,10 +58,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; } } diff --git a/src/srf/ratpoly.cpp b/src/srf/ratpoly.cpp index d4fd703f8..46180a55e 100644 --- a/src/srf/ratpoly.cpp +++ b/src/srf/ratpoly.cpp @@ -13,84 +13,27 @@ // and convergence should be fast by now. #define RATPOLY_EPS (LENGTH_EPS/(1e2)) -double SolveSpace::Bernstein(int k, int deg, double t) -{ - if(k > deg || k < 0) return 0; - - switch(deg) { - case 0: - return 1; - - case 1: - if(k == 0) { - return (1 - t); - } else if(k == 1) { - return t; - } - break; - - case 2: - if(k == 0) { - return (1 - t)*(1 - t); - } else if(k == 1) { - return 2*(1 - t)*t; - } else if(k == 2) { - return t*t; - } - break; - - case 3: - if(k == 0) { - return (1 - t)*(1 - t)*(1 - t); - } else if(k == 1) { - return 3*(1 - t)*(1 - t)*t; - } else if(k == 2) { - return 3*(1 - t)*t*t; - } else if(k == 3) { - return t*t*t; - } - break; - } - ssassert(false, "Unexpected degree of spline"); +static inline double Bernstein(int k, int deg, double t) { +// indexed by [degree][k][exponent] + static const double bernstein_coeff[4][4][4] = { + { { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 } }, + { { 1.0,-1.0,0.0,0.0 }, { 0.0,1.0,0.0,0.0 }, { 0.0,0.0,0.0,0.0 }, { 0.0,0.0,0.0,0.0 } }, + { { 1.0,-2.0,1.0,0.0 }, { 0.0,2.0,-2.0,0.0 },{ 0.0,0.0,1.0,0.0 }, { 0.0,0.0,0.0,0.0 } }, + { { 1.0,-3.0,3.0,-1.0 },{ 0.0,3.0,-6.0,3.0 },{ 0.0,0.0,3.0,-3.0}, { 0.0,0.0,0.0,1.0 } } }; + + const double *c = bernstein_coeff[deg][k]; + return (((c[3]*t+c[2])*t)+c[1])*t+c[0]; } -double SolveSpace::BernsteinDerivative(int k, int deg, double t) -{ - switch(deg) { - case 0: - return 0; - - case 1: - if(k == 0) { - return -1; - } else if(k == 1) { - return 1; - } - break; - - case 2: - if(k == 0) { - return -2 + 2*t; - } else if(k == 1) { - return 2 - 4*t; - } else if(k == 2) { - return 2*t; - } - break; +static inline double BernsteinDerivative(int k, int deg, double t) { + static const double bernstein_derivative_coeff[4][4][3] = { + { { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 } }, + { { -1.0,0.0,0.0 }, { 1.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 } }, + { { -2.0,2.0,0.0 }, { 2.0,-4.0,0.0 },{ 0.0,2.0,0.0 }, { 0.0,0.0,0.0 } }, + { { -3.0,6.0,-3.0 },{ 3.0,-12.0,9.0 },{ 0.0,6.0,-9.0}, { 0.0,0.0,3.0 } } }; - case 3: - if(k == 0) { - return -3 + 6*t - 3*t*t; - } else if(k == 1) { - return 3 - 12*t + 9*t*t; - } else if(k == 2) { - return 6*t - 9*t*t; - } else if(k == 3) { - return 3*t*t; - } - break; - } - ssassert(false, "Unexpected degree of spline"); + const double *c = bernstein_derivative_coeff[deg][k]; + return ((c[2]*t)+c[1])*t+c[0]; } Vector SBezier::PointAt(double t) const { @@ -226,18 +169,18 @@ void SBezier::SplitAt(double t, SBezier *bef, SBezier *aft) const { } } -void SBezier::MakePwlInto(SEdgeList *sel, double chordTol) const { +void SBezier::MakePwlInto(SEdgeList *sel, double chordTol, double max_dt) const { List lv = {}; - MakePwlInto(&lv, chordTol); + MakePwlInto(&lv, chordTol, max_dt); int i; for(i = 1; i < lv.n; i++) { sel->AddEdge(lv[i-1], lv[i]); } lv.Clear(); } -void SBezier::MakePwlInto(List *l, double chordTol) const { +void SBezier::MakePwlInto(List *l, double chordTol, double max_dt) const { List lv = {}; - MakePwlInto(&lv, chordTol); + MakePwlInto(&lv, chordTol, max_dt); int i; for(i = 0; i < lv.n; i++) { SCurvePt scpt; @@ -248,32 +191,42 @@ void SBezier::MakePwlInto(List *l, double chordTol) const { } lv.Clear(); } -void SBezier::MakePwlInto(SContour *sc, double chordTol) const { +void SBezier::MakePwlInto(SContour *sc, double chordTol, double max_dt) const { List lv = {}; - MakePwlInto(&lv, chordTol); + MakePwlInto(&lv, chordTol, max_dt); int i; for(i = 0; i < lv.n; i++) { sc->AddPoint(lv[i]); } lv.Clear(); } -void SBezier::MakePwlInto(List *l, double chordTol) const { +//-------------------------------------------------------------------------------------- +// all variants of MakePwlInto come here. Split a rational Bezier into Piecewise Linear +// segments that don't deviate from the actual curve by more than the chordTol distance. +// max_dt allows to force curves to be split into spans of no more than a certain +// length based on t-parameter. RemoveShortSegments() may delete points when dt <= 0.1 +//-------------------------------------------------------------------------------------- +void SBezier::MakePwlInto(List *l, double chordTol, double max_dt) const { if(EXACT(chordTol == 0)) { // Use the default chord tolerance. chordTol = SS.ChordTolMm(); } + // Never do fewer than three intermediate points for curves; people seem to get + // unhappy when their circles turn into squares, but maybe less + // unhappy with octagons. Now 16-gons. + if (EXACT(max_dt == 0.0)) { + max_dt = (deg == 1) ? 1.0 : 0.25; + } l->Add(&(ctrl[0])); - if(deg == 1) { + // don't split first degee (lines) unless asked to by the caller via max_dt + if((deg == 1) && (max_dt >= 1.0)) { l->Add(&(ctrl[1])); } else { - // Never do fewer than one intermediate point; people seem to get - // unhappy when their circles turn into squares, but maybe less - // unhappy with octagons. - MakePwlInitialWorker(l, 0.0, 0.5, chordTol); - MakePwlInitialWorker(l, 0.5, 1.0, chordTol); + MakePwlInitialWorker(l, 0.0, 0.5, chordTol, max_dt); + MakePwlInitialWorker(l, 0.5, 1.0, chordTol, max_dt); } } -void SBezier::MakePwlWorker(List *l, double ta, double tb, double chordTol) const +void SBezier::MakePwlWorker(List *l, double ta, double tb, double chordTol, double max_dt) const { Vector pa = PointAt(ta); Vector pb = PointAt(tb); @@ -282,16 +235,16 @@ void SBezier::MakePwlWorker(List *l, double ta, double tb, double chordT double d = pm.DistanceToLine(pa, pb.Minus(pa)); double step = 1.0/SS.GetMaxSegments(); - if((tb - ta) < step || d < chordTol) { + if(((tb - ta) < step || d < chordTol) && ((tb-ta) <= max_dt) ) { // A previous call has already added the beginning of our interval. l->Add(&pb); } else { double tm = (ta + tb) / 2; - MakePwlWorker(l, ta, tm, chordTol); - MakePwlWorker(l, tm, tb, chordTol); + MakePwlWorker(l, ta, tm, chordTol, max_dt); + MakePwlWorker(l, tm, tb, chordTol, max_dt); } } -void SBezier::MakePwlInitialWorker(List *l, double ta, double tb, double chordTol) const +void SBezier::MakePwlInitialWorker(List *l, double ta, double tb, double chordTol, double max_dt) const { Vector pa = PointAt(ta); Vector pb = PointAt(tb); @@ -312,13 +265,13 @@ void SBezier::MakePwlInitialWorker(List *l, double ta, double tb, double }); double step = 1.0/SS.GetMaxSegments(); - if((tb - ta) < step || d < chordTol) { + if( ((tb - ta) < step || d < chordTol) && ((tb-ta) <= max_dt) ) { // A previous call has already added the beginning of our interval. l->Add(&pb); } else { double tm = (ta + tb) / 2; - MakePwlWorker(l, ta, tm, chordTol); - MakePwlWorker(l, tm, tb, chordTol); + MakePwlWorker(l, ta, tm, chordTol, max_dt); + MakePwlWorker(l, tm, tb, chordTol, max_dt); } } @@ -375,7 +328,7 @@ Vector SSurface::PointAt(double u, double v) const { return num; } -void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv) const { +void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv, bool retry) const { Vector num = Vector::From(0, 0, 0), num_u = Vector::From(0, 0, 0), num_v = Vector::From(0, 0, 0); @@ -407,6 +360,12 @@ void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv) const { *tv = ((num_v.ScaledBy(den)).Minus(num.ScaledBy(den_v))); *tv = tv->ScaledBy(1.0/(den*den)); + + // Tangent is zero at sungularities like the north pole. Move away a bit and retry. + if(tv->Equals(Vector::From(0,0,0)) && retry) + TangentsAt(u+(0.5-u)*0.00001, v, tu, tv, false); + if(tu->Equals(Vector::From(0,0,0)) && retry) + TangentsAt(u, v+(0.5-v)*0.00001, tu, tv, false); } Vector SSurface::NormalAt(Point2d puv) const { @@ -439,9 +398,14 @@ void SSurface::ClosestPointTo(Vector p, double *u, double *v, bool mustConverge) bu = (ctrl[1][0]).Minus(orig), bv = (ctrl[0][1]).Minus(orig); if((ctrl[1][1]).Equals(orig.Plus(bu).Plus(bv))) { + + Vector n = bu.Cross(bv); + Vector ty = n.Cross(bu).ScaledBy(1.0/bu.MagSquared()); + Vector tx = bv.Cross(n).ScaledBy(1.0/bv.MagSquared()); + Vector dp = p.Minus(orig); - *u = dp.Dot(bu) / bu.MagSquared(); - *v = dp.Dot(bv) / bv.MagSquared(); + *u = dp.Dot(bu) / tx.MagSquared(); + *v = dp.Dot(bv) / ty.MagSquared(); return; } } @@ -484,7 +448,16 @@ void SSurface::ClosestPointTo(Vector p, double *u, double *v, bool mustConverge) } // If we failed to converge, then at least don't return NaN. - if(isnan(*u) || isnan(*v)) { + if(mustConverge) { +// This is expected not to converge when the target point is not on the surface but nearby. +// let's not pollute the output window for normal use. +// Vector p0 = PointAt(*u, *v); +// dbp("didn't converge"); +// dbp("have %.3f %.3f %.3f", CO(p0)); +// dbp("want %.3f %.3f %.3f", CO(p)); +// dbp("distance = %g", (p.Minus(p0)).Magnitude()); + } + if(IsReasonable(*u) || IsReasonable(*v)) { *u = *v = 0; } } @@ -501,31 +474,37 @@ bool SSurface::ClosestPointNewton(Vector p, double *u, double *v, bool mustConve } } - Vector tu, tv; + Vector tu, tv, tx, ty; TangentsAt(*u, *v, &tu, &tv); + Vector n = tu.Cross(tv); + // since tu and tv may not be orthogonal, use y in place of v. + // |y| = |v|sin(theta) where theta is the angle between tu and tv. + ty = n.Cross(tu).ScaledBy(1.0/tu.MagSquared()); + tx = tv.Cross(n).ScaledBy(1.0/tv.MagSquared()); // Project the point into a plane through p0, with basis tu, tv; a // second-order thing would converge faster but needs second // derivatives. Vector dp = p.Minus(p0); - double du = dp.Dot(tu), dv = dp.Dot(tv); - *u += du / (tu.MagSquared()); - *v += dv / (tv.MagSquared()); - } + double du = dp.Dot(tx), + dv = dp.Dot(ty); + *u += du / (tx.MagSquared()); + *v += dv / (ty.MagSquared()); + + if (*u < 0.0) *u = 0.0; + else if (*u > 1.0) *u = 1.0; + if (*v < 0.0) *v = 0.0; + else if (*v > 1.0) *v = 1.0; - if(mustConverge) { - dbp("didn't converge"); - dbp("have %.3f %.3f %.3f", CO(p0)); - dbp("want %.3f %.3f %.3f", CO(p)); - dbp("distance = %g", (p.Minus(p0)).Magnitude()); } + return false; } bool SSurface::PointIntersectingLine(Vector p0, Vector p1, double *u, double *v) const { int i; - for(i = 0; i < 15; i++) { + for(i = 0; i < 20; i++) { Vector pi, p, tu, tv; p = PointAt(*u, *v); TangentsAt(*u, *v, &tu, &tv); @@ -535,18 +514,25 @@ bool SSurface::PointIntersectingLine(Vector p0, Vector p1, double *u, double *v) bool parallel; pi = Vector::AtIntersectionOfPlaneAndLine(n, d, p0, p1, ¶llel); - if(parallel) break; + if(parallel) { + dbp("parallel (surface intersecting line)"); + break; + } // Check for convergence if(pi.Equals(p, RATPOLY_EPS)) return true; + n = tu.Cross(tv); + Vector ty = n.Cross(tu).ScaledBy(1.0/tu.MagSquared()); + Vector tx = tv.Cross(n).ScaledBy(1.0/tv.MagSquared()); + // Adjust our guess and iterate Vector dp = pi.Minus(p); - double du = dp.Dot(tu), dv = dp.Dot(tv); - *u += du / (tu.MagSquared()); - *v += dv / (tv.MagSquared()); + double du = dp.Dot(tx), dv = dp.Dot(ty); + *u += du / tx.MagSquared(); + *v += dv / ty.MagSquared(); } -// dbp("didn't converge (surface intersecting line)"); + dbp("didn't converge (surface intersecting line)"); return false; } @@ -582,10 +568,14 @@ Vector SSurface::ClosestPointOnThisAndSurface(SSurface *srf2, Vector p) { // Adjust our guess and iterate for(j = 0; j < 2; j++) { + Vector n = tu[j].Cross(tv[j]); + Vector ty = n.Cross(tu[j]).ScaledBy(1.0/tu[j].MagSquared()); + Vector tx = tv[j].Cross(n).ScaledBy(1.0/tv[j].MagSquared()); + Vector dc = pc.Minus(cp[j]); - double du = dc.Dot(tu[j]), dv = dc.Dot(tv[j]); - puv[j].x += du / ((tu[j]).MagSquared()); - puv[j].y += dv / ((tv[j]).MagSquared()); + double du = dc.Dot(tx), dv = dc.Dot(ty); + puv[j].x += du / tx.MagSquared(); + puv[j].y += dv / ty.MagSquared(); } } if(i >= 10) { @@ -634,15 +624,78 @@ void SSurface::PointOnSurfaces(SSurface *s1, SSurface *s2, double *up, double *v Vector pi = Vector::AtIntersectionOfPlanes(n[0], d[0], n[1], d[1], n[2], d[2], ¶llel); - if(parallel) break; + + if(parallel) { // lets try something else for parallel planes + pi = p[0].Plus(p[1]).Plus(p[2]).ScaledBy(1.0/3.0); + } for(j = 0; j < 3; j++) { + Vector n = tu[j].Cross(tv[j]); + Vector ty = n.Cross(tu[j]).ScaledBy(1.0/tu[j].MagSquared()); + Vector tx = tv[j].Cross(n).ScaledBy(1.0/tv[j].MagSquared()); + Vector dp = pi.Minus(p[j]); - double du = dp.Dot(tu[j]), dv = dp.Dot(tv[j]); - u[j] += du / (tu[j]).MagSquared(); - v[j] += dv / (tv[j]).MagSquared(); + double du = dp.Dot(tx), dv = dp.Dot(ty); + + u[j] += du / tx.MagSquared(); + v[j] += dv / ty.MagSquared(); } } dbp("didn't converge (three surfaces intersecting)"); } +void SSurface::PointOnCurve(const SBezier *curve, double *up, double *vp) +{ + Vector tu,tv,n; + double u = *up, v = *vp; + Vector ps = PointAt(u, v); + // Get initial guesses for t on the curve + double tCurve = 0.5; + curve->ClosestPointTo(ps, &tCurve, /*mustConverge=*/false); + if(tCurve < 0.0) tCurve = 0.0; + if(tCurve > 1.0) tCurve = 1.0; + + for(int i = 0; i < 30; i++) { + // Approximate the surface by a plane + Vector ps = PointAt(u, v); + TangentsAt(u, v, &tu, &tv); + n = tu.Cross(tv).WithMagnitude(1); + + // point on curve and tangent line direction + Vector pc = curve->PointAt(tCurve); + Vector tc = curve->TangentAt(tCurve); + + if(ps.Equals(pc, RATPOLY_EPS)) { + *up = u; + *vp = v; + return; + } + + //pi is where the curve tangent line intersects the surface tangent plane + Vector pi; + double d = tc.Dot(n); + if (fabs(d) < 1e-10) { // parallel line and plane, guess the average rather than fail + pi = pc.Plus(ps).ScaledBy(0.5); + } else { + pi = pc.Minus(tc.ScaledBy(pc.Minus(ps).Dot(n)/d)); + } + + // project the point onto the tangent plane and line + { + Vector n = tu.Cross(tv); + Vector ty = n.Cross(tu).ScaledBy(1.0/tu.MagSquared()); + Vector tx = tv.Cross(n).ScaledBy(1.0/tv.MagSquared()); + + Vector dp = pi.Minus(ps); + double du = dp.Dot(tx), dv = dp.Dot(ty); + + u += du / tx.MagSquared(); + v += dv / ty.MagSquared(); + } + tCurve += pi.Minus(pc).Dot(tc) / tc.MagSquared(); + if(tCurve < 0.0) tCurve = 0.0; + if(tCurve > 1.0) tCurve = 1.0; + } + dbp("didn't converge (surface and curve intersecting)"); +} + diff --git a/src/srf/raycast.cpp b/src/srf/raycast.cpp index 4d6533778..acbcd8fdc 100644 --- a/src/srf/raycast.cpp +++ b/src/srf/raycast.cpp @@ -130,39 +130,38 @@ void SSurface::SplitInHalf(bool byU, SSurface *sa, SSurface *sb) { sa->degn = sb->degn = degn; // by de Casteljau's algorithm in a projective space; so we must work - // on points (w*x, w*y, w*z, w) - WeightControlPoints(); + // on points (w*x, w*y, w*z, w) so create a temporary copy + SSurface st; + st = *this; + st.WeightControlPoints(); switch(byU ? degm : degn) { case 1: - sa->CopyRowOrCol (byU, 0, this, 0); - sb->CopyRowOrCol (byU, 1, this, 1); + sa->CopyRowOrCol (byU, 0, &st, 0); + sb->CopyRowOrCol (byU, 1, &st, 1); - sa->BlendRowOrCol(byU, 1, this, 0, this, 1); - sb->BlendRowOrCol(byU, 0, this, 0, this, 1); + sa->BlendRowOrCol(byU, 1, &st, 0, &st, 1); + sb->BlendRowOrCol(byU, 0, &st, 0, &st, 1); break; case 2: - sa->CopyRowOrCol (byU, 0, this, 0); - sb->CopyRowOrCol (byU, 2, this, 2); + sa->CopyRowOrCol (byU, 0, &st, 0); + sb->CopyRowOrCol (byU, 2, &st, 2); - sa->BlendRowOrCol(byU, 1, this, 0, this, 1); - sb->BlendRowOrCol(byU, 1, this, 1, this, 2); + sa->BlendRowOrCol(byU, 1, &st, 0, &st, 1); + sb->BlendRowOrCol(byU, 1, &st, 1, &st, 2); sa->BlendRowOrCol(byU, 2, sa, 1, sb, 1); sb->BlendRowOrCol(byU, 0, sa, 1, sb, 1); break; case 3: { - SSurface st; - st.degm = degm; st.degn = degn; + sa->CopyRowOrCol (byU, 0, &st, 0); + sb->CopyRowOrCol (byU, 3, &st, 3); - sa->CopyRowOrCol (byU, 0, this, 0); - sb->CopyRowOrCol (byU, 3, this, 3); - - sa->BlendRowOrCol(byU, 1, this, 0, this, 1); - sb->BlendRowOrCol(byU, 2, this, 2, this, 3); - st. BlendRowOrCol(byU, 0, this, 1, this, 2); // scratch var + sa->BlendRowOrCol(byU, 1, &st, 0, &st, 1); + sb->BlendRowOrCol(byU, 2, &st, 2, &st, 3); + st. BlendRowOrCol(byU, 0, &st, 1, &st, 2); // use row/col 0 as scratch sa->BlendRowOrCol(byU, 2, sa, 1, &st, 0); sb->BlendRowOrCol(byU, 1, sb, 2, &st, 0); @@ -177,7 +176,6 @@ void SSurface::SplitInHalf(bool byU, SSurface *sa, SSurface *sb) { sa->UnWeightControlPoints(); sb->UnWeightControlPoints(); - UnWeightControlPoints(); } //----------------------------------------------------------------------------- @@ -383,9 +381,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); } } @@ -401,14 +398,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; } } @@ -421,6 +418,11 @@ SShell::Class SShell::ClassifyRegion(Vector edge_n, Vector inter_surf_n, // using the closest intersection point. If the ray hits a surface on edge, // then just reattempt in a different random direction. //----------------------------------------------------------------------------- + +// table of vectors in 6 arbitrary directions covering 4 of the 8 octants. +// use overlapping sets of 3 to reduce memory usage. +static const double Random[8] = {1.278, 5.0103, 9.427, -2.331, 7.13, 2.954, 5.034, -4.777}; + bool SShell::ClassifyEdge(Class *indir, Class *outdir, Vector ea, Vector eb, Vector p, @@ -428,16 +430,13 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, { List l = {}; - srand(0); - // 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)) || @@ -447,9 +446,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. @@ -475,7 +474,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 @@ -485,25 +484,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. @@ -519,25 +518,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); @@ -551,15 +550,15 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, // Cast a ray in a random direction (two-sided so that we test if // the point lies on a surface, but use only one side for in/out // testing) - Vector ray = Vector::From(Random(1), Random(1), Random(1)); + Vector ray = Vector::From(Random[cnt], Random[cnt+1], Random[cnt+2]); AllPointsIntersecting( p.Minus(ray), p.Plus(ray), &l, /*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; @@ -585,11 +584,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; } @@ -600,7 +599,8 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, // then our ray always lies on edge, and that's okay. Otherwise // try again in a different random direction. if(!onEdge) break; - if(cnt++ > 5) { + cnt++; + if(cnt > 5) { dbp("can't find a ray that doesn't hit on edge!"); dbp("on edge = %d, edge_inters = %d", onEdge, edge_inters); SS.nakedEdges.AddEdge(ea, eb); diff --git a/src/srf/shell.cpp b/src/srf/shell.cpp new file mode 100644 index 000000000..ca3b75ee9 --- /dev/null +++ b/src/srf/shell.cpp @@ -0,0 +1,614 @@ +//----------------------------------------------------------------------------- +// Anything involving NURBS shells (i.e., shells); except +// for the real math, which is in ratpoly.cpp. +// +// Copyright 2008-2013 Jonathan Westhues. +//----------------------------------------------------------------------------- +#include "../solvespace.h" + +typedef struct { + hSCurve hc; + hSSurface hs; +} TrimLine; + + +void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, RgbaColor color) +{ + // Make the extrusion direction consistent with respect to the normal + // of the sketch we're extruding. + if((t0.Minus(t1)).Dot(sbls->normal) < 0) { + swap(t0, t1); + } + + // Define a coordinate system to contain the original sketch, and get + // a bounding box in that csys + Vector n = sbls->normal.ScaledBy(-1); + Vector u = n.Normal(0), v = n.Normal(1); + Vector orig = sbls->point; + double umax = VERY_NEGATIVE, umin = VERY_POSITIVE; + sbls->GetBoundingProjd(u, orig, &umin, &umax); + double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE; + sbls->GetBoundingProjd(v, orig, &vmin, &vmax); + // and now fix things up so that all u and v lie between 0 and 1 + orig = orig.Plus(u.ScaledBy(umin)); + orig = orig.Plus(v.ScaledBy(vmin)); + u = u.ScaledBy(umax - umin); + v = v.ScaledBy(vmax - vmin); + + // So we can now generate the top and bottom surfaces of the extrusion, + // planes within a translated (and maybe mirrored) version of that csys. + SSurface s0, s1; + s0 = SSurface::FromPlane(orig.Plus(t0), u, v); + s0.color = color; + s1 = SSurface::FromPlane(orig.Plus(t1).Plus(u), u.ScaledBy(-1), v); + s1.color = color; + hSSurface hs0 = surface.AddAndAssignId(&s0), + hs1 = surface.AddAndAssignId(&s1); + + // Now go through the input curves. For each one, generate its surface + // of extrusion, its two translated trim curves, and one trim line. We + // go through by loops so that we can assign the lines correctly. + SBezierLoop *sbl; + for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { + SBezier *sb; + List 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(); +} diff --git a/src/srf/surface.cpp b/src/srf/surface.cpp index a61713c38..18edabf61 100644 --- a/src/srf/surface.cpp +++ b/src/srf/surface.cpp @@ -489,578 +489,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 = 1e-10, umin = 1e10; - sbls->GetBoundingProjd(u, orig, &umin, &umax); - double vmax = 1e-10, vmin = 1e10; - 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; - - if(CheckNormalAxisRelationship(sbls, pt, axis, anglef-angles, distf-dists)) { - swap(angles, anglef); - swap(dists, distf); - dist = -dist; - wedge = -wedge; - } - - // 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 = 1e-10, umin = 1e10; - sbls->GetBoundingProjd(u, orig, &umin, &umax); - double vmax = 1e-10, vmin = 1e10; - 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; - 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; - hSSurface hs0 = surface.AddAndAssignId(&s0), 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 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); - (sc.exact).MakePwlInto(&(sc.pts)); - - // the surfaces already exist 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]; - (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(); - } - - 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) { - SSurface *s; - for(s = surface.First(); s; s = surface.NextAfter(s)) { - s->TriangulateInto(this, sm); - } -} - -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(); -} diff --git a/src/srf/surface.h b/src/srf/surface.h index 99418c53e..b1a7cd90d 100644 --- a/src/srf/surface.h +++ b/src/srf/surface.h @@ -10,10 +10,6 @@ #ifndef SOLVESPACE_SURFACE_H #define SOLVESPACE_SURFACE_H -// Utility functions, Bernstein polynomials of order 1-3 and their derivatives. -double Bernstein(int k, int deg, double t); -double BernsteinDerivative(int k, int deg, double t); - class SBezierList; class SSurface; class SCurvePt; @@ -96,12 +92,12 @@ class SBezier { Vector Start() const; Vector Finish() const; bool Equals(SBezier *b) const; - void MakePwlInto(SEdgeList *sel, double chordTol=0) const; - void MakePwlInto(List *l, double chordTol=0) const; - void MakePwlInto(SContour *sc, double chordTol=0) const; - void MakePwlInto(List *l, double chordTol=0) const; - void MakePwlWorker(List *l, double ta, double tb, double chordTol) const; - void MakePwlInitialWorker(List *l, double ta, double tb, double chordTol) const; + void MakePwlInto(SEdgeList *sel, double chordTol=0, double max_dt=0.0) const; + void MakePwlInto(List *l, double chordTol=0, double max_dt=0.0) const; + void MakePwlInto(SContour *sc, double chordTol=0, double max_dt=0.0) const; + void MakePwlInto(List *l, double chordTol=0, double max_dt=0.0) const; + void MakePwlWorker(List *l, double ta, double tb, double chordTol, double max_dt) const; + void MakePwlInitialWorker(List *l, double ta, double tb, double chordTol, double max_dt) const; void MakeNonrationalCubicInto(SBezierList *bl, double tolerance, int depth = 0) const; void AllIntersectionsWith(const SBezier *sbb, SPointList *spl) const; @@ -223,6 +219,7 @@ class SCurve { SSurface *GetSurfaceB(SShell *a, SShell *b) const; void Clear(); + void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const; }; // A segment of a curve by which a surface is trimmed: indicates which curve, @@ -260,7 +257,7 @@ class SSurface { enum class CombineAs : uint32_t { UNION = 10, DIFFERENCE = 11, - INTERSECT = 12 + INTERSECTION = 12 }; int tag; @@ -303,7 +300,7 @@ class SSurface { SShell *shell, SShell *sha, SShell *shb); void FindChainAvoiding(SEdgeList *src, SEdgeList *dest, SPointList *avoid); SSurface MakeCopyTrimAgainst(SShell *parent, SShell *a, SShell *b, - SShell *into, SSurface::CombineAs type); + SShell *into, SSurface::CombineAs type, int dbg_index); void TrimFromEdgeList(SEdgeList *el, bool asUv); void IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, SShell *into); @@ -336,15 +333,17 @@ class SSurface { bool PointIntersectingLine(Vector p0, Vector p1, double *u, double *v) const; Vector ClosestPointOnThisAndSurface(SSurface *srf2, Vector p); void PointOnSurfaces(SSurface *s1, SSurface *s2, double *u, double *v); + void PointOnCurve(const SBezier *curve, double *up, double *vp); Vector PointAt(double u, double v) const; Vector PointAt(Point2d puv) const; - void TangentsAt(double u, double v, Vector *tu, Vector *tv) const; + void TangentsAt(double u, double v, Vector *tu, Vector *tv, bool retry=true) const; Vector NormalAt(Point2d puv) const; Vector NormalAt(double u, double v) const; bool LineEntirelyOutsideBbox(Vector a, Vector b, bool asSegment) const; void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const; bool CoincidentWithPlane(Vector n, double d) const; bool CoincidentWith(SSurface *ss, bool sameNormal) const; + bool ContainsPlaneCurve(SCurve *sc) const; bool IsExtrusion(SBezier *of, Vector *along) const; bool IsCylinder(Vector *axis, Vector *center, double *r, Vector *start, Vector *finish) const; @@ -366,8 +365,9 @@ class SSurface { void MakeClassifyingBsp(SShell *shell, SShell *useCurvesFrom); double ChordToleranceForEdge(Vector a, Vector b) const; void MakeTriangulationGridInto(List *l, double vs, double vf, - bool swapped) const; + bool swapped, int depth) const; Vector PointAtMaybeSwapped(double u, double v, bool swapped) const; + Vector NormalAtMaybeSwapped(double u, double v, bool swapped) const; void Reverse(); void Clear(); @@ -390,6 +390,7 @@ class SShell { void MakeFirstOrderRevolvedSurfaces(Vector pt, Vector axis, int i0); void MakeFromUnionOf(SShell *a, SShell *b); void MakeFromDifferenceOf(SShell *a, SShell *b); + void MakeFromIntersectionOf(SShell *a, SShell *b); void MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type); void CopyCurvesSplitAgainst(bool opA, SShell *agnst, SShell *into); void CopySurfacesTrimAgainst(SShell *sha, SShell *shb, SShell *into, SSurface::CombineAs type); @@ -406,10 +407,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, diff --git a/src/srf/surfinter.cpp b/src/srf/surfinter.cpp index 9f6c85cc4..5b6464dc2 100644 --- a/src/srf/surfinter.cpp +++ b/src/srf/surfinter.cpp @@ -23,23 +23,26 @@ 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; - for(se = into->curve.First(); se; se = into->curve.NextAfter(se)) { - if(se->isExact) { - if(sb->Equals(&(se->exact))) { - existing = se; - break; - } - if(sbrev.Equals(&(se->exact))) { - existing = se; - backwards = true; - break; +#pragma omp critical(into) + { + for(SCurve &se : into->curve) { + if(se.isExact) { + if(sb->Equals(&(se.exact))) { + existing = &se; + break; + } + if(sbrev.Equals(&(se.exact))) { + existing = &se; + backwards = true; + break; + } } } - } + }// end omp critical if(existing) { SCurvePt *v; for(v = existing->pts.First(); v; v = existing->pts.NextAfter(v)) { @@ -101,7 +104,10 @@ void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, "Unexpected zero-length edge"); split.source = SCurve::Source::INTERSECTION; - into->curve.AddAndAssignId(&split); +#pragma omp critical(into) + { + into->curve.AddAndAssignId(&split); + } } void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, @@ -307,6 +313,44 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, inters.Clear(); lv.Clear(); } else { + if((degm == 1 && degn == 1) || (b->degm == 1 && b->degn == 1)) { + // we should only be here if just one surface is a plane because the + // plane-plane case was already handled above. Need to check the other + // nonplanar surface for trim curves that lie in the plane and are not + // already trimming both surfaces. This happens when we cut a Lathe shell + // on one of the seams for example. + // This also seems necessary to merge some coincident surfaces. + SSurface *splane, *sext; + SShell *shext; + if(degm == 1 && degn == 1) { // this and other checks assume coplanar ctrl pts. + splane = this; + sext = b; + shext = agnstB; + } else { + splane = b; + sext = this; + shext = agnstA; + } + bool foundExact = false; + 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; + AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into); + foundExact = true; + } + } + // if we found at lest one of these we don't want to do the numerical + // intersection as well. Sometimes it will also find the same curve but + // with different PWLs and the polygon will fail to assemble. + if(foundExact) + return; + } + // Try intersecting the surfaces numerically, by a marching algorithm. // First, we find all the intersections between a surface and the // boundary of the other surface. @@ -341,7 +385,11 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, Vector p = si->p; double u, v; srfB->ClosestPointTo(p, &u, &v); - srfB->PointOnSurfaces(srfA, other, &u, &v); + if(sc->isExact) { + srfB->PointOnCurve(&(sc->exact), &u, &v); + } else { + srfB->PointOnSurfaces(srfA, other, &u, &v); + } p = srfB->PointAt(u, v); if(!spl.ContainsPoint(p)) { SPoint sp; @@ -408,13 +456,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(); @@ -452,7 +498,10 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, // And now we split and insert the curve SCurve split = sc.MakeCopySplitAgainst(agnstA, agnstB, this, b); sc.Clear(); - into->curve.AddAndAssignId(&split); +#pragma omp critical(into) + { + into->curve.AddAndAssignId(&split); + } } spl.Clear(); } @@ -492,6 +541,24 @@ bool SSurface::CoincidentWithPlane(Vector n, double d) const { return true; } +//----------------------------------------------------------------------------- +// Does a planar surface contain a curve? Does the curve lie completely in plane? +//----------------------------------------------------------------------------- +bool SSurface::ContainsPlaneCurve(SCurve *sc) const { + if(degm != 1 || degn != 1) return false; + if(!sc->isExact) return false; // we don't handle those (yet?) + + Vector p = ctrl[0][0]; + Vector n = NormalAt(0, 0).WithMagnitude(1); + double d = n.Dot(p); + + // check all control points on the curve + for(int i=0; i<= sc->exact.deg; i++) { + if(fabs(n.Dot(sc->exact.ctrl[i]) - d) > LENGTH_EPS) return false; + } + return true; +} + //----------------------------------------------------------------------------- // In our shell, find all surfaces that are coincident with the prototype // surface (with same or opposite normal, as specified), and copy all of @@ -501,10 +568,9 @@ bool SSurface::CoincidentWithPlane(Vector n, double d) 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); } } diff --git a/src/srf/triangulate.cpp b/src/srf/triangulate.cpp index 895c9409e..464bce624 100644 --- a/src/srf/triangulate.cpp +++ b/src/srf/triangulate.cpp @@ -109,6 +109,7 @@ bool SContour::BridgeToContour(SContour *sc, SEdgeList *avoidEdges, List *avoidPts) { int i, j; + bool withbridge = true; // Start looking for a bridge on our new hole near its leftmost (min x) // point. @@ -123,7 +124,7 @@ bool SContour::BridgeToContour(SContour *sc, // to the leftmost point of the new segment. int thiso = 0; double dmin = 1e10; - for(i = 0; i < l.n; i++) { + for(i = 0; i < l.n-1; i++) { Vector p = l[i].p; double d = (p.Minus(sc->xminPt)).MagSquared(); if(d < dmin) { @@ -139,7 +140,7 @@ bool SContour::BridgeToContour(SContour *sc, // First check if the contours share a point; in that case we should // merge them there, without a bridge. for(i = 0; i < l.n; i++) { - thisp = WRAP(i+thiso, l.n); + thisp = WRAP(i+thiso, l.n-1); a = l[thisp].p; for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) { @@ -152,6 +153,7 @@ bool SContour::BridgeToContour(SContour *sc, b = sc->l[scp].p; if(a.Equals(b)) { + withbridge = false; goto haveEdge; } } @@ -190,7 +192,9 @@ bool SContour::BridgeToContour(SContour *sc, haveEdge: SContour merged = {}; for(i = 0; i < l.n; i++) { - merged.AddPoint(l[i].p); + if(withbridge || (i != thisp)) { + merged.AddPoint(l[i].p); + } if(i == thisp) { // less than or equal; need to duplicate the join point for(j = 0; j <= (sc->l.n - 1); j++) { @@ -198,14 +202,18 @@ bool SContour::BridgeToContour(SContour *sc, merged.AddPoint((sc->l[jp]).p); } // and likewise duplicate join point for the outer curve - merged.AddPoint(l[i].p); + if(withbridge) { + merged.AddPoint(l[i].p); + } } } // and future bridges mustn't cross our bridge, and it's tricky to get // things right if two bridges come from the same point - avoidEdges->AddEdge(a, b); - avoidPts->Add(&a); + if(withbridge) { + avoidEdges->AddEdge(a, b); + avoidPts->Add(&a); + } avoidPts->Add(&b); l.Clear(); @@ -213,6 +221,63 @@ bool SContour::BridgeToContour(SContour *sc, return true; } +bool SContour::IsEmptyTriangle(int ap, int bp, int cp, double scaledEPS) const { + + STriangle tr = {}; + tr.a = l[ap].p; + tr.b = l[bp].p; + tr.c = l[cp].p; + + // Accelerate with an axis-aligned bounding box test + Vector maxv = tr.a, minv = tr.a; + (tr.b).MakeMaxMin(&maxv, &minv); + (tr.c).MakeMaxMin(&maxv, &minv); + + Vector n = Vector::From(0, 0, -1); + + int i; + for(i = 0; i < l.n; i++) { + if(i == ap || i == bp || i == cp) continue; + + Vector p = l[i].p; + if(p.OutsideAndNotOn(maxv, minv)) continue; + + // A point on the edge of the triangle is considered to be inside, + // and therefore makes it a non-ear; but a point on the vertex is + // "outside", since that's necessary to make bridges work. + if(p.EqualsExactly(tr.a)) continue; + if(p.EqualsExactly(tr.b)) continue; + if(p.EqualsExactly(tr.c)) continue; + + if(tr.ContainsPointProjd(n, p)) { + return false; + } + } + return true; +} + +// Test if ray b->d passes through triangle a,b,c +static bool RayIsInside(Vector a, Vector c, Vector b, Vector d) { + // coincident edges are not considered to intersect the triangle + if (d.Equals(a)) return false; + if (d.Equals(c)) return false; + // if d and c are on opposite sides of ba, we are ok + // likewise if d and a are on opposite sides of bc + Vector ba = a.Minus(b); + Vector bc = c.Minus(b); + Vector bd = d.Minus(b); + + // perpendicular to (x,y) is (x,-y) so dot that with the two points. If they + // have opposite signs their product will be negative. If bd and bc are on + // opposite sides of ba the ray does not intersect. Likewise for bd,ba and bc. + if ( (bd.x*(ba.y) + (bd.y * (-ba.x))) * ( bc.x*(ba.y) + (bc.y * (-ba.x))) < LENGTH_EPS) + return false; + if ( (bd.x*(bc.y) + (bd.y * (-bc.x))) * ( ba.x*(bc.y) + (ba.y * (-bc.x))) < LENGTH_EPS) + return false; + + return true; +} + bool SContour::IsEar(int bp, double scaledEps) const { int ap = WRAP(bp-1, l.n), cp = WRAP(bp+1, l.n); @@ -251,8 +316,21 @@ bool SContour::IsEar(int bp, double scaledEps) const { // and therefore makes it a non-ear; but a point on the vertex is // "outside", since that's necessary to make bridges work. if(p.EqualsExactly(tr.a)) continue; - if(p.EqualsExactly(tr.b)) continue; if(p.EqualsExactly(tr.c)) continue; + // points coincident with bp have to be allowed for bridges but edges + // from that other point must not cross through our triangle. + if(p.EqualsExactly(tr.b)) { + int j = WRAP(i-1, l.n); + int k = WRAP(i+1, l.n); + Vector jp = l[j].p; + Vector kp = l[k].p; + + // two consecutive bridges (A,B,C) and later (C,B,A) are not an ear + if (jp.Equals(tr.c) && kp.Equals(tr.a)) return false; + // check both edges from the point in question + if (!RayIsInside(tr.a, tr.c, p,jp) && !RayIsInside(tr.a, tr.c, p,kp)) + continue; + } if(tr.ContainsPointProjd(n, p)) { return false; @@ -297,29 +375,87 @@ void SContour::UvTriangulateInto(SMesh *m, SSurface *srf) { int i; // Clean the original contour by removing any zero-length edges. + // initialize eartypes to unknown while we're going over them. l.ClearTags(); + l[0].ear = EarType::UNKNOWN; for(i = 1; i < l.n; i++) { + l[i].ear = EarType::UNKNOWN; if((l[i].p).Equals(l[i-1].p)) { l[i].tag = 1; } } - l.RemoveTagged(); - - // Now calculate the ear-ness of each vertex - for(i = 0; i < l.n; i++) { - (l[i]).ear = IsEar(i, scaledEps) ? EarType::EAR : EarType::NOT_EAR; + if( (l[0].p).Equals(l[l.n-1].p) ) { + l[l.n-1].tag = 1; } + l.RemoveTagged(); - bool toggle = false; - while(l.n > 3) { - // Some points may have changed ear-ness, so recalculate - for(i = 0; i < l.n; i++) { - if(l[i].ear == EarType::UNKNOWN) { - (l[i]).ear = IsEar(i, scaledEps) ? - EarType::EAR : EarType::NOT_EAR; + // Handle simple triangle fans all at once. This pass is optional. + if(srf->degm == 1 && srf->degn == 1) { + l.ClearTags(); + int j=0; + int pstart = 0; + double elen = -1.0; + double oldspan = 0.0; + for(i = 1; i < l.n; i++) { + Vector ab = l[i].p.Minus(l[i-1].p); + // first time just measure the segment + if (elen < 0.0) { + elen = ab.Dot(ab); + oldspan = elen; + j = 1; + continue; + } + // check for consecutive segments of similar size which are also + // ears and where the group forms a convex ear + bool end = false; + double ratio = ab.Dot(ab) / elen; + if ((ratio < 0.25) || (ratio > 4.0)) end = true; + + double slen = l[pstart].p.Minus(l[i].p).MagSquared(); + if (slen < oldspan) end = true; + + if (!IsEar(i-1, scaledEps) ) end = true; +// if ((j>0) && !IsEar(pstart, i-1, i, scaledEps)) end = true; + if ((j>0) && !IsEmptyTriangle(pstart, i-1, i, scaledEps)) end = true; + // the new segment is valid so add to the fan + if (!end) { + j++; + oldspan = slen; + } + // we need to stop at the end of polygon but may still + if (i == l.n-1) { + end = true; + } + 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; xAddTriangle(&tr); + } + for (int x=1; xAddTriangle(&tr); + } + pstart = i-1; + elen = ab.Dot(ab); + oldspan = elen; + j = 1; } } + l.RemoveTagged(); + } // end optional fan creation pass + bool toggle = false; + while(l.n > 3) { int bestEar = -1; double bestChordTol = VERY_POSITIVE; // Alternate the starting position so we generate strip-like @@ -328,6 +464,9 @@ void SContour::UvTriangulateInto(SMesh *m, SSurface *srf) { int offset = toggle ? -1 : 0; for(i = 0; i < l.n; i++) { int ear = WRAP(i+offset, l.n); + if(l[ear].ear == EarType::UNKNOWN) { + (l[ear]).ear = IsEar(ear, scaledEps) ? EarType::EAR : EarType::NOT_EAR; + } if(l[ear].ear == EarType::EAR) { if(srf->degm == 1 && srf->degn == 1) { // This is a plane; any ear is a good ear. @@ -382,13 +521,24 @@ Vector SSurface::PointAtMaybeSwapped(double u, double v, bool swapped) const { } } +Vector SSurface::NormalAtMaybeSwapped(double u, double v, bool swapped) const { + Vector du, dv; + if(swapped) { + TangentsAt(v, u, &dv, &du); + } else { + TangentsAt(u, v, &du, &dv); + } + return du.Cross(dv).WithMagnitude(1.0); +} + void SSurface::MakeTriangulationGridInto(List *l, double vs, double vf, - bool swapped) const + bool swapped, int depth) const { double worst = 0; // Try piecewise linearizing four curves, at u = 0, 1/3, 2/3, 1; choose // the worst chord tolerance of any of those. + double worst_twist = 1.0; int i; for(i = 0; i <= 3; i++) { double u = i/3.0; @@ -405,16 +555,24 @@ void SSurface::MakeTriangulationGridInto(List *l, double vs, double vf, Vector pm1 = PointAtMaybeSwapped(u, vm1, swapped), pm2 = PointAtMaybeSwapped(u, vm2, swapped); + // 0.999 is about 2.5 degrees of twist over the middle 1/3 V-span. + // we don't check at the ends because the derivative may not be valid there. + double twist = 1.0; + if (degm == 1) twist = NormalAtMaybeSwapped(u, vm1, swapped).Dot( + NormalAtMaybeSwapped(u, vm2, swapped) ); + if (twist < worst_twist) worst_twist = twist; + worst = max(worst, pm1.DistanceToLine(ps, pf.Minus(ps))); worst = max(worst, pm2.DistanceToLine(ps, pf.Minus(ps))); } double step = 1.0/SS.GetMaxSegments(); - if((vf - vs) < step || worst < SS.ChordTolMm()) { + if( ((vf - vs) < step || worst < SS.ChordTolMm()) + && ((worst_twist > 0.999) || (depth > 3)) ) { l->Add(&vf); } else { - MakeTriangulationGridInto(l, vs, (vs+vf)/2, swapped); - MakeTriangulationGridInto(l, (vs+vf)/2, vf, swapped); + MakeTriangulationGridInto(l, vs, (vs+vf)/2, swapped, depth+1); + MakeTriangulationGridInto(l, (vs+vf)/2, vf, swapped, depth+1); } } @@ -432,73 +590,121 @@ void SPolygon::UvGridTriangulateInto(SMesh *mesh, SSurface *srf) { List li, lj; li = {}; lj = {}; - double v = 0; - li.Add(&v); - srf->MakeTriangulationGridInto(&li, 0, 1, /*swapped=*/true); - lj.Add(&v); - srf->MakeTriangulationGridInto(&lj, 0, 1, /*swapped=*/false); - - // Now iterate over each quad in the grid. If it's outside the polygon, - // or if it intersects the polygon, then we discard it. Otherwise we - // generate two triangles in the mesh, and cut it out of our polygon. - int i, j; - for(i = 0; i < (li.n - 1); i++) { - for(j = 0; j < (lj.n - 1); j++) { - double us = li[i], uf = li[i+1], - vs = lj[j], vf = lj[j+1]; - - Vector a = Vector::From(us, vs, 0), - b = Vector::From(us, vf, 0), - c = Vector::From(uf, vf, 0), - d = Vector::From(uf, vs, 0); - - if(orig.AnyEdgeCrossings(a, b, NULL) || - orig.AnyEdgeCrossings(b, c, NULL) || - orig.AnyEdgeCrossings(c, d, NULL) || - orig.AnyEdgeCrossings(d, a, NULL)) - { - continue; - } + double v[5] = {0.0, 0.25, 0.5, 0.75, 1.0}; + li.Add(&v[0]); + srf->MakeTriangulationGridInto(&li, 0, 1, /*swapped=*/true, 0); + lj.Add(&v[0]); + srf->MakeTriangulationGridInto(&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 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 minimum + lj.Clear(); + lj.Add(&v[0]);lj.Add(&v[1]);lj.Add(&v[2]);lj.Add(&v[3]);lj.Add(&v[4]); + } - // There's no intersections, so it doesn't matter which point - // we decide to test. - if(!this->ContainsPoint(a)) { - continue; - } + if ((li.n > 3) && (lj.n > 3)) { + // Now iterate over each quad in the grid. If it's outside the polygon, + // or if it intersects the polygon, then we discard it. Otherwise we + // generate two triangles in the mesh, and cut it out of our polygon. + // Quads around the perimeter would be rejected by AnyEdgeCrossings. + std::vector bottom(lj.n, false); // did we use this quad? + Vector tu = {0,0,0}, tv = {0,0,0}; + int i, j; + for(i = 1; i < (li.n-1); i++) { + bool prev_flag = false; + for(j = 1; j < (lj.n-1); j++) { + bool this_flag = true; + double us = li[i], uf = li[i+1], + vs = lj[j], vf = lj[j+1]; + + Vector a = Vector::From(us, vs, 0), + b = Vector::From(us, vf, 0), + c = Vector::From(uf, vf, 0), + d = Vector::From(uf, vs, 0); + + // | d-----c + // | | | + // | | | + // | a-----b + // | + // +-------------> j/v axis + + if( (i==(li.n-2)) || (j==(lj.n-2)) || + orig.AnyEdgeCrossings(a, b, NULL) || + orig.AnyEdgeCrossings(b, c, NULL) || + orig.AnyEdgeCrossings(c, d, NULL) || + orig.AnyEdgeCrossings(d, a, NULL)) + { + this_flag = false; + } - // Add the quad to our mesh - STriangle tr = {}; - tr.a = a; - tr.b = b; - tr.c = c; - mesh->AddTriangle(&tr); - tr.a = a; - tr.b = c; - tr.c = d; - mesh->AddTriangle(&tr); - - holes.AddEdge(a, b); - holes.AddEdge(b, c); - holes.AddEdge(c, d); - holes.AddEdge(d, a); + // There's no intersections, so it doesn't matter which point + // we decide to test. + if(!this->ContainsPoint(a)) { + this_flag = false; + } + + if (this_flag) { + // Add the quad to our mesh + srf->TangentsAt(us,vs, &tu,&tv); + if(tu.Dot(tv) < LENGTH_EPS) { + /* Split "the other way" if angle>90 + compare to LENGTH_EPS instead of zero to avoid alternating triangle + "orientations" when the tangents are orthogonal (revolve, lathe etc.) + this results in a higher quality mesh. */ + STriangle tr = {}; + tr.a = a; + tr.b = b; + tr.c = c; + mesh->AddTriangle(&tr); + tr.a = a; + tr.b = c; + tr.c = d; + mesh->AddTriangle(&tr); + } else{ + STriangle tr = {}; + tr.a = a; + tr.b = b; + tr.c = d; + mesh->AddTriangle(&tr); + tr.a = b; + tr.b = c; + tr.c = d; + mesh->AddTriangle(&tr); + } + if (!prev_flag) // add our own left edge + holes.AddEdge(d, a); + if (!bottom[j]) // add our own bottom edge + holes.AddEdge(a, b); + } else { + 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); + } + prev_flag = this_flag; + bottom[j] = this_flag; + } } - } - holes.CullExtraneousEdges(); - SPolygon hp = {}; - holes.AssemblePolygon(&hp, NULL, /*keepDir=*/true); + // Because no duplicate edges were created we do not need to cull them. + SPolygon hp = {}; + holes.AssemblePolygon(&hp, NULL, /*keepDir=*/true); - SContour *sc; - for(sc = hp.l.First(); sc; sc = hp.l.NextAfter(sc)) { - l.Add(sc); + SContour *sc; + for(sc = hp.l.First(); sc; sc = hp.l.NextAfter(sc)) { + l.Add(sc); + } + hp.l.Clear(); } - orig.Clear(); holes.Clear(); li.Clear(); lj.Clear(); - hp.l.Clear(); - UvTriangulateInto(mesh, srf); } diff --git a/src/style.cpp b/src/style.cpp index a60740a43..4349eb36e 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -8,22 +8,22 @@ #include "solvespace.h" const Style::Default Style::Defaults[] = { - { { ACTIVE_GRP }, "ActiveGrp", RGBf(1.0, 1.0, 1.0), 1.5, 4 }, - { { CONSTRUCTION }, "Construction", RGBf(0.1, 0.7, 0.1), 1.5, 0 }, - { { INACTIVE_GRP }, "InactiveGrp", RGBf(0.5, 0.3, 0.0), 1.5, 3 }, - { { DATUM }, "Datum", RGBf(0.0, 0.8, 0.0), 1.5, 0 }, - { { SOLID_EDGE }, "SolidEdge", RGBf(0.8, 0.8, 0.8), 1.0, 2 }, - { { CONSTRAINT }, "Constraint", RGBf(1.0, 0.1, 1.0), 1.0, 0 }, - { { SELECTED }, "Selected", RGBf(1.0, 0.0, 0.0), 1.5, 0 }, - { { HOVERED }, "Hovered", RGBf(1.0, 1.0, 0.0), 1.5, 0 }, - { { CONTOUR_FILL }, "ContourFill", RGBf(0.0, 0.1, 0.1), 1.0, 0 }, - { { NORMALS }, "Normals", RGBf(0.0, 0.4, 0.4), 1.0, 0 }, - { { ANALYZE }, "Analyze", RGBf(0.0, 1.0, 1.0), 3.0, 0 }, - { { DRAW_ERROR }, "DrawError", RGBf(1.0, 0.0, 0.0), 8.0, 0 }, - { { DIM_SOLID }, "DimSolid", RGBf(0.1, 0.1, 0.1), 1.0, 0 }, - { { HIDDEN_EDGE }, "HiddenEdge", RGBf(0.8, 0.8, 0.8), 1.0, 1 }, - { { OUTLINE }, "Outline", RGBf(0.8, 0.8, 0.8), 3.0, 5 }, - { { 0 }, NULL, RGBf(0.0, 0.0, 0.0), 0.0, 0 } + { { 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 }, + { { INACTIVE_GRP }, "InactiveGrp", RGBf(0.5, 0.3, 0.0), 1.5, 3, true, StipplePattern::CONTINUOUS }, + { { DATUM }, "Datum", RGBf(0.0, 0.8, 0.0), 1.5, 0, true, StipplePattern::CONTINUOUS }, + { { SOLID_EDGE }, "SolidEdge", RGBf(0.8, 0.8, 0.8), 1.0, 2, true, StipplePattern::CONTINUOUS }, + { { CONSTRAINT }, "Constraint", RGBf(1.0, 0.1, 1.0), 1.0, 0, true, StipplePattern::CONTINUOUS }, + { { SELECTED }, "Selected", RGBf(1.0, 0.0, 0.0), 1.5, 0, true, StipplePattern::CONTINUOUS }, + { { HOVERED }, "Hovered", RGBf(1.0, 1.0, 0.0), 1.5, 0, true, StipplePattern::CONTINUOUS }, + { { CONTOUR_FILL }, "ContourFill", RGBf(0.0, 0.1, 0.1), 1.0, 0, true, StipplePattern::CONTINUOUS }, + { { NORMALS }, "Normals", RGBf(0.0, 0.4, 0.4), 1.0, 0, true, StipplePattern::CONTINUOUS }, + { { ANALYZE }, "Analyze", RGBf(0.0, 1.0, 1.0), 3.0, 0, true, StipplePattern::CONTINUOUS }, + { { DRAW_ERROR }, "DrawError", RGBf(1.0, 0.0, 0.0), 8.0, 0, true, StipplePattern::CONTINUOUS }, + { { DIM_SOLID }, "DimSolid", RGBf(0.1, 0.1, 0.1), 1.0, 0, true, StipplePattern::CONTINUOUS }, + { { HIDDEN_EDGE }, "HiddenEdge", RGBf(0.8, 0.8, 0.8), 1.0, 1, true, StipplePattern::DASH }, + { { OUTLINE }, "Outline", RGBf(0.8, 0.8, 0.8), 3.0, 5, true, StipplePattern::CONTINUOUS }, + { { 0 }, NULL, RGBf(0.0, 0.0, 0.0), 0.0, 0, true, StipplePattern::CONTINUOUS } }; std::string Style::CnfColor(const std::string &prefix) { @@ -32,9 +32,18 @@ std::string Style::CnfColor(const std::string &prefix) { std::string Style::CnfWidth(const std::string &prefix) { return "Style_" + prefix + "_Width"; } +std::string Style::CnfStippleType(const std::string &prefix) { + return "Style_" + prefix + "_StippleType"; +} +std::string Style::CnfStippleScale(const std::string &prefix) { + return "Style_" + prefix + "_StippleScale"; +} std::string Style::CnfTextHeight(const std::string &prefix) { return "Style_" + prefix + "_TextHeight"; } +std::string Style::CnfExportable(const std::string &prefix) { + return "Style_" + prefix + "_Exportable"; +} std::string Style::CnfPrefixToName(const std::string &prefix) { std::string name = "#def-"; @@ -97,12 +106,19 @@ void Style::FillDefaultStyle(Style *s, const Default *d, bool factory) { s->textOrigin = TextOrigin::NONE; s->textAngle = 0; s->visible = true; - s->exportable = true; + s->exportable = (factory) + ? d->exportable + : settings->ThawBool(CnfExportable(d->cnfPrefix), d->exportable); s->filled = false; s->fillColor = RGBf(0.3, 0.3, 0.3); - s->stippleType = (d->h.v == Style::HIDDEN_EDGE) ? StipplePattern::DASH - : StipplePattern::CONTINUOUS; - s->stippleScale = 15.0; + s->stippleType = (factory) + ? d->stippleType + : Style::StipplePatternFromString( + settings->ThawString(CnfStippleType(d->cnfPrefix), + StipplePatternName(d->stippleType))); + s->stippleScale = (factory) + ? 15.0 + : settings->ThawFloat(CnfStippleScale(d->cnfPrefix), 15.0); s->zIndex = d->zIndex; } @@ -120,7 +136,10 @@ void Style::FreezeDefaultStyles(Platform::SettingsRef settings) { for(d = &(Defaults[0]); d->h.v; d++) { settings->FreezeColor(CnfColor(d->cnfPrefix), Color(d->h)); settings->FreezeFloat(CnfWidth(d->cnfPrefix), (float)Width(d->h)); + settings->FreezeString(CnfStippleType(d->cnfPrefix), StipplePatternName(d->h)); + settings->FreezeFloat(CnfStippleScale(d->cnfPrefix), (float)StippleScale(d->h)); settings->FreezeFloat(CnfTextHeight(d->cnfPrefix), (float)TextHeight(d->h)); + settings->FreezeBool(CnfExportable(d->cnfPrefix), Exportable(d->h.v)); } } @@ -347,11 +366,62 @@ hStyle Style::ForEntity(hEntity he) { return hs; } +StipplePattern Style::StipplePatternFromString(std::string name) { + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + if(name == "continuous") { + return StipplePattern::CONTINUOUS; + } else if(name == "shortdash") { + return StipplePattern::SHORT_DASH; + } else if(name == "dash") { + return StipplePattern::DASH; + } else if(name == "longdash") { + return StipplePattern::LONG_DASH; + } else if(name == "dashdot") { + return StipplePattern::DASH_DOT; + } else if(name == "dashdotdot") { + return StipplePattern::DASH_DOT_DOT; + } else if(name == "dot") { + return StipplePattern::DOT; + } else if(name == "freehand") { + return StipplePattern::FREEHAND; + } else if(name == "zigzag") { + return StipplePattern::ZIGZAG; + } + + return StipplePattern::CONTINUOUS; +} + StipplePattern Style::PatternType(hStyle hs) { Style *s = Get(hs); return s->stippleType; } +std::string Style::StipplePatternName(hStyle hs) { + Style *s = Get(hs); + 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"; + case StipplePattern::LONG_DASH: return "LongDash"; + case StipplePattern::DASH_DOT: return "DashDot"; + case StipplePattern::DASH_DOT_DOT: return "DashDotDot"; + case StipplePattern::DOT: return "Dot"; + case StipplePattern::FREEHAND: return "FreeHand"; + case StipplePattern::ZIGZAG: return "ZigZag"; + } + + return "Continuous"; +} + +double Style::StippleScale(hStyle hs) { + Style *s = Get(hs); + return s->stippleScale; +} + double Style::StippleScaleMm(hStyle hs) { Style *s = Get(hs); if(s->widthAs == UnitsAs::MM) { @@ -375,6 +445,7 @@ void TextWindow::ScreenShowListOfStyles(int link, uint32_t v) { SS.TW.GoToScreen(Screen::LIST_OF_STYLES); } void TextWindow::ScreenShowStyleInfo(int link, uint32_t v) { + GraphicsWindow::MenuEdit(Command::UNSELECT_ALL); SS.TW.GoToScreen(Screen::STYLE_INFO); SS.TW.shown.style.v = v; } @@ -382,6 +453,7 @@ void TextWindow::ScreenShowStyleInfo(int link, uint32_t v) { void TextWindow::ScreenLoadFactoryDefaultStyles(int link, uint32_t v) { Style::LoadFactoryDefaults(); SS.TW.GoToScreen(Screen::LIST_OF_STYLES); + SS.GW.persistentDirty = true; } void TextWindow::ScreenCreateCustomStyle(int link, uint32_t v) { @@ -398,14 +470,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; } @@ -492,7 +563,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; @@ -849,17 +920,19 @@ void TextWindow::ShowStyleInfo() { ((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::TOP) ? RADIO_TRUE : RADIO_FALSE); } - if(s->h.v >= Style::FIRST_CUSTOM) { - Printf(false, ""); + Printf(false, ""); + if(s->h.v >= Style::FIRST_CUSTOM) { Printf(false, " %Fd%D%f%Lv%s show these objects on screen%E", s->h.v, &ScreenChangeStyleYesNo, s->visible ? CHECK_TRUE : CHECK_FALSE); + } - Printf(false, " %Fd%D%f%Le%s export these objects%E", - s->h.v, &ScreenChangeStyleYesNo, - s->exportable ? CHECK_TRUE : CHECK_FALSE); + Printf(false, " %Fd%D%f%Le%s export these objects%E", + s->h.v, &ScreenChangeStyleYesNo, + s->exportable ? CHECK_TRUE : CHECK_FALSE); + if(s->h.v >= Style::FIRST_CUSTOM) { Printf(false, ""); Printf(false, "To assign lines or curves to this style,"); Printf(false, "right-click them on the drawing."); diff --git a/src/system.cpp b/src/system.cpp index 08752600c..dd6e1893a 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -8,81 +8,152 @@ //----------------------------------------------------------------------------- #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 // 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(Param &p : param) { + if(p.tag != tag) continue; + mat.param.push_back(p.h); } - mat.n = j; - - int i = 0; + mat.n = mat.param.size(); - 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(); + for(Equation &e : eq) { + if(e.tag != tag) continue; + mat.eq.push_back(&e); + } + mat.m = mat.eq.size(); + mat.A.sym.resize(mat.m, mat.n); + mat.A.sym.reserve(Eigen::VectorXi::Constant(mat.n, LikelyPartialCountPerEq)); + + // Fill the param id to index map + std::map paramToIndex; + for(int j = 0; j < mat.n; j++) { + paramToIndex[mat.param[j].v] = j; + } - // 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; + } + std::vector paramsUsed; + // In some experimenting, this is almost always the right size. + // Value is usually between 0 and 20, comes from number of constraints? + mat.B.sym.reserve(mat.eq.size()); + for(size_t i = 0; i < mat.eq.size(); i++) { + Equation *e = mat.eq[i]; + if(e->tag != tag) continue; + // Simplify (fold) then deep-copy the current equation. + Expr *f = e->e->FoldConstants(); + f = f->DeepCopyWithParamsAsPointers(¶m, &(SK.param)); + + paramsUsed.clear(); + 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(); + if(pd->IsZeroConst()) + continue; + mat.A.sym.insert(i, j) = pd; } - mat.B.sym[i] = f; - i++; + paramsUsed.clear(); + 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; + const auto b = dragged.begin(); + const auto e = dragged.end(); + return e != std::find(b, e, p); +} + +Param *System::GetLastParamSubstitution(Param *p) { + Param *current = p; + while(current->substd != NULL) { + current = current->substd; + if(current == p) { + // Break the loop + current->substd = NULL; + break; + } + } + return current; +} + +void System::SortSubstitutionByDragged(Param *p) { + std::vector subsParams; + Param *by = NULL; + Param *current = p; + while(current != NULL) { + subsParams.push_back(current); + if(IsDragged(current->h)) { + by = current; + } + current = current->substd; + } + if(by == NULL) by = p; + for(Param *p : subsParams) { + if(p == by) continue; + p->substd = by; + p->tag = VAR_SUBSTITUTED; + } + by->substd = NULL; + by->tag = 0; +} + +void System::SubstituteParamsByLast(Expr *e) { + ssassert(e->op != Expr::Op::PARAM_PTR, "Expected an expression that refer to params via handles"); + + if(e->op == Expr::Op::PARAM) { + Param *p = param.FindByIdNoOops(e->parh); + if(p != NULL) { + Param *s = GetLastParamSubstitution(p); + if(s != NULL) { + e->parh = s->h; + } + } + } else { + int c = e->Children(); + if(c >= 1) { + SubstituteParamsByLast(e->a); + if(c >= 2) SubstituteParamsByLast(e->b); + } } - return false; } void System::SolveBySubstitution() { @@ -102,172 +173,114 @@ void System::SolveBySubstitution() { continue; } - if(IsDragged(a)) { - // A is being dragged, so A should stay, and B should go - std::swap(a, b); + if(a.v == b.v) { + teq.tag = EQ_SUBSTITUTED; + continue; } - for(auto &req : eq) { - req.e->Substitute(a, b); // A becomes B, B unchanged - } - for(auto &rp : param) { - if(rp.substd == a) { - rp.substd = b; + Param *pa = param.FindById(a); + Param *pb = param.FindById(b); + + // Take the last substitution of parameter a + // This resulted in creation of substitution chains + Param *last = GetLastParamSubstitution(pa); + last->substd = pb; + last->tag = VAR_SUBSTITUTED; + + if(pb->substd != NULL) { + // Break the loops + GetLastParamSubstitution(pb); + // if b loop was broken + if(pb->substd == NULL) { + // Clear substitution + pb->tag = 0; } } - Param *ptr = param.FindById(a); - ptr->tag = VAR_SUBSTITUTED; - ptr->substd = b; - teq.tag = EQ_SUBSTITUTED; } } -} -//----------------------------------------------------------------------------- -// 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. -//----------------------------------------------------------------------------- -int System::CalculateRank() { - // Actually work with magnitudes squared, not the magnitudes - double rowMag[MAX_UNKNOWNS] = {}; - double tol = RANK_MAG_TOLERANCE*RANK_MAG_TOLERANCE; + // + for(Param &p : param) { + SortSubstitutionByDragged(&p); + } - int i, iprev, j; - int rank = 0; + // Substitute all the equations + for(auto &req : eq) { + SubstituteParamsByLast(req.e); + } - 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; + // Substitute all the parameters with last substitutions + for(auto &p : param) { + if(p.substd == NULL) continue; + p.substd = GetLastParamSubstitution(p.substd); } +} - return rank; +//----------------------------------------------------------------------------- +// Calculate the rank of the Jacobian matrix +//----------------------------------------------------------------------------- +int System::CalculateRank() { + 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) { 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; 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(ffabs(A[ip][i]) > max) { - imax = ip; - max = ffabs(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(ffabs(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(ffabs(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++) { + mat.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]; + mat.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() *= mat.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] *= mat.scale[c]; } return true; } @@ -279,6 +292,7 @@ bool System::NewtonSolve(int tag) { 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(); } @@ -293,7 +307,7 @@ bool System::NewtonSolve(int tag) { for(i = 0; i < mat.n; i++) { Param *p = param.FindById(mat.param[i]); p->val -= mat.X[i]; - if(isnan(p->val)) { + if(IsReasonable(p->val)) { // Very bad, and clearly not convergent return false; } @@ -306,10 +320,10 @@ bool System::NewtonSolve(int tag) { // Check for convergence converged = true; for(i = 0; i < mat.m; i++) { - if(isnan(mat.B.num[i])) { + if(IsReasonable(mat.B.num[i])) { return false; } - if(ffabs(mat.B.num[i]) > CONVERGE_TOLERANCE) { + if(fabs(mat.B.num[i]) > CONVERGE_TOLERANCE) { converged = false; break; } @@ -355,10 +369,17 @@ void System::WriteEquationsExceptFor(hConstraint hc, Group *g) { } void System::FindWhichToRemoveToFixJacobian(Group *g, List *bad, bool forceDofCheck) { + auto time = GetMilliseconds(); + g->solved.timeout = false; int a; for(a = 0; a < 2; a++) { for(auto &con : SK.constraint) { + if((GetMilliseconds() - time) > g->solved.findToFixTimeout) { + g->solved.timeout = true; + return; + } + ConstraintBase *c = &con; if(c->group != g->h) continue; if((c->type == Constraint::Type::POINTS_COINCIDENT && a == 0) || @@ -398,26 +419,25 @@ SolveResult System::Solve(Group *g, int *rank, int *dof, List *bad, { 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) { + // 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) { SolveBySubstitution(); } @@ -455,22 +475,21 @@ 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)) { 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 @@ -478,7 +497,7 @@ SolveResult System::Solve(Group *g, int *rank, int *dof, List *bad, for(auto &p : param) { double val; if(p.tag == VAR_SUBSTITUTED) { - val = param.FindById(p.substd)->val; + val = p.substd->val; } else { val = p.val; } @@ -492,12 +511,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++) { - if(ffabs(mat.B.num[i]) > CONVERGE_TOLERANCE || isnan(mat.B.num[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 @@ -527,11 +546,14 @@ SolveResult System::SolveRank(Group *g, int *rank, int *dof, List * return SolveResult::TOO_MANY_UNKNOWNS; } - bool rankOk = TestRank(rank); + bool rankOk = TestRank(dof); 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; @@ -542,6 +564,8 @@ void System::Clear() { param.Clear(); eq.Clear(); dragged.Clear(); + mat.A.num.setZero(); + mat.A.sym.setZero(); } void System::MarkParamsFree(bool find) { @@ -566,7 +590,3 @@ void System::MarkParamsFree(bool find) { } } -int System::CalculateDof() { - return mat.n - mat.m; -} - diff --git a/src/textscreens.cpp b/src/textscreens.cpp index 7e9423932..ec75b01ec 100644 --- a/src/textscreens.cpp +++ b/src/textscreens.cpp @@ -44,6 +44,7 @@ void TextWindow::ShowHeader(bool withNav) { // to hide or show them, and to view them in detail. This is our home page. //----------------------------------------------------------------------------- void TextWindow::ScreenSelectGroup(int link, uint32_t v) { + GraphicsWindow::MenuEdit(Command::UNSELECT_ALL); SS.TW.GoToScreen(Screen::GROUP_INFO); SS.TW.shown.group.v = v; } @@ -56,11 +57,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; } @@ -122,13 +125,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 @@ -142,16 +150,19 @@ void TextWindow::ShowListOfGroups() { // Link to the errors, if a problem occurred while solving ok ? (warn ? 'm' : (dof > 0 ? 'i' : 's')) : 'x', g->h.v, (&TextWindow::ScreenHowGroupSolved), - ok ? (warn ? "err" : sdof) : "", + ok ? ((warn && SS.checkClosedContour) ? "err" : sdof) : "", ok ? "" : "ERR", // Link to a screen that gives more details on the group - g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str()); + 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 /" @@ -167,12 +178,16 @@ void TextWindow::ShowListOfGroups() { // The screen that shows information about a specific group, and allows the // user to edit various things about it. //----------------------------------------------------------------------------- -void TextWindow::ScreenHoverConstraint(int link, uint32_t v) { - if(!SS.GW.showConstraints) return; - - hConstraint hc = { v }; +void TextWindow::ScreenHoverGroupWorkplane(int link, uint32_t v) { SS.GW.hover.Clear(); - SS.GW.hover.constraint = hc; + hGroup hg = { v }; + SS.GW.hover.entity = hg.entity(0); + SS.GW.hover.emphasized = true; +} +void TextWindow::ScreenHoverEntity(int link, uint32_t v) { + SS.GW.hover.Clear(); + hEntity he = { v }; + SS.GW.hover.entity = he; SS.GW.hover.emphasized = true; } void TextWindow::ScreenHoverRequest(int link, uint32_t v) { @@ -181,10 +196,18 @@ void TextWindow::ScreenHoverRequest(int link, uint32_t v) { SS.GW.hover.entity = hr.entity(0); SS.GW.hover.emphasized = true; } -void TextWindow::ScreenSelectConstraint(int link, uint32_t v) { +void TextWindow::ScreenHoverConstraint(int link, uint32_t v) { + if( SS.GW.showConstraints == GraphicsWindow::ShowConstraintMode::SCM_NOSHOW ) return; + hConstraint hc = { v }; + SS.GW.hover.Clear(); + SS.GW.hover.constraint = hc; + SS.GW.hover.emphasized = true; +} +void TextWindow::ScreenSelectEntity(int link, uint32_t v) { SS.GW.ClearSelection(); GraphicsWindow::Selection sel = {}; - sel.constraint.v = v; + hEntity he = { v }; + sel.entity = he; SS.GW.selection.Add(&sel); } void TextWindow::ScreenSelectRequest(int link, uint32_t v) { @@ -194,14 +217,48 @@ void TextWindow::ScreenSelectRequest(int link, uint32_t v) { sel.entity = hr.entity(0); SS.GW.selection.Add(&sel); } +void TextWindow::ScreenSelectConstraint(int link, uint32_t v) { + SS.GW.ClearSelection(); + GraphicsWindow::Selection sel = {}; + sel.constraint.v = v; + SS.GW.selection.Add(&sel); +} void TextWindow::ScreenChangeGroupOption(int link, uint32_t v) { SS.UndoRemember(); 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; @@ -210,11 +267,19 @@ void TextWindow::ScreenChangeGroupOption(int link, uint32_t v) { if(g->type == Group::Type::EXTRUDE) { // When an extrude group is first created, it's positioned for a union // extrusion. If no constraints were added, flip it when we switch between - // union and difference modes to avoid manual work doing the same. - if(g->meshCombine != (Group::CombineAs)v && g->GetNumConstraints() == 0 && - ((Group::CombineAs)v == Group::CombineAs::DIFFERENCE || - g->meshCombine == Group::CombineAs::DIFFERENCE)) { - g->ExtrusionForceVectorTo(g->ExtrusionGetVector().Negated()); + // union/assemble and difference/intersection modes to avoid manual work doing the same. + if(g->meshCombine != (Group::CombineAs)v && g->GetNumConstraints() == 0) { + // I apologise for this if statement + if((((Group::CombineAs::DIFFERENCE == g->meshCombine) || + (Group::CombineAs::INTERSECTION == g->meshCombine)) && + (Group::CombineAs::DIFFERENCE != (Group::CombineAs)v) && + (Group::CombineAs::INTERSECTION != (Group::CombineAs)v)) || + ((Group::CombineAs::DIFFERENCE != g->meshCombine) && + (Group::CombineAs::INTERSECTION != g->meshCombine) && + ((Group::CombineAs::DIFFERENCE == (Group::CombineAs)v) || + (Group::CombineAs::INTERSECTION == (Group::CombineAs)v)))) { + g->ExtrusionForceVectorTo(g->ExtrusionGetVector().Negated()); + } } } g->meshCombine = (Group::CombineAs)v; @@ -226,6 +291,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; @@ -271,6 +338,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(); @@ -323,15 +407,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; @@ -370,26 +473,51 @@ 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) { bool un = (g->meshCombine == Group::CombineAs::UNION); bool diff = (g->meshCombine == Group::CombineAs::DIFFERENCE); + bool intr = (g->meshCombine == Group::CombineAs::INTERSECTION); bool asy = (g->meshCombine == Group::CombineAs::ASSEMBLE); Printf(false, " %Ftsolid model as"); Printf(false, "%Ba %f%D%Lc%Fd%s union%E " - "%f%D%Lc%Fd%s difference%E " "%f%D%Lc%Fd%s assemble%E ", &TextWindow::ScreenChangeGroupOption, Group::CombineAs::UNION, un ? RADIO_TRUE : RADIO_FALSE, &TextWindow::ScreenChangeGroupOption, + Group::CombineAs::ASSEMBLE, + (asy ? RADIO_TRUE : RADIO_FALSE)); + Printf(false, "%Ba %f%D%Lc%Fd%s difference%E " + "%f%D%Lc%Fd%s intersection%E ", + &TextWindow::ScreenChangeGroupOption, Group::CombineAs::DIFFERENCE, diff ? RADIO_TRUE : RADIO_FALSE, &TextWindow::ScreenChangeGroupOption, - Group::CombineAs::ASSEMBLE, - (asy ? RADIO_TRUE : RADIO_FALSE)); + Group::CombineAs::INTERSECTION, + intr ? RADIO_TRUE : RADIO_FALSE); if(g->type == Group::Type::EXTRUDE || g->type == Group::Type::LATHE || g->type == Group::Type::REVOLVE || g->type == Group::Type::HELIX) { @@ -418,7 +546,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); @@ -434,6 +562,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); @@ -545,6 +677,11 @@ void TextWindow::ShowGroupSolveInfo() { c->DescriptionString().c_str()); } + if(g->solved.timeout) { + Printf(true, "%FxSome items in list have been omitted%Fd"); + Printf(false, "%Fxbecause the operation timed out.%Fd"); + } + Printf(true, "It may be possible to fix the problem "); Printf(false, "by selecting Edit -> Undo."); @@ -565,7 +702,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); } @@ -604,7 +741,7 @@ void TextWindow::ScreenStepDimGo(int link, uint32_t v) { if(time - SS.TW.stepDim.time < STEP_MILLIS) { SS.TW.stepDim.timer->RunAfterNextFrame(); } else { - SS.TW.stepDim.timer->RunAfter(time - SS.TW.stepDim.time - STEP_MILLIS); + SS.TW.stepDim.timer->RunAfter((unsigned)(time - SS.TW.stepDim.time - STEP_MILLIS)); } SS.TW.stepDim.time = time; } else { @@ -652,7 +789,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; } @@ -751,6 +888,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) { @@ -758,7 +904,8 @@ void TextWindow::EditControlDone(std::string s) { Group *g = SK.group.FindByIdNoOops(SS.TW.shown.group); if(!g) break; - g->color = RgbaColor::FromFloat(rgb.x, rgb.y, rgb.z, g->color.alphaF()); + g->color = RgbaColor::FromFloat((float)rgb.x, (float)rgb.y, (float)rgb.z, + g->color.alphaF()); SS.MarkGroupDirty(g->h); SS.GW.ClearSuper(); diff --git a/src/textwin.cpp b/src/textwin.cpp index 131e12e98..1a76033dd 100644 --- a/src/textwin.cpp +++ b/src/textwin.cpp @@ -57,7 +57,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 +83,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; - break; - - case GraphicsWindow::DrawOccludedAs::VISIBLE: - SS.GW.drawOccludedAs = GraphicsWindow::DrawOccludedAs::INVISIBLE; + for(size_t k = 0; k < tri; ++k) + if(*variable == states[k]) { + *variable = states[(k + 1) % tri]; break; - } + } SS.GenerateAll(); SS.GW.Invalidate(); @@ -163,8 +153,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 +169,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 +204,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 +236,7 @@ void TextWindow::Init() { using namespace std::placeholders; window->onClose = []() { + SS.TW.HideEditControl(); SS.GW.showTextWindow = false; SS.GW.EnsureValidActives(); }; @@ -252,8 +254,18 @@ void TextWindow::Init() { MouseLeave(); return true; } else if(event.type == MouseEvent::Type::SCROLL_VERT) { - window->SetScrollbarPosition(window->GetScrollbarPosition() - - LINE_HEIGHT / 2 * event.scrollDelta); + if (event.scrollDelta == 0) { + return true; + } + if (abs(event.scrollDelta) < 0.2) { + if (event.scrollDelta > 0) { + event.scrollDelta = 0.2; + } else { + event.scrollDelta = -0.2; + } + } + double offset = LINE_HEIGHT / 2 * event.scrollDelta; + ScrollbarEvent(window->GetScrollbarPosition() - offset); } return false; }; @@ -338,11 +350,22 @@ void TextWindow::ClearScreen() { rows = 0; } +// 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, ...) { if(!canvas) return; if(rows >= MAX_ROWS) return; + if(rows >= MAX_ROWS-2 && (fmt != endString)) { + // twice due to some half-row issues on resizing + Printf(halfLine, endString); + Printf(halfLine, endString); + return; + } + va_list vl; va_start(vl, fmt); @@ -603,7 +626,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)); @@ -1130,16 +1153,16 @@ void TextWindow::MouseLeave() { void TextWindow::ScrollbarEvent(double newPos) { if(window->IsEditorVisible()) { - window->SetScrollbarPosition(scrollPos); + // An edit field is active. Do not move the scrollbar. return; } int bottom = top[rows-1] + 2; newPos = min((int)newPos, bottom - halfRows); newPos = max((int)newPos, 0); - if(newPos != scrollPos) { scrollPos = (int)newPos; + window->SetScrollbarPosition(scrollPos); window->Invalidate(); } } diff --git a/src/toolbar.cpp b/src/toolbar.cpp index 4d0b88395..74764883a 100644 --- a/src/toolbar.cpp +++ b/src/toolbar.cpp @@ -68,6 +68,10 @@ static ToolIcon Toolbar[] = { N_("New group extruding active sketch"), {} }, { "lathe", Command::GROUP_LATHE, N_("New group rotating active sketch"), {} }, + { "helix", Command::GROUP_HELIX, + N_("New group helix from active sketch"), {} }, + { "revolve", Command::GROUP_REVOLVE, + N_("New group revolve active sketch"), {} }, { "step-rotate", Command::GROUP_ROT, N_("New group step and repeat rotating"), {} }, { "step-translate", Command::GROUP_TRANS, @@ -149,11 +153,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 = 34*16 + 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 = diff --git a/src/ttf.cpp b/src/ttf.cpp index ddb2aa175..10ba0d888 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 @@ -81,9 +81,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 +105,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 +120,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 +328,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 +341,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 +380,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 +407,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,7 +431,14 @@ 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; diff --git a/src/ttf.h b/src/ttf.h index 565e6a9ca..1dc7205d3 100644 --- a/src/ttf.h +++ b/src/ttf.h @@ -24,8 +24,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 +43,8 @@ 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); }; #endif diff --git a/src/ui.h b/src/ui.h index 7d24e84db..eef546ece 100644 --- a/src/ui.h +++ b/src/ui.h @@ -80,7 +80,9 @@ enum class Command : uint32_t { ZOOM_OUT, ZOOM_TO_FIT, SHOW_GRID, + DIM_SOLID_MODEL, PERSPECTIVE_PROJ, + EXPLODE_SKETCH, ONTO_WORKPLANE, NEAREST_ORTHO, NEAREST_ISO, @@ -88,6 +90,7 @@ enum class Command : uint32_t { SHOW_TOOLBAR, SHOW_TEXT_WND, UNITS_INCHES, + UNITS_FEET_INCHES, UNITS_MM, UNITS_METERS, FULL_SCREEN, @@ -105,6 +108,9 @@ enum class Command : uint32_t { ROTATE_90, UNSELECT_ALL, REGEN_ALL, + EDIT_LINE_STYLES, + VIEW_PROJECTION, + CONFIGURATION, // Request SEL_WORKPLANE, FREE_IN_3D, @@ -166,6 +172,7 @@ enum class Command : uint32_t { // Help LOCALE, WEBSITE, + GITHUB, ABOUT, }; @@ -176,7 +183,7 @@ class TextWindow { enum { MAX_COLS = 100, MIN_COLS = 45, - MAX_ROWS = 2000 + MAX_ROWS = 4000 }; typedef struct { @@ -308,10 +315,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, + 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 @@ -336,7 +348,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; @@ -389,10 +403,12 @@ 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); // when we're describing a constraint + static void ScreenConstraintToggleReference(int link, uint32_t v); static void ScreenConstraintShowAsRadius(int link, uint32_t v); // and the rest from the stuff in textscreens.cpp @@ -403,9 +419,12 @@ class TextWindow { static void ScreenShowGroupsSpecial(int link, uint32_t v); static void ScreenDeleteGroup(int link, uint32_t v); - static void ScreenHoverConstraint(int link, uint32_t v); + static void ScreenHoverGroupWorkplane(int link, uint32_t v); static void ScreenHoverRequest(int link, uint32_t v); + static void ScreenHoverEntity(int link, uint32_t v); + static void ScreenHoverConstraint(int link, uint32_t v); static void ScreenSelectRequest(int link, uint32_t v); + static void ScreenSelectEntity(int link, uint32_t v); static void ScreenSelectConstraint(int link, uint32_t v); static void ScreenChangeGroupOption(int link, uint32_t v); @@ -425,10 +444,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); @@ -462,8 +485,11 @@ 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); static void ScreenChangeColor(int link, uint32_t v); static void ScreenChangeChordTolerance(int link, uint32_t v); static void ScreenChangeMaxSegments(int link, uint32_t v); @@ -471,6 +497,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); @@ -478,6 +505,8 @@ class TextWindow { static void ScreenChangeExportOffset(int link, uint32_t v); 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); @@ -520,7 +549,9 @@ class GraphicsWindow { Platform::MenuRef linkRecentMenu; Platform::MenuItemRef showGridMenuItem; + Platform::MenuItemRef dimSolidModelMenuItem; Platform::MenuItemRef perspectiveProjMenuItem; + Platform::MenuItemRef explodeMenuItem; Platform::MenuItemRef showToolbarMenuItem; Platform::MenuItemRef showTextWndMenuItem; Platform::MenuItemRef fullScreenMenuItem; @@ -528,6 +559,7 @@ class GraphicsWindow { Platform::MenuItemRef unitsMmMenuItem; Platform::MenuItemRef unitsMetersMenuItem; Platform::MenuItemRef unitsInchesMenuItem; + Platform::MenuItemRef unitsFeetInchesMenuItem; Platform::MenuItemRef inWorkplaneMenuItem; Platform::MenuItemRef in3dMenuItem; @@ -596,6 +628,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, @@ -715,6 +748,7 @@ class GraphicsWindow { public: int zIndex; double distance; + double depth; Selection selection; }; @@ -722,6 +756,7 @@ class GraphicsWindow { Selection hover; bool hoverWasSelectedOnMousedown; List selection; + const unsigned MAX_SELECTABLE_FACES = 3u; Selection ChooseFromHoverToSelect(); Selection ChooseFromHoverToDrag(); @@ -772,24 +807,31 @@ 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; + bool dimSolidModel; void DrawSnapGrid(Canvas *canvas); void AddPointToDraggedList(hEntity hp); @@ -812,7 +854,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, int delta); + void MouseScroll(double delta); void MouseLeave(); bool KeyboardEvent(Platform::KeyboardEvent event); void EditControlDone(const std::string &s); diff --git a/src/util.cpp b/src/util.cpp index 6840257f2..f14417cc9 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -138,9 +138,11 @@ static void MessageBox(const char *fmt, va_list va, bool error, } } - std::string::iterator it = description.begin(); - while(isspace(*it)) it++; - description = description.substr(it - description.begin()); + if(description.length() > 0) { + std::string::iterator it = description.begin(); + while(isspace(*it)) it++; + description = description.substr(it - description.begin()); + } Platform::MessageDialogRef dialog = CreateMessageDialog(SS.GW.window); if (!dialog) { @@ -426,12 +428,6 @@ Quaternion Quaternion::Mirror() const { } -Vector Vector::From(double x, double y, double z) { - Vector v; - v.x = x; v.y = y; v.z = z; - return v; -} - Vector Vector::From(hParam x, hParam y, hParam z) { Vector v; v.x = SK.GetParam(x)->val; @@ -446,50 +442,6 @@ bool Vector::EqualsExactly(Vector v) const { z == v.z); } -Vector Vector::Plus(Vector b) const { - Vector r; - - r.x = x + b.x; - r.y = y + b.y; - r.z = z + b.z; - - return r; -} - -Vector Vector::Minus(Vector b) const { - Vector r; - - r.x = x - b.x; - r.y = y - b.y; - r.z = z - b.z; - - return r; -} - -Vector Vector::Negated() const { - Vector r; - - r.x = -x; - r.y = -y; - r.z = -z; - - return r; -} - -Vector Vector::Cross(Vector b) const { - Vector r; - - r.x = -(z*b.y) + (y*b.z); - r.y = (z*b.x) - (x*b.z); - r.z = -(y*b.x) + (x*b.y); - - return r; -} - -double Vector::Dot(Vector b) const { - return (x*b.x + y*b.y + z*b.z); -} - double Vector::DirectionCosineWith(Vector b) const { Vector a = this->WithMagnitude(1); b = b.WithMagnitude(1); @@ -627,24 +579,6 @@ Vector Vector::ClosestPointOnLine(Vector p0, Vector dp) const { return this->Plus(n.WithMagnitude(d)); } -double Vector::MagSquared() const { - return x*x + y*y + z*z; -} - -double Vector::Magnitude() const { - return sqrt(x*x + y*y + z*z); -} - -Vector Vector::ScaledBy(double v) const { - Vector r; - - r.x = x * v; - r.y = y * v; - r.z = z * v; - - return r; -} - Vector Vector::WithMagnitude(double v) const { double m = Magnitude(); if(EXACT(m == 0)) { @@ -727,16 +661,6 @@ Vector Vector::ClampWithin(double minv, double maxv) const { return ret; } -void Vector::MakeMaxMin(Vector *maxv, Vector *minv) const { - maxv->x = max(maxv->x, x); - maxv->y = max(maxv->y, y); - maxv->z = max(maxv->z, z); - - minv->x = min(minv->x, x); - minv->y = min(minv->y, y); - minv->z = min(minv->z, z); -} - bool Vector::OutsideAndNotOn(Vector maxv, Vector minv) const { return (x > maxv.x + LENGTH_EPS) || (x < minv.x - LENGTH_EPS) || (y > maxv.y + LENGTH_EPS) || (y < minv.y - LENGTH_EPS) || diff --git a/src/view.cpp b/src/view.cpp index 14d09ba35..5f6de3299 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 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. @@ -35,8 +35,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 +72,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 +87,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: { 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/normal.png b/test/analysis/contour_area/normal.png index 9daf8eebb..3b50df9cd 100644 Binary files a/test/analysis/contour_area/normal.png and b/test/analysis/contour_area/normal.png differ diff --git a/test/constraint/angle/skew.png b/test/constraint/angle/skew.png index 97fb0dbf5..92d7577a4 100644 Binary files a/test/constraint/angle/skew.png and b/test/constraint/angle/skew.png differ diff --git a/test/constraint/arc_line_tangent/normal.png b/test/constraint/arc_line_tangent/normal.png index b885995f8..ab59db735 100644 Binary files a/test/constraint/arc_line_tangent/normal.png and b/test/constraint/arc_line_tangent/normal.png differ diff --git a/test/constraint/cubic_line_tangent/free_in_3d.png b/test/constraint/cubic_line_tangent/free_in_3d.png index 736cd73a2..5b5769238 100644 Binary files a/test/constraint/cubic_line_tangent/free_in_3d.png and b/test/constraint/cubic_line_tangent/free_in_3d.png differ diff --git a/test/constraint/cubic_line_tangent/normal.png b/test/constraint/cubic_line_tangent/normal.png index 66d86a0fc..394f8ff73 100644 Binary files a/test/constraint/cubic_line_tangent/normal.png and b/test/constraint/cubic_line_tangent/normal.png differ diff --git a/test/constraint/curve_curve_tangent/arc_arc.png b/test/constraint/curve_curve_tangent/arc_arc.png index c6ceb2abf..c83e0a889 100644 Binary files a/test/constraint/curve_curve_tangent/arc_arc.png and b/test/constraint/curve_curve_tangent/arc_arc.png differ diff --git a/test/constraint/curve_curve_tangent/arc_cubic.png b/test/constraint/curve_curve_tangent/arc_cubic.png index 647e4fe00..8c7f0dcf1 100644 Binary files a/test/constraint/curve_curve_tangent/arc_cubic.png and b/test/constraint/curve_curve_tangent/arc_cubic.png differ diff --git a/test/constraint/diameter/normal.png b/test/constraint/diameter/normal.png index c58025bd0..fc5762315 100644 Binary files a/test/constraint/diameter/normal.png and b/test/constraint/diameter/normal.png differ diff --git a/test/constraint/diameter/reference.png b/test/constraint/diameter/reference.png index b923aeab5..09a3f28ea 100644 Binary files a/test/constraint/diameter/reference.png and b/test/constraint/diameter/reference.png differ diff --git a/test/constraint/equal_angle/normal.png b/test/constraint/equal_angle/normal.png index 3f81a9543..aecf31558 100644 Binary files a/test/constraint/equal_angle/normal.png and b/test/constraint/equal_angle/normal.png differ diff --git a/test/constraint/equal_angle/normal.slvs b/test/constraint/equal_angle/normal.slvs index b1d538f1d..60e7b924c 100644 --- a/test/constraint/equal_angle/normal.slvs +++ b/test/constraint/equal_angle/normal.slvs @@ -119,7 +119,7 @@ Param.val=-5.00000000000000000000 AddParam Param.h.v.=00040011 -Param.val=5.00000000000000000000 +Param.val=5.00000000000000088818 AddParam Param.h.v.=00040013 @@ -147,7 +147,7 @@ Param.val=10.00000000000000000000 AddParam Param.h.v.=00060010 -Param.val=10.00000000000000000000 +Param.val=10.29878739785912600269 AddParam Param.h.v.=00060011 @@ -155,7 +155,7 @@ Param.val=10.00000000000000000000 AddParam Param.h.v.=00060013 -Param.val=5.00000000000000000000 +Param.val=5.29878739785912422633 AddParam Param.h.v.=00060014 @@ -163,7 +163,7 @@ Param.val=5.00000000000000000000 AddParam Param.h.v.=00070010 -Param.val=5.00000000000000000000 +Param.val=5.29878739785912422633 AddParam Param.h.v.=00070011 @@ -171,7 +171,7 @@ Param.val=5.00000000000000000000 AddParam Param.h.v.=00070013 -Param.val=10.00000000000000000000 +Param.val=10.29878739785912600269 AddParam Param.h.v.=00070014 @@ -310,7 +310,7 @@ Entity.type=2001 Entity.construction=0 Entity.workplane.v=80020000 Entity.actPoint.x=-5.00000000000000000000 -Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000088818 Entity.actVisible=1 AddEntity @@ -363,7 +363,7 @@ Entity.h.v=00060001 Entity.type=2001 Entity.construction=0 Entity.workplane.v=80020000 -Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.x=10.29878739785912600269 Entity.actPoint.y=10.00000000000000000000 Entity.actVisible=1 AddEntity @@ -372,7 +372,7 @@ Entity.h.v=00060002 Entity.type=2001 Entity.construction=0 Entity.workplane.v=80020000 -Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.x=5.29878739785912422633 Entity.actPoint.y=5.00000000000000000000 Entity.actVisible=1 AddEntity @@ -390,7 +390,7 @@ Entity.h.v=00070001 Entity.type=2001 Entity.construction=0 Entity.workplane.v=80020000 -Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.x=5.29878739785912422633 Entity.actPoint.y=5.00000000000000000000 Entity.actVisible=1 AddEntity @@ -399,7 +399,7 @@ Entity.h.v=00070002 Entity.type=2001 Entity.construction=0 Entity.workplane.v=80020000 -Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.x=10.29878739785912600269 Entity.actPoint.y=5.00000000000000000000 Entity.actVisible=1 AddEntity diff --git a/test/constraint/equal_angle/normal_old_version.slvs b/test/constraint/equal_angle/normal_old_version.slvs new file mode 100644 index 000000000..b1d538f1d --- /dev/null +++ b/test/constraint/equal_angle/normal_old_version.slvs @@ -0,0 +1,463 @@ +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=-10.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070014 +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=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000007 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +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=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +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=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +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=00070000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00070001 +Entity.point[1].v=00070002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070001 +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=00070002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.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 + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00060002 +Constraint.ptB.v=00070001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=54 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.entityC.v=00070000 +Constraint.entityD.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/equal_angle/test.cpp b/test/constraint/equal_angle/test.cpp index f1e5fce94..e96c709f0 100644 --- a/test/constraint/equal_angle/test.cpp +++ b/test/constraint/equal_angle/test.cpp @@ -8,12 +8,12 @@ TEST_CASE(normal_roundtrip) { TEST_CASE(normal_migrate_from_v20) { CHECK_LOAD("normal_v20.slvs"); - CHECK_SAVE("normal.slvs"); + CHECK_SAVE("normal_old_version.slvs"); } TEST_CASE(normal_migrate_from_v22) { CHECK_LOAD("normal_v22.slvs"); - CHECK_SAVE("normal.slvs"); + CHECK_SAVE("normal_old_version.slvs"); } TEST_CASE(other_roundtrip) { diff --git a/test/constraint/equal_line_arc_len/normal.png b/test/constraint/equal_line_arc_len/normal.png index bb0e2181d..b2456fecb 100644 Binary files a/test/constraint/equal_line_arc_len/normal.png and b/test/constraint/equal_line_arc_len/normal.png differ diff --git a/test/constraint/equal_line_arc_len/pi.png b/test/constraint/equal_line_arc_len/pi.png index 9207fcaa1..4e06218a0 100644 Binary files a/test/constraint/equal_line_arc_len/pi.png and b/test/constraint/equal_line_arc_len/pi.png differ diff --git a/test/constraint/equal_line_arc_len/tau.png b/test/constraint/equal_line_arc_len/tau.png index 5336ac964..1abd363cd 100644 Binary files a/test/constraint/equal_line_arc_len/tau.png and b/test/constraint/equal_line_arc_len/tau.png differ diff --git a/test/constraint/equal_radius/normal.png b/test/constraint/equal_radius/normal.png index c7f8e797d..2d338c93b 100644 Binary files a/test/constraint/equal_radius/normal.png and b/test/constraint/equal_radius/normal.png differ diff --git a/test/constraint/pt_face_distance/normal.png b/test/constraint/pt_face_distance/normal.png index c92fc4f3e..5fe42722b 100644 Binary files a/test/constraint/pt_face_distance/normal.png and b/test/constraint/pt_face_distance/normal.png differ diff --git a/test/constraint/pt_face_distance/reference.png b/test/constraint/pt_face_distance/reference.png index b01d4ea8f..6e392b3c6 100644 Binary files a/test/constraint/pt_face_distance/reference.png and b/test/constraint/pt_face_distance/reference.png differ diff --git a/test/constraint/pt_on_circle/normal.png b/test/constraint/pt_on_circle/normal.png index fe0734c2b..406aa817d 100644 Binary files a/test/constraint/pt_on_circle/normal.png and b/test/constraint/pt_on_circle/normal.png differ diff --git a/test/constraint/pt_on_face/normal.png b/test/constraint/pt_on_face/normal.png index 1f7a7e895..0d6ef8215 100644 Binary files a/test/constraint/pt_on_face/normal.png and b/test/constraint/pt_on_face/normal.png differ diff --git a/test/core/path/test.cpp b/test/core/path/test.cpp index 926395649..90ff12455 100644 --- a/test/core/path/test.cpp +++ b/test/core/path/test.cpp @@ -83,6 +83,7 @@ TEST_CASE(extension) { } TEST_CASE(with_extension) { + CHECK_EQ_STR(Path::From("foo.bar").WithExtension("").raw, "foo"); CHECK_EQ_STR(Path::From("foo.bar").WithExtension("baz").raw, "foo.baz"); CHECK_EQ_STR(Path::From("foo").WithExtension("baz").raw, "foo.baz"); } @@ -124,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 546a79cb8..aa66c1fee 100644 --- a/test/debugtool.cpp +++ b/test/debugtool.cpp @@ -6,7 +6,7 @@ #include "solvespace.h" int main(int argc, char **argv) { - std::vector args = InitPlatform(argc, argv); + std::vector args = Platform::InitCli(argc, argv); if(args.size() == 3 && args[1] == "expr") { std::string expr = args[2], err; diff --git a/test/harness.cpp b/test/harness.cpp index 601242582..2eee03738 100644 --- a/test/harness.cpp +++ b/test/harness.cpp @@ -23,9 +23,11 @@ namespace Platform { #ifdef TEST_BUILD_ON_WINDOWS -static char BUILD_PATH_SEP = '\\'; +static const char *VALID_BUILD_PATH_SEPS = "/\\"; +static char BUILD_PATH_SEP = '\\'; #else -static char BUILD_PATH_SEP = '/'; +static const char *VALID_BUILD_PATH_SEPS = "/"; +static char BUILD_PATH_SEP = '/'; #endif static std::string BuildRoot() { @@ -33,7 +35,7 @@ static std::string BuildRoot() { if(!rootDir.empty()) return rootDir; rootDir = __FILE__; - rootDir.erase(rootDir.rfind(BUILD_PATH_SEP) + 1); + rootDir.erase(rootDir.find_last_of(VALID_BUILD_PATH_SEPS) + 1); return rootDir; } @@ -156,7 +158,7 @@ Platform::Path Test::Helper::GetAssetPath(std::string testFile, std::string asse assetName.insert(assetName.rfind('.'), "." + mangle); } testFile.erase(0, BuildRoot().size()); - testFile.erase(testFile.rfind(BUILD_PATH_SEP) + 1); + testFile.erase(testFile.find_last_of(VALID_BUILD_PATH_SEPS) + 1); return HostRoot().Join(Platform::Path::FromPortable(testFile + assetName)); } @@ -333,7 +335,7 @@ int Test::Case::Register(Test::Case testCase) { } int main(int argc, char **argv) { - std::vector args = InitPlatform(argc, argv); + std::vector args = Platform::InitCli(argc, argv); std::regex filter(".*"); if(args.size() == 1) { @@ -354,7 +356,7 @@ int main(int argc, char **argv) { for(Test::Case &testCase : *testCasesPtr) { std::string testCaseName = testCase.fileName; testCaseName.erase(0, BuildRoot().size()); - testCaseName.erase(testCaseName.rfind(BUILD_PATH_SEP)); + testCaseName.erase(testCaseName.find_last_of(VALID_BUILD_PATH_SEPS)); testCaseName += BUILD_PATH_SEP + testCase.caseName; std::smatch filterMatch; diff --git a/test/request/arc_of_circle/normal.png b/test/request/arc_of_circle/normal.png index e006a1d84..2839007cc 100644 Binary files a/test/request/arc_of_circle/normal.png and b/test/request/arc_of_circle/normal.png differ diff --git a/test/request/circle/free_in_3d.png b/test/request/circle/free_in_3d.png index 86257e073..31e2499f9 100644 Binary files a/test/request/circle/free_in_3d.png and b/test/request/circle/free_in_3d.png differ diff --git a/test/request/circle/free_in_3d_dof.png b/test/request/circle/free_in_3d_dof.png index 0a4422b0e..2b5dff0e5 100644 Binary files a/test/request/circle/free_in_3d_dof.png and b/test/request/circle/free_in_3d_dof.png differ diff --git a/test/request/circle/normal.png b/test/request/circle/normal.png index 86257e073..31e2499f9 100644 Binary files a/test/request/circle/normal.png and b/test/request/circle/normal.png differ diff --git a/test/request/circle/normal_dof.png b/test/request/circle/normal_dof.png index a032f1aa3..0e62eba3d 100644 Binary files a/test/request/circle/normal_dof.png and b/test/request/circle/normal_dof.png differ diff --git a/test/request/cubic/normal.png b/test/request/cubic/normal.png index c4fc05047..e7eb94d81 100644 Binary files a/test/request/cubic/normal.png and b/test/request/cubic/normal.png differ diff --git a/test/request/cubic_periodic/normal.png b/test/request/cubic_periodic/normal.png index 7b935c824..f98b7c60a 100644 Binary files a/test/request/cubic_periodic/normal.png and b/test/request/cubic_periodic/normal.png differ diff --git a/test/request/image/linked.png b/test/request/image/linked.png new file mode 100644 index 000000000..8667e3414 Binary files /dev/null and b/test/request/image/linked.png differ diff --git a/test/request/image/normal.png b/test/request/image/normal.png new file mode 100644 index 000000000..a44f72d7d Binary files /dev/null and b/test/request/image/normal.png differ diff --git a/test/request/image/test.cpp b/test/request/image/test.cpp index da0954e0e..a9507beed 100644 --- a/test/request/image/test.cpp +++ b/test/request/image/test.cpp @@ -2,13 +2,12 @@ TEST_CASE(normal_roundtrip) { CHECK_LOAD("normal.slvs"); - // Can't render images through cairo for now. - // CHECK_RENDER("normal.png"); + CHECK_RENDER("normal.png"); CHECK_SAVE("normal.slvs"); } TEST_CASE(linked_roundtrip) { CHECK_LOAD("linked.slvs"); - // CHECK_RENDER("linked.png"); + CHECK_RENDER("linked.png"); CHECK_SAVE("linked.slvs"); } 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/normal.png b/test/request/ttf_text/normal.png index 9aa4d1853..b023b8388 100644 Binary files a/test/request/ttf_text/normal.png and b/test/request/ttf_text/normal.png differ 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");