Merge branch 'solvespace:master' into python

pull/493/head
Yuan Chang 2021-12-02 09:18:36 +08:00 committed by GitHub
commit 91d3477314
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 10607 additions and 3619 deletions

View File

@ -1,27 +1,52 @@
#!/bin/sh -xe
mkdir build || true
cd build
OSX_TARGET="10.9"
ENABLE_SANITIZERS="OFF"
if [ "$1" = "release" ]; then
BUILD_TYPE=RelWithDebInfo
cmake \
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_TARGET}" \
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
-DENABLE_OPENMP="ON" \
-DENABLE_LTO="ON" \
..
BUILD_TYPE="RelWithDebInfo"
ENABLE_LTO="ON"
else
BUILD_TYPE=Debug
cmake \
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_TARGET}" \
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
-DENABLE_OPENMP="ON" \
-DENABLE_SANITIZERS="ON" \
..
BUILD_TYPE="Debug"
ENABLE_LTO="OFF"
fi
cmake --build . --config "${BUILD_TYPE}" -- -j$(nproc)
make -j$(nproc) test_solvespace
# 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}" \
..
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

View File

@ -1,4 +1,16 @@
#!/bin/sh -xe
brew install libomp
git submodule update --init
if [ "$1" = "ci" ]; then
armloc=$(brew fetch --bottle-tag=arm64_big_sur libomp | grep -i downloaded | grep tar.gz | cut -f2 -d:)
x64loc=$(brew fetch --bottle-tag=big_sur 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

View File

@ -1,8 +1,29 @@
#!/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/lib/libomp.dylib"
openmp="bin/SolveSpace.app/Contents/Resources/libomp.dylib"
app="bin/SolveSpace.app"
dmg="bin/SolveSpace.dmg"
bundle_id="com.solvespace.solvespace"

View File

@ -9,7 +9,16 @@ on:
- created
jobs:
cancel_previous_runs:
runs-on: ubuntu-latest
name: Cancel Previous Runs
steps:
- uses: styfle/cancel-workflow-action@0.8.0
with:
access_token: ${{ github.token }}
test_ubuntu:
needs: [cancel_previous_runs]
runs-on: ubuntu-18.04
name: Test Ubuntu
steps:
@ -20,6 +29,7 @@ jobs:
run: .github/scripts/build-ubuntu.sh
test_windows:
needs: [cancel_previous_runs]
runs-on: windows-2019
name: Test Windows
steps:
@ -32,14 +42,15 @@ jobs:
shell: bash
test_macos:
runs-on: macos-latest
needs: [cancel_previous_runs]
runs-on: macos-10.15
name: Test macOS
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
run: .github/scripts/install-macos.sh
run: .github/scripts/install-macos.sh ci
- name: Build & Test
run: .github/scripts/build-macos.sh
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]
@ -80,13 +91,13 @@ jobs:
build_release_macos:
needs: [test_ubuntu, test_windows, test_macos]
name: Build Release macOS
runs-on: macos-latest
runs-on: macos-10.15
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
run: .github/scripts/install-macos.sh
run: .github/scripts/install-macos.sh ci
- name: Build & Test
run: .github/scripts/build-macos.sh release
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:
@ -107,6 +118,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Fetch Tags
run: git fetch --force --tags
- name: Set Up Source
run: rsync --filter=":- .gitignore" -r ./ pkg/snap/solvespace-snap-src
- name: Build Snap
@ -135,7 +148,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: docker/setup-qemu-action@v1
with:
image: tonistiigi/binfmt@sha256:df15403e06a03c2f461c1f7938b171fda34a5849eb63a70e2a2109ed5a778bde
- uses: actions/checkout@v2
- name: Fetch Tags
run: git fetch --force --tags
- name: Set Up Source
run: rsync --filter=":- .gitignore" -r ./ pkg/snap/solvespace-snap-src
- name: Build Snap
@ -159,37 +176,10 @@ jobs:
snap: ${{ steps.build.outputs.snap }}
release: edge,beta
update_edge_release:
name: Update Edge Release
needs: [build_release_windows, build_release_windows_openmp, build_release_macos]
if: always() && github.event_name == 'push'
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Delete Old Edge Release
uses: dev-drprasad/delete-tag-and-release@v0.1.2
with:
delete_release: true
tag_name: edge
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create New Edge Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: edge
release_name: Edge
prerelease: true
draft: false
body: ${{ github.event.head_commit.message }}
upload_release_assets:
name: Upload Release Assets
needs: [build_release_windows, build_release_windows_openmp, build_release_macos, update_edge_release]
if: always()
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
@ -197,15 +187,9 @@ jobs:
- name: Get Release Upload URL
id: get_upload_url
env:
event_name: ${{ github.event_name }}
event: ${{ toJson(github.event) }}
edge_upload_url: ${{ needs.update_edge_release.outputs.upload_url }}
run: |
if [ "$event_name" = "release" ]; then
upload_url=$(echo "$event" | jq -r ".release.upload_url")
else
upload_url="$edge_upload_url"
fi
echo "::set-output name=upload_url::$upload_url"
echo "Upload URL: $upload_url"
- name: Upload solvespace.exe

View File

@ -34,11 +34,11 @@ jobs:
shell: bash
test_macos:
runs-on: macos-latest
runs-on: macos-10.15
name: Test macOS
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
run: .github/scripts/install-macos.sh
run: .github/scripts/install-macos.sh ci
- name: Build & Test
run: .github/scripts/build-macos.sh
run: .github/scripts/build-macos.sh debug arm64 && .github/scripts/build-macos.sh debug x86_64

View File

@ -1,283 +1,329 @@
Changelog
=========
3.x - since the 3.0 release, only available in edge builds
---
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 "∆" 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:
* More changes to the ID list implementation.
3.0
---
New sketch features:
* New intersection boolean operation for solid models.
* New groups, revolution and helical extrusion.
* Extrude, lathe, translate and rotate groups can use the "assembly"
* 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,
* 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
* 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
* 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.
* 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
* 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
* 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,
* 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.
* 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
* 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
* 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
* 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
* 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.
* 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
* 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
* 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
* 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
* 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
* 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
* Swap vertical and horizontal constraints when pasting rotated by 90/270
degrees.
New export/import features:
* Link IDF circuit boards in an assembly (.emn files)
* Three.js: allow configuring projection for exported model, and initially
* 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
* Wavefront OBJ: a material file is exported alongside the model, containing
mesh color information.
* DXF/DWG: 3D DXF files are imported as construction entities, in 3d.
* [ADDED 2019-02-25](https://github.com/solvespace/solvespace/pull/384) and [REMOVED 2020-11-13](https://github.com/solvespace/solvespace/issues/795):
Q3D: [Q3D](https://github.com/q3k/q3d/) triangle meshes can now be
exported. This format allows to easily hack on triangle mesh data created
in SolveSpace, supports colour information and is more space efficient than
most other formats.
* VRML (WRL) triangle meshes can now be exported, useful for e.g. [KiCAD](http://kicad.org).
* Export 2d section: custom styled entities that lie in the same
* 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.
* 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
* 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"
* 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,
* 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
* 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
* 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
* 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
* When selecting a point and a line, projected distance to current
workplane is displayed.
Other new features:
* Improvements to the text window for selected entities and constraints.
* Ambient light source added in text window to allow flat shaded renderings.
* New command-line interface, for batch exporting and more.
* The graphical interface now supports HiDPI screens on every OS.
* New option to lock Z axis to be always vertical, like in SketchUp.
* New button to hide all construction entities.
* New link to match the on-screen size of the sketch with its actual size,
* 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,
* 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,
* 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
* 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"
* 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
* New cmake build options using -DENABLE_OPENMP=yes and -DENABLE_LTO=yes
to enable support for multi-threading and link-time optimization.
* "Shift+Scroll" for ten times finer zoom.
* Chinese translation
* "Shift+Scroll" for ten times finer zoom.
* Translations: Chinese, French, German, Russian, Turkish, Ukrainian.
Bugs fixed:
* Fixed broken --view options for command line thumbnail image creation.
* Some errors in Triangulation of surfaces.
* Some NURNS boolean operations that failed particularly on surfaces
* 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
* 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
* 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
* 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
* 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
* 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
* 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
* 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
* 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.
* 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
* 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
* 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
* 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.
* 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
* 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
* 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
* 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
* 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
* 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 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
* 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.
* 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
* 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
* 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.
* 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
* 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
* 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
* 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.
* 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
* 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
* 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
* 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,
* 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.
* 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.
* 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,
* 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
* The vector font is grid-fitted when rendered on screen to make it easier
to read regardless of its size.
2.0

View File

@ -1,4 +1,5 @@
# cmake configuration
cmake_minimum_required(VERSION 3.9...3.19)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(FATAL_ERROR
@ -7,17 +8,10 @@ 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()
if(NOT CMAKE_VERSION VERSION_LESS 3.9)
# LTO/IPO with non-Intel compilers on Linux requires policy CMP0069 to be set to NEW.
# Set it explicitly until cmake_minimum_required is raised to >= 3.9.
cmake_policy(SET CMP0069 NEW)
endif()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
"${CMAKE_SOURCE_DIR}/cmake/")
cmake_policy(SET CMP0048 OLD)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
@ -27,10 +21,15 @@ set(CMAKE_USER_MAKE_RULES_OVERRIDE
set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX
"${CMAKE_SOURCE_DIR}/cmake/cxx_flag_overrides.cmake")
if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
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
@ -40,10 +39,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 LANGUAGES C CXX ASM)
set(ENABLE_GUI ON CACHE BOOL
"Whether the graphical interface is enabled")
@ -68,10 +67,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 <regex>. meh.
@ -82,11 +77,13 @@ 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)
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)
@ -210,6 +207,12 @@ if(WIN32 OR APPLE)
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)
@ -222,11 +225,14 @@ if(WIN32 OR APPLE)
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_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)
@ -275,6 +281,7 @@ if(ENABLE_GUI)
elseif(APPLE)
find_package(OpenGL REQUIRED)
find_library(APPKIT_LIBRARY AppKit REQUIRED)
set(util_LIBRARIES ${APPKIT_LIBRARY})
else()
find_package(OpenGL REQUIRED)
find_package(SpaceWare)

View File

@ -37,16 +37,19 @@ the SolveSpace maintainers for each stable release.
### Via Snap Store
Builds from master are automatically released to the `edge` channel in the Snap Store. Those packages contain the latest improvements, but receive less testing than release builds.
Official releases can be installed from the `stable` channel.
Future official releases will appear in the `stable` channel.
Builds from master are automatically released to the `edge` channel in the Snap Store. Those packages contain the latest improvements, but receive less testing than release builds.
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/solvespace)
Or install from a terminal:
```
snap install --edge solvespace
# for the latest stable release:
snap install solvespace
# for the bleeding edge builds from master:
snap install solvespace --edge
```
### Via third-party binary packages
@ -58,6 +61,20 @@ and cannot guarantee their functionality.
[notesalexp]: https://notesalexp.org/packages/en/source/solvespace/
### 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:
- [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)
Extract the downloaded archive and install or execute the contained file as is appropriate for your platform.
### Via source code
See below.

View File

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

View File

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

View File

@ -46,7 +46,7 @@ POINT_N_ROT_TRANS: Rotates a point via quaternion param[3],param[4],param[5],par
POINT_N_COPY: A non-transformed copy of a point - numeric copy?
POINT_N_ROT_AA: A point rotated arount point param[0],param[1],param[2] Where the
POINT_N_ROT_AA: A point rotated around point param[0],param[1],param[2] Where the
angle is given by param[3]*timesApplied (times 2?) and the axis
of rotation defined by param[4],param[5],param[6]
@ -130,7 +130,7 @@ the entity itself.
The ForceTo() functions are shortcuts for using the solver. They are passed the
desired location of a point (or orientation of a normal...) and have the opportunity
to back-calculate what the group parameters should be to place it there. This is
used for mouse dragging of copied entites. It is notable that the constraints will
used for mouse dragging of copied entities. It is notable that the constraints will
still be applied afterward, but this is a good shortcut.
When creating a new entity transformation, the first thing to do is define the

@ -1 +1 @@
Subproject commit e9c3d83d5a04835806287f1e8c0f2d3a962d6673
Subproject commit dbe3e0c43e549a1602286144d94b0666549b18e6

@ -1 +1 @@
Subproject commit 27627843648ef84aee1621976f25bee5946e6bda
Subproject commit 4e643b6d3178e0ea2a093b7e14fe621631a91e4b

View File

@ -113,6 +113,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;

View File

@ -1,19 +1,29 @@
{
"app-id": "com.solvespace.SolveSpace",
"runtime": "org.gnome.Platform",
"runtime-version": "3.30",
"sdk": "org.gnome.Sdk",
"runtime": "org.freedesktop.Platform",
"runtime-version": "20.08",
"sdk": "org.freedesktop.Sdk",
"finish-args": [
/* Access to display server and OpenGL */
"--share=ipc", "--socket=fallback-x11", "--socket=wayland", "--device=dri",
"--share=ipc",
"--socket=fallback-x11",
"--socket=wayland",
"--device=dri",
/* Access to save files */
"--filesystem=home"
],
"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/*/include",
"*.a",
"*.la",
"*.m4",
"/lib/libslvs*.so*",
"/lib/libglibmm_generate_extra_defs*.so*",
"/share/pkgconfig",
"*.pc",
"/share/man",
"/share/doc",
"/share/aclocal",
/* mm-common junk */
"/bin/mm-common-prepare",
@ -26,8 +36,8 @@
"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.2.tar.xz",
"sha256": "a2a99f3fa943cf662f189163ed39a2cfc19a428d906dd4f92b387d3659d1641d"
}
]
},
@ -39,21 +49,20 @@
"sources": [
{
"type": "archive",
"url": "http://ftp.gnome.org/pub/GNOME/sources/libsigc++/2.10/libsigc++-2.10.1.tar.xz",
"sha256": "c9a25f26178c6cbb147f9904d8c533b5a5c5111a41ac2eb781eb734eea446003"
"url": "https://download.gnome.org/sources/libsigc++/2.10/libsigc%2B%2B-2.10.6.tar.xz",
"sha256": "dda176dc4681bda9d5a2ac1bc55273bdd381662b7a6d49e918267d13e8774e1b"
}
]
},
{
"name": "glibmm",
"config-opts": [
"--disable-documentation"
],
"config-opts": [],
"buildsystem": "meson",
"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.64/glibmm-2.64.5.tar.xz",
"sha256": "508fc86e2c9141198aa16c225b16fd6b911917c0d3817602652844d0973ea386"
}
]
},
@ -98,14 +107,13 @@
},
{
"name": "gtkmm",
"config-opts": [
"--disable-documentation"
],
"config-opts": [],
"buildsystem": "meson",
"sources": [
{
"type": "archive",
"url": "http://ftp.gnome.org/pub/GNOME/sources/gtkmm/3.24/gtkmm-3.24.1.tar.xz",
"sha256": "ddfe42ed2458a20a34de252854bcf4b52d3f0c671c045f56b42aa27c7542d2fd"
"url": "https://download.gnome.org/sources/gtkmm/3.24/gtkmm-3.24.4.tar.xz",
"sha256": "9beb71c3e90cfcfb790396b51e3f5e7169966751efd4f3ef9697114be3be6743"
}
]
},
@ -113,6 +121,7 @@
"name": "libjson-c",
"sources": [
{
/* 0.15-nodoc doesn't build */
"type": "archive",
"url": "https://s3.amazonaws.com/json-c_releases/releases/json-c-0.13.1-nodoc.tar.gz",
"sha256": "94a26340c0785fcff4f46ff38609cf84ebcd670df0c8efd75d039cc951d80132"
@ -125,8 +134,8 @@
"name": "SolveSpace",
"sources": [
{
"type": "git",
"path": "/home/whitequark/Projects/solvespace"
"type": "dir",
"path": "../.."
}
],
"buildsystem": "cmake",

View File

@ -31,7 +31,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)
@ -134,6 +134,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 +183,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 +199,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 +207,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()
@ -257,6 +266,8 @@ add_resources(
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
fonts/unifont.hex.gz

Binary file not shown.

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.solvespace.SolveSpace</id>
<name>SolveSpace</name>
<summary>A free (GPLv3) parametric 3d CAD tool</summary>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-or-later</project_license>
<update_contact>ryan_AT_ryanpavlik.com</update_contact>
<categories>
<category>Graphics</category>
<category>3DGraphics</category>
<category>Engineering</category>
</categories>
<description>
<p>
SolveSpace is a free (GPLv3) parametric 3d CAD tool. Applications include:
</p>
<ul>
<li>Modeling 3d parts — draw with extrudes, revolves, and Boolean operations</li>
<li>Modeling 2d parts — draw the part as a single section, and export; use 3d assembly to verify fit</li>
<li>Modeling 3d-printed parts — export the STL or other triangle mesh expected by most slicers</li>
<li>Preparing 2D CAM data — export 2d vector art for a waterjet machine or laser cutter</li>
<li>Mechanism design — use the constraint solver to simulate planar or spatial linkages</li>
<li>Plane and solid geometry — replace hand-solved trigonometry with a live dimensioned drawing</li>
</ul>
</description>
<url type="homepage">https://solvespace.com</url>
<url type="bugtracker">https://github.com/solvespace/solvespace/issues</url>
<launchable type="desktop-id">@DESKTOP_FILE_NAME@</launchable>
<provides>
<mediatype>application/x-solvespace</mediatype>
</provides>
<content_rating type="oars-1.0" />
<releases>
<release version="3.0" date="2021-04-18" type="stable">
<description>
<p>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.</p>
</description>
<url>https://github.com/solvespace/solvespace/releases/tag/v3.0</url>
</release>
<release version="3.0~rc2" date="2021-01-17" type="development">
<description>
<p>Second release candidate for the 3.0 stable release.</p>
</description>
<url>https://github.com/solvespace/solvespace/releases/tag/v3.0.rc2</url>
</release>
<release version="3.0~rc1" date="2020-11-18" type="development">
<description>
<p>First release candidate for the 3.0 stable release.</p>
</description>
<url>https://github.com/solvespace/solvespace/releases/tag/v3.0.rc1</url>
</release>
<release version="2.3" date="2016-12-23" type="stable">
<description>
<p>Bug-fix release in the 2.x series, fixing some crashes.</p>
</description>
<url>https://github.com/solvespace/solvespace/releases/tag/v2.3</url>
</release>
<release version="2.2" date="2016-10-16" type="stable">
<description>
<p>Bug-fix release, including performance improvements.</p>
</description>
<url>https://github.com/solvespace/solvespace/releases/tag/v2.2</url>
</release>
<release version="2.1" date="2016-06-11" type="stable">
<description>
<p>Introduced *nix compatibility, internationalization, technical drawing mode, improved import and export, and other features and fixes.</p>
</description>
<url>https://github.com/solvespace/solvespace/releases/tag/v2.1</url>
</release>
</releases>
</component>

View File

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

View File

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

View File

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

View File

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

View File

@ -6,5 +6,5 @@ Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace
MimeType=application/x-solvespace
Icon=com.solvespace.SolveSpace
Type=Application
Categories=Graphics
Categories=Graphics;3DGraphics;Engineering;
Keywords=parametric;cad;2d;3d;

View File

@ -6,5 +6,5 @@ Exec=solvespace
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;

View File

@ -6,5 +6,5 @@ Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace
MimeType=application/x-solvespace
Icon=solvespace
Type=Application
Categories=Graphics
Categories=Graphics;3DGraphics;Engineering;
Keywords=parametric;cad;2d;3d;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 B

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 B

After

Width:  |  Height:  |  Size: 264 B

View File

@ -3,6 +3,8 @@
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,简体中文

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2276
res/locales/es_AR.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2265
res/locales/tr_TR.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -117,6 +117,7 @@ if(WIN32)
${SPACEWARE_LIBRARIES})
elseif(APPLE)
add_compile_options(
-DGL_SILENCE_DEPRECATION
-fobjc-arc)
list(APPEND platform_SOURCES
@ -176,6 +177,7 @@ set(solvespace_core_SOURCES
groupmesh.cpp
importdxf.cpp
importidf.cpp
importmesh.cpp
mesh.cpp
modify.cpp
mouse.cpp
@ -338,7 +340,10 @@ if(ENABLE_GUI)
LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /INCREMENTAL:NO /OPT:REF")
elseif(APPLE)
set_target_properties(solvespace PROPERTIES
OUTPUT_NAME SolveSpace)
OUTPUT_NAME SolveSpace
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.solvespace"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
endif()
endif()
@ -401,17 +406,30 @@ endif()
# solvespace macOS package
if(APPLE)
set(bundle SolveSpace)
set(bundle_bin ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app/Contents/MacOS)
set(bundle_resources ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app/Contents/Resources/lib)
execute_process(
COMMAND mkdir -p ${bundle_resources}
COMMAND cp -p /usr/local/opt/libomp/lib/libomp.dylib ${bundle_resources}/libomp.dylib
)
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 ${CMAKE_COMMAND} -E make_directory ${bundle_bin}
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:solvespace-cli> ${bundle_bin}
COMMAND install_name_tool -change /usr/local/opt/libomp/lib/libomp.dylib "@executable_path/../Resources/lib/libomp.dylib" ${bundle_bin}/${bundle}
COMMAND cp -r ${CMAKE_BINARY_DIR}/Resources $<TARGET_BUNDLE_CONTENT_DIR:solvespace>
)
if(ENABLE_OPENMP)
execute_process(COMMAND install_name_tool -id ${LIBOMP_LINK_PATH} ${LIBOMP_LIB_PATH})
message("FROM " ${${LIBOMP_LIB_PATH}} "TO" $<TARGET_BUNDLE_CONTENT_DIR:solvespace>/Resources/libomp.dylib)
add_custom_command(TARGET solvespace POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${LIBOMP_LIB_PATH} $<TARGET_BUNDLE_CONTENT_DIR:solvespace>/Resources/libomp.dylib
COMMAND install_name_tool -change ${LIBOMP_LINK_PATH} ${LIBOMP_LINK_PATH_UTILS} $<TARGET_FILE:solvespace-debugtool>
)
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} $<TARGET_FILE:solvespace-testsuite>)
endif()
if(ENABLE_CLI)
add_custom_command(TARGET solvespace POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:solvespace-cli> $<TARGET_FILE_DIR:solvespace>
COMMENT "Bundling executable solvespace-cli"
VERBATIM)
endif()
endif()

View File

@ -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);
}
}
@ -282,7 +281,7 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
}
case Constraint::Type::HORIZONTAL:
case Constraint::Type::VERTICAL:
// When rotating 90 or 270 degrees, swap the vertical / horizontal constaints
// When rotating 90 or 270 degrees, swap the vertical / horizontal constraints
if (EXACT(fmod(theta + (PI/2), PI) == 0)) {
if(c.type == Constraint::Type::HORIZONTAL) {
c.type = Constraint::Type::VERTICAL;

View File

@ -9,24 +9,6 @@
#include <omp.h>
#endif
void TextWindow::ScreenChangeLightDirection(int link, uint32_t v) {
SS.TW.ShowEditControl(8, ssprintf("%.2f, %.2f, %.2f", CO(SS.lightDir[v])));
SS.TW.edit.meaning = Edit::LIGHT_DIRECTION;
SS.TW.edit.i = v;
}
void TextWindow::ScreenChangeLightIntensity(int link, uint32_t v) {
SS.TW.ShowEditControl(31, ssprintf("%.2f", SS.lightIntensity[v]));
SS.TW.edit.meaning = Edit::LIGHT_INTENSITY;
SS.TW.edit.i = v;
}
void TextWindow::ScreenChangeLightAmbient(int link, uint32_t v) {
SS.TW.ShowEditControl(31, ssprintf("%.2f", SS.ambientIntensity));
SS.TW.edit.meaning = Edit::LIGHT_AMBIENT;
SS.TW.edit.i = 0;
}
void TextWindow::ScreenChangeColor(int link, uint32_t v) {
SS.TW.ShowEditControlWithColorPicker(13, SS.modelColor[v]);
@ -58,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;
}
@ -89,7 +66,7 @@ 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;
}
@ -171,7 +148,7 @@ void TextWindow::ScreenChangeCanvasSize(int link, uint32_t v) {
}
int col = 13;
if(v < 10) col = 11;
SS.TW.ShowEditControl(col, SS.MmToString(d));
SS.TW.ShowEditControl(col, SS.MmToString(d, true));
SS.TW.edit.meaning = Edit::CANVAS_SIZE;
SS.TW.edit.i = v;
}
@ -181,7 +158,12 @@ void TextWindow::ScreenChangeGCodeParameter(int link, uint32_t v) {
switch(link) {
case 'd':
SS.TW.edit.meaning = Edit::G_CODE_DEPTH;
buf += SS.MmToString(SS.gCode.depth);
buf += SS.MmToString(SS.gCode.depth, true);
break;
case 'h':
SS.TW.edit.meaning = Edit::G_CODE_SAFE_HEIGHT;
buf += SS.MmToString(SS.gCode.safeHeight, true);
break;
case 's':
@ -191,12 +173,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);
@ -227,20 +209,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, "%Bp ambient lighting "
"%2 %Fl%D%f%Ll[c]%E",
(i & 1) ? 'd' : 'a', i,
SS.ambientIntensity, &ScreenChangeLightAmbient);
Printf(false, "");
Printf(false, "%Ft chord tolerance (in percents)%E");
Printf(false, "%Ba %@ %% %Fl%Ll%f%D[change]%E; %@ mm, %d triangles",
@ -262,11 +230,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(),
@ -461,6 +424,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) {
@ -529,6 +497,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());

View File

@ -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;
@ -384,10 +388,27 @@ 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;
}
@ -401,10 +422,27 @@ 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;
}
@ -767,10 +805,19 @@ void Constraint::MenuConstrain(Command id) {
break;
case Command::COMMENT:
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);
} 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");

View File

@ -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;
@ -39,7 +43,11 @@ bool ConstraintBase::IsProjectible() const {
case Type::EQ_PT_LN_DISTANCES:
case Type::EQUAL_ANGLE:
case Type::LENGTH_RATIO:
case Type::ARC_ARC_LEN_RATIO:
case Type::ARC_LINE_LEN_RATIO:
case Type::LENGTH_DIFFERENCE:
case Type::ARC_ARC_DIFFERENCE:
case Type::ARC_LINE_DIFFERENCE:
case Type::SYMMETRIC:
case Type::SYMMETRIC_HORIZ:
case Type::SYMMETRIC_VERT:
@ -335,6 +343,110 @@ void ConstraintBase::GenerateEquations(IdList<Equation,hEquation> *l,
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);
EntityBase *b = SK.GetEntity(entityB);
@ -344,6 +456,110 @@ void ConstraintBase::GenerateEquations(IdList<Equation,hEquation> *l,
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();

View File

@ -210,16 +210,15 @@ void GraphicsWindow::SelectByMarquee() {
BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, VERY_NEGATIVE),
Vector::From(orig.mouse.x, orig.mouse.y, VERY_POSITIVE));
Entity *e;
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
if(e->group != SS.GW.activeGroup) continue;
if(e->IsFace() || e->IsDistance()) continue;
if(!e->IsVisible()) continue;
for(Entity &e : SK.entity) {
if(e.group != SS.GW.activeGroup) continue;
if(e.IsFace() || e.IsDistance()) continue;
if(!e.IsVisible()) continue;
bool entityHasBBox;
BBox entityBBox = e->GetOrGenerateScreenBBox(&entityHasBBox);
BBox entityBBox = e.GetOrGenerateScreenBBox(&entityHasBBox);
if(entityHasBBox && entityBBox.Overlaps(marqueeBBox)) {
MakeSelected(e->h);
MakeSelected(e.h);
}
}
}
@ -412,8 +411,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;
}
}

View File

@ -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);
@ -534,6 +538,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 +561,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 +590,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 +616,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 +629,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 +695,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 +737,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 +755,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 +780,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 +837,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 +860,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 +895,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 +913,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 +936,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 +946,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 +960,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 +988,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 +1012,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 +1035,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,6 +1051,41 @@ 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);
@ -1009,6 +1095,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 +1115,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 +1142,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 +1176,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 +1214,8 @@ s:
}
// 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 +1239,8 @@ s:
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 +1290,13 @@ s:
}
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 +1344,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 +1357,7 @@ bool Constraint::HasLabel() const {
return false;
}
}
bool Constraint::ShouldDrawExploded() const {
return SK.GetGroup(group)->ShouldDrawExploded();
}

View File

@ -26,7 +26,7 @@ void Entity::GenerateEdges(SEdgeList *el) {
List<Vector> 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();
}
@ -88,7 +88,7 @@ void Entity::GetReferencePoints(std::vector<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<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;
}
@ -466,6 +466,26 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) const {
}
}
bool Entity::ShouldDrawExploded() const {
return SK.GetGroup(group)->ShouldDrawExploded();
}
Vector Entity::ExplodeOffset() const {
if(ShouldDrawExploded() && workplane.v != 0) {
int requestIdx = SK.GetRequest(h.request())->groupRequestIndex;
double offset = SS.explodeDistance * (requestIdx + 1);
return SK.GetEntity(workplane)->Normal()->NormalN().ScaledBy(offset);
} else {
return Vector::From(0, 0, 0);
}
}
Vector Entity::PointGetDrawNum() const {
// As per EntityBase::PointGetNum but specifically for when drawing/rendering the point
// (and not when solving), so we can potentially draw it somewhere different
return PointGetNum().Plus(ExplodeOffset());
}
void Entity::Draw(DrawAs how, Canvas *canvas) {
if(!(how == DrawAs::HOVERED || how == DrawAs::SELECTED) &&
!IsVisible()) return;
@ -557,16 +577,17 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
pointStroke.unit = Canvas::Unit::PX;
Canvas::hStroke hcsPoint = canvas->GetStroke(pointStroke);
Vector p = PointGetDrawNum();
if(free) {
Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE);
analyzeStroke.width = 14.0;
analyzeStroke.layer = Canvas::Layer::FRONT;
Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke);
canvas->DrawPoint(PointGetNum(), hcsAnalyze);
canvas->DrawPoint(p, hcsAnalyze);
}
canvas->DrawPoint(PointGetNum(), hcsPoint);
canvas->DrawPoint(p, hcsPoint);
return;
}
@ -621,7 +642,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
tail = camera.projRight.ScaledBy(w/s).Plus(
camera.projUp. ScaledBy(h/s)).Minus(camera.offset);
} else {
tail = SK.GetEntity(point[0])->PointGetNum();
tail = SK.GetEntity(point[0])->PointGetDrawNum();
}
tail = camera.AlignToPixelGrid(tail);
@ -709,8 +730,32 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
case Type::TTF_TEXT: {
// Generate the rational polynomial curves, then piecewise linearize
// them, and display those.
if(!canvas->DrawBeziers(*GetOrGenerateBezierCurves(), hcs)) {
canvas->DrawEdges(*GetOrGenerateEdges(), hcs);
// Calculating the draw offset, if necessary.
const bool shouldExplode = ShouldDrawExploded();
Vector explodeOffset;
SBezierList offsetBeziers = {};
SBezierList *beziers = GetOrGenerateBezierCurves();
if(shouldExplode) {
explodeOffset = ExplodeOffset();
for(const SBezier& b : beziers->l) {
SBezier offset = b.TransformedBy(explodeOffset, Quaternion::IDENTITY, 1.0);
offsetBeziers.l.Add(&offset);
}
beziers = &offsetBeziers;
}
SEdgeList *edges = nullptr;
SEdgeList offsetEdges = {};
if(!canvas->DrawBeziers(*beziers, hcs)) {
edges = GetOrGenerateEdges();
if(shouldExplode) {
for(const SEdge &e : edges->l) {
offsetEdges.AddEdge(e.a.Plus(explodeOffset), e.b.Plus(explodeOffset), e.auxA, e.auxB, e.tag);
}
edges = &offsetEdges;
}
canvas->DrawEdges(*edges, hcs);
}
if(type == Type::CIRCLE) {
Entity *dist = SK.GetEntity(distance);
@ -720,12 +765,14 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE);
analyzeStroke.layer = Canvas::Layer::FRONT;
Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke);
if(!canvas->DrawBeziers(*GetOrGenerateBezierCurves(), hcsAnalyze)) {
canvas->DrawEdges(*GetOrGenerateEdges(), hcsAnalyze);
if(!canvas->DrawBeziers(*beziers, hcsAnalyze)) {
canvas->DrawEdges(*edges, hcsAnalyze);
}
}
}
}
offsetBeziers.Clear();
offsetEdges.Clear();
return;
}
case Type::IMAGE: {
@ -757,7 +804,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]);

258
src/dsc.h
View File

@ -10,6 +10,7 @@
#include "solvespace.h"
#include <type_traits>
#include <vector>
/// Trait indicating which types are handle types and should get the associated operators.
/// Specialize for each handle type and inherit from std::true_type.
@ -371,15 +372,28 @@ public:
}
};
template<class T, class H> class IdList;
// Comparison functor used by IdList and related classes
template <class T, class H>
struct CompareId {
bool operator()(T const& lhs, T const& rhs) const {
return lhs.h.v < rhs.h.v;
CompareId(const IdList<T, H> *list) {
idlist = list;
}
bool operator()(T const& lhs, H rhs) const {
return lhs.h.v < rhs.v;
bool operator()(int lhs, T const& rhs) const {
return idlist->elemstore[lhs].h.v < rhs.h.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<T, H> *idlist;
};
// A list, where each element has an integer identifier. The list is kept
@ -387,148 +401,152 @@ struct CompareId {
// id.
template <class T, class H>
class IdList {
T *elem = nullptr;
int elemsAllocated = 0;
std::vector<T> elemstore;
std::vector<int> elemidx;
std::vector<int> freelist;
public:
int n = 0;
int n = 0; // PAR@@@@@ make this private to see all interesting and suspicious places in SoveSpace ;-)
friend struct CompareId<T, H>;
using Compare = CompareId<T, H>;
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;
}
// 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<T, H> *l) : position(0), list(l) {
if(list) {
if(list->elemstore.size() && list->elemidx.size()) {
elem = &(list->elemstore[list->elemidx[position]]);
}
}
};
iterator(IdList<T, H> *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<T, H> *list;
};
bool IsEmpty() const {
return n == 0;
}
void AllocForOneMore() {
if(n >= elemsAllocated) {
ReserveMore((elemsAllocated + 32)*2 - n);
}
}
uint32_t MaximumId() {
if(IsEmpty()) {
return 0;
} else {
return Last()->h.v;
return elemstore[elemidx.back()].h.v;
}
}
H AddAndAssignId(T *t) {
t->h.v = (MaximumId() + 1);
AllocForOneMore();
// Copy-construct at the end of the list.
new(&elem[n]) T(*t);
// Add at the end of the list.
elemstore.push_back(*t);
elemidx.push_back(elemstore.size()-1);
++n;
return t->h;
}
T * LowerBound(T const& t) {
if(IsEmpty()) {
return nullptr;
}
auto it = std::lower_bound(begin(), end(), t, Compare());
return it;
}
T * LowerBound(H const& h) {
if(IsEmpty()) {
return nullptr;
}
auto it = std::lower_bound(begin(), end(), h, Compare());
return it;
}
int LowerBoundIndex(T const& t) {
if(IsEmpty()) {
return 0;
}
auto it = LowerBound(t);
auto idx = std::distance(begin(), it);
auto i = static_cast<int>(idx);
return i;
}
void ReserveMore(int howMuch) {
if(n + howMuch > elemsAllocated) {
elemsAllocated = n + howMuch;
T *newElem = (T *)::operator new[]((size_t)elemsAllocated*sizeof(T));
for(int i = 0; i < n; i++) {
new(&newElem[i]) T(std::move(elem[i]));
elem[i].~T();
}
::operator delete[](elem);
elem = newElem;
}
elemstore.reserve(elemstore.size() + howMuch);
elemidx.reserve(elemidx.size() + howMuch);
// freelist.reserve(freelist.size() + howMuch); // PAR@@@@ maybe we should - not much more RAM
}
void Add(T *t) {
AllocForOneMore();
// Look to see if we already have something with the same handle value.
ssassert(FindByIdNoOops(t->h) == nullptr, "Handle isn't unique");
// 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;
}
if (it->h.v == h.v) {
return it;
return &elemstore[*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; }
@ -545,22 +563,23 @@ public:
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();
@ -568,28 +587,35 @@ public:
void MoveSelfInto(IdList<T,H> *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<T,H> *l) {
l->Clear();
l->elem = (T *)::operator new[](elemsAllocated * sizeof(elem[0]));
for(int i = 0; i < n; i++)
new(&l->elem[i]) T(elem[i]);
l->elemsAllocated = elemsAllocated;
for(auto const &it : elemstore) {
l->elemstore.push_back(it);
}
for(auto const &it : elemidx) {
l->elemidx.push_back(it);
}
l->n = n;
}
void Clear() {
for(int i = 0; i < n; i++) {
elem[i].Clear();
elem[i].~T();
for(auto &it : elemidx) {
elemstore[it].Clear();
// elemstore[it].~T(); // clear below calls the destructors
}
if(elem) ::operator delete[](elem);
elem = NULL;
elemsAllocated = n = 0;
freelist.clear();
elemidx.clear();
elemstore.clear();
n = 0;
}
};

View File

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

View File

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

View File

@ -170,22 +170,21 @@ public:
}
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 @@ public:
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 @@ public:
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 @@ public:
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 @@ public:
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;
}
@ -1309,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());
}
}

View File

@ -354,19 +354,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 +373,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));
}
@ -713,6 +711,8 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const Platform::Path &filename, EntityLi
{
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 {
return LoadEntitiesFromSlvs(filename, le, m, sh);
}
@ -909,6 +909,8 @@ try_again:
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.

View File

@ -224,9 +224,11 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox)
if(PruneGroups(hg))
goto pruned;
int groupRequestIndex = 0;
for(auto &req : SK.request) {
Request *r = &req;
if(r->group != hg) continue;
r->groupRequestIndex = groupRequestIndex++;
r->Generate(&(SK.entity), &(SK.param));
}

View File

@ -94,10 +94,12 @@ const MenuEntry Menu[] = {
{ 1, N_("Show Snap &Grid"), Command::SHOW_GRID, '>', KC, mView },
{ 1, N_("Darken Inactive Solids"), Command::DIM_SOLID_MODEL, 0, KC, mView },
{ 1, N_("Use &Perspective Projection"), Command::PERSPECTIVE_PROJ, '`', KC, mView },
{ 1, N_("Show E&xploded View"), Command::EXPLODE_SKETCH, '\\', KC, mView },
{ 1, N_("Dimension &Units"), Command::NONE, 0, KN, NULL },
{ 2, N_("Dimensions in &Millimeters"), Command::UNITS_MM, 0, KR, mView },
{ 2, N_("Dimensions in M&eters"), Command::UNITS_METERS, 0, KR, mView },
{ 2, N_("Dimensions in &Inches"), Command::UNITS_INCHES, 0, KR, mView },
{ 2, N_("Dimensions in &Feet and Inches"), Command::UNITS_FEET_INCHES, 0, KR, mView },
{ 1, NULL, Command::NONE, 0, KN, NULL },
{ 1, N_("Show &Toolbar"), Command::SHOW_TOOLBAR, 0, KC, mView },
{ 1, N_("Show Property Bro&wser"), Command::SHOW_TEXT_WND, '\t', KC, mView },
@ -153,8 +155,8 @@ const MenuEntry Menu[] = {
{ 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_("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 },
@ -317,6 +319,8 @@ void GraphicsWindow::PopulateMainMenu() {
dimSolidModelMenuItem = menuItem;
} else if(Menu[i].cmd == Command::PERSPECTIVE_PROJ) {
perspectiveProjMenuItem = menuItem;
} else if(Menu[i].cmd == Command::EXPLODE_SKETCH) {
explodeMenuItem = menuItem;
} else if(Menu[i].cmd == Command::SHOW_TOOLBAR) {
showToolbarMenuItem = menuItem;
} else if(Menu[i].cmd == Command::SHOW_TEXT_WND) {
@ -329,6 +333,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) {
@ -369,8 +375,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) {
@ -404,6 +413,8 @@ void GraphicsWindow::Init() {
showEdges = true;
showMesh = false;
showOutlines = false;
showFacesDrawing = false;
showFacesNonDrawing = true;
drawOccludedAs = DrawOccludedAs::INVISIBLE;
showTextWindow = true;
@ -419,7 +430,7 @@ 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->onRender = std::bind(&GraphicsWindow::Paint, this);
window->onKeyboardEvent = std::bind(&GraphicsWindow::KeyboardEvent, this, _1);
@ -745,6 +756,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();
@ -838,6 +855,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();
@ -920,6 +943,7 @@ void GraphicsWindow::EnsureValidActives() {
case Unit::MM:
case Unit::METERS:
case Unit::INCHES:
case Unit::FEET_INCHES:
break;
default:
SS.viewUnits = Unit::MM;
@ -928,6 +952,7 @@ void GraphicsWindow::EnsureValidActives() {
unitsMmMenuItem->SetActive(SS.viewUnits == Unit::MM);
unitsMetersMenuItem->SetActive(SS.viewUnits == Unit::METERS);
unitsInchesMenuItem->SetActive(SS.viewUnits == Unit::INCHES);
unitsFeetInchesMenuItem->SetActive(SS.viewUnits == Unit::FEET_INCHES);
if(SS.TW.window) SS.TW.window->SetVisible(SS.GW.showTextWindow);
showTextWndMenuItem->SetActive(SS.GW.showTextWindow);
@ -935,6 +960,7 @@ void GraphicsWindow::EnsureValidActives() {
showGridMenuItem->SetActive(SS.GW.showSnapGrid);
dimSolidModelMenuItem->SetActive(SS.GW.dimSolidModel);
perspectiveProjMenuItem->SetActive(SS.usePerspectiveProj);
explodeMenuItem->SetActive(SS.explode);
showToolbarMenuItem->SetActive(SS.showToolbar);
fullScreenMenuItem->SetActive(SS.GW.window->IsFullScreen());
@ -965,20 +991,19 @@ void GraphicsWindow::ForceTextWindowShown() {
}
void GraphicsWindow::DeleteTaggedRequests() {
Request *r;
// Delete any requests that were affected by this deletion.
for(r = SK.request.First(); r; r = SK.request.NextAfter(r)) {
if(r->workplane == Entity::FREE_IN_3D) continue;
if(!r->workplane.isFromRequest()) continue;
Request *wrkpl = SK.GetRequest(r->workplane.request());
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;
r.tag = 1;
}
// Rewrite any point-coincident constraints that were affected by this
// deletion.
for(r = SK.request.First(); r; r = SK.request.NextAfter(r)) {
if(!r->tag) continue;
FixConstraintsForRequestBeingDeleted(r->h);
for(Request &r : SK.request) {
if(!r.tag) continue;
FixConstraintsForRequestBeingDeleted(r.h);
}
// and then delete the tagged requests.
SK.request.RemoveTagged();
@ -1042,9 +1067,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;
@ -1054,13 +1078,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();
@ -1068,24 +1091,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<Selection> *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;
}
@ -1102,7 +1124,7 @@ void GraphicsWindow::MenuEdit(Command id) {
}
}
if(onChain && !alreadySelected) {
SS.GW.MakeSelected(e->h);
SS.GW.MakeSelected(e.h);
newlySelected++;
didSomething = true;
}
@ -1367,6 +1389,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();
}

View File

@ -138,12 +138,24 @@ void Group::MenuGroup(Command id, Platform::Path linkFile) {
g.predef.q = wrkplg->predef.q;
} 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.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"
" * a point and a normal (through the point, "
"orthogonal to the normal)\n"
/*" * a point and a face (through the point, "
"parallel to the face)\n"*/
" * a workplane (copy of the workplane)\n"));
return;
}
@ -392,7 +404,11 @@ bool Group::IsForcedToMeshBySource() const {
}
bool Group::IsForcedToMesh() const {
return forceToMesh || IsForcedToMeshBySource();
return forceToMesh || IsTriangleMeshAssembly() || IsForcedToMeshBySource();
}
bool Group::IsTriangleMeshAssembly() const {
return type == Type::LINKED && linkFile.Extension() == "stl";
}
std::string Group::DescriptionString() {
@ -404,11 +420,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();
@ -440,7 +455,7 @@ void Group::Generate(IdList<Entity,hEntity> *entity,
if(predef.negateU) u = u.ScaledBy(-1);
if(predef.negateV) v = v.ScaledBy(-1);
q = Quaternion::From(u, v);
} else if(subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) {
} else if(subtype == Subtype::WORKPLANE_BY_POINT_ORTHO || subtype == Subtype::WORKPLANE_BY_POINT_NORMAL /*|| subtype == Subtype::WORKPLANE_BY_POINT_FACE*/) {
// Already given, numerically.
q = predef.q;
} else ssassert(false, "Unexpected workplane subtype");
@ -448,6 +463,7 @@ void Group::Generate(IdList<Entity,hEntity> *entity,
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);
@ -800,6 +816,12 @@ void Group::GenerateEquations(IdList<Equation,hEquation> *l) {
AddEq(l, (EC(axis.z))->Minus(EP(6)), 5);
#undef EC
#undef EP
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) {
if(predef.entityB != Entity::FREE_IN_3D) {
// The extrusion path is locked along a line, normal to the
@ -1172,3 +1194,6 @@ void Group::CopyEntity(IdList<Entity,hEntity> *el,
el->Add(&en);
}
bool Group::ShouldDrawExploded() const {
return SS.explode && h == SS.GW.activeGroup && type == Type::DRAWING_WORKPLANE && !SS.exportMode;
}

View File

@ -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;
}
}
@ -292,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.
@ -307,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;
}

View File

@ -54,7 +54,7 @@ static std::vector <std::string> splitString(const std::string line) {
}
//////////////////////////////////////////////////////////////////////////////
// Functions for linking an IDF file - we need to create entites that
// Functions for linking an IDF file - we need to create entities that
// get remapped into a linked group similar to linking .slvs files
//////////////////////////////////////////////////////////////////////////////
@ -77,7 +77,7 @@ static hEntity newPoint(EntityList *el, int *id, Vector p, bool visible = true)
return en.h;
}
static hEntity newLine(EntityList *el, int *id, hEntity p0, hEntity p1) {
static hEntity newLine(EntityList *el, int *id, hEntity p0, hEntity p1, bool keepout) {
Entity en = {};
en.type = Entity::Type::LINE_SEGMENT;
en.point[0] = p0;
@ -85,8 +85,8 @@ static hEntity newLine(EntityList *el, int *id, hEntity p0, hEntity p1) {
en.extraPoints = 0;
en.timesApplied = 0;
en.group.v = 493;
en.construction = false;
en.style.v = Style::ACTIVE_GRP;
en.construction = keepout;
en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP;
en.actVisible = true;
en.forceHidden = false;
@ -117,7 +117,7 @@ static hEntity newNormal(EntityList *el, int *id, Quaternion normal) {
return en.h;
}
static hEntity newArc(EntityList *el, int *id, hEntity p0, hEntity p1, hEntity pc, hEntity hnorm) {
static hEntity newArc(EntityList *el, int *id, hEntity p0, hEntity p1, hEntity pc, hEntity hnorm, bool keepout) {
Entity en = {};
en.type = Entity::Type::ARC_OF_CIRCLE;
en.point[0] = pc;
@ -127,8 +127,8 @@ static hEntity newArc(EntityList *el, int *id, hEntity p0, hEntity p1, hEntity p
en.extraPoints = 0;
en.timesApplied = 0;
en.group.v = 403;
en.construction = false;
en.style.v = Style::ACTIVE_GRP;
en.construction = keepout;
en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP;
en.actVisible = true;
en.forceHidden = false; *id = *id+1;
@ -158,7 +158,7 @@ static hEntity newDistance(EntityList *el, int *id, double distance) {
return en.h;
}
static hEntity newCircle(EntityList *el, int *id, hEntity p0, hEntity hdist, hEntity hnorm) {
static hEntity newCircle(EntityList *el, int *id, hEntity p0, hEntity hdist, hEntity hnorm, bool keepout) {
Entity en = {};
en.type = Entity::Type::CIRCLE;
en.point[0] = p0;
@ -167,8 +167,8 @@ static hEntity newCircle(EntityList *el, int *id, hEntity p0, hEntity hdist, hEn
en.extraPoints = 0;
en.timesApplied = 0;
en.group.v = 399;
en.construction = false;
en.style.v = Style::ACTIVE_GRP;
en.construction = keepout;
en.style.v = keepout? Style::CONSTRUCTION : Style::ACTIVE_GRP;
en.actVisible = true;
en.forceHidden = false;
@ -196,18 +196,18 @@ static Vector ArcCenter(Vector p0, Vector p1, double angle) {
// Positive angles are counter clockwise, negative are clockwise. An angle of 360
// indicates a circle centered at x1,y1 passing through x2,y2 and is a complete loop.
static void CreateEntity(EntityList *el, int *id, hEntity h0, hEntity h1, hEntity hnorm,
Vector p0, Vector p1, double angle) {
Vector p0, Vector p1, double angle, bool keepout) {
if (angle == 0.0) {
//line
if(p0.Equals(p1)) return;
newLine(el, id, h0, h1);
newLine(el, id, h0, h1, keepout);
} else if(angle == 360.0) {
// circle
double d = p1.Minus(p0).Magnitude();
hEntity hd = newDistance(el, id, d);
newCircle(el, id, h1, hd, hnorm);
newCircle(el, id, h1, hd, hnorm, keepout);
} else {
// arc
@ -226,7 +226,7 @@ static void CreateEntity(EntityList *el, int *id, hEntity h0, hEntity h1, hEntit
}
Vector c = m.Minus(perp.ScaledBy(dist));
hEntity hc = newPoint(el, id, c, /*visible=*/false);
newArc(el, id, h0, h1, hc, hnorm);
newArc(el, id, h0, h1, hc, hnorm, keepout);
}
}
@ -291,9 +291,9 @@ static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vecto
namespace SolveSpace {
// Here we read the important section of an IDF file. SolveSpace Entities are directly created by
// the funcions above, which is only OK because of the way linking works. For example points do
// the functions above, which is only OK because of the way linking works. For example points do
// not have handles for solver parameters (coordinates), they only have their actPoint values
// set (or actNormal or actDistance). These are incompete entites and would be a problem if
// set (or actNormal or actDistance). These are incomplete entities and would be a problem if
// they were part of the sketch, but they are not. After making a list of them here, a new group
// gets created from copies of these. Those copies are complete and part of the sketch group.
bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *sh) {
@ -355,10 +355,9 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
} else if (line.find(".BOARD_OUTLINE") == 0) {
section = board_outline;
record_number = 1;
// no keepouts for now - they should also be shown as construction?
// } else if (line.find(".ROUTE_KEEPOUT") == 0) {
// section = routing_keepout;
// record_number = 1;
} else if (line.find(".ROUTE_KEEPOUT") == 0) {
section = routing_keepout;
record_number = 1;
} else if(line.find(".DRILLED_HOLES") == 0) {
section = drilled_holes;
record_number = 1;
@ -433,13 +432,15 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
bool vis = (ang == 360.0);
if (bottomEntities) {
hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis);
CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang);
CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang,
(section == routing_keepout) );
pprev = point;
hprev = hp;
}
if (topEntities) {
hEntity hp = newPoint(el, &entityCount, pTop, /*visible=*/vis);
CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop, ang);
CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop,
ang, (section == routing_keepout) );
pprevTop = pTop;
hprevTop = hp;
}
@ -467,12 +468,12 @@ bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *s
Vector cent = Vector::From(x,y,0.0);
hEntity hcent = newPoint(el, &entityCount, cent);
hEntity hdist = newDistance(el, &entityCount, d/2);
newCircle(el, &entityCount, hcent, hdist, hnorm);
newCircle(el, &entityCount, hcent, hdist, hnorm, false);
// and again for the top
Vector cTop = Vector::From(x,y,board_thickness);
hcent = newPoint(el, &entityCount, cTop);
hdist = newDistance(el, &entityCount, d/2);
newCircle(el, &entityCount, hcent, hdist, hnorm);
newCircle(el, &entityCount, hcent, hdist, hnorm, false);
// create the curves for the extrusion
Vector pt = Vector::From(x+d/2, y, 0.0);
MakeBeziersForArcs(&sbl, cent, pt, pt, normal, 360.0);

221
src/importmesh.cpp Normal file
View File

@ -0,0 +1,221 @@
//-----------------------------------------------------------------------------
// 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 <vector>
#define MIN_POINT_DISTANCE 0.001
// we will check for duplicate verticies and keep all their normals
class vertex {
public:
Vector p;
std::vector<Vector> normal;
};
static bool isEdgeVertex(vertex &v) {
unsigned int i,j;
bool result = false;
for(i=0;i<v.normal.size();i++) {
for(j=i;j<v.normal.size();j++) {
if(v.normal[i].Dot(v.normal[j]) < 0.9) {
result = true;
}
}
}
return result;
}
// This function has poor performance, used inside a loop it is O(n**2)
static void addUnique(std::vector<vertex> &lv, Vector &p, Vector &n) {
unsigned int i;
for(i=0; i<lv.size(); i++) {
if(lv[i].p.Equals(p, MIN_POINT_DISTANCE)) {
break;
}
}
if(i==lv.size()) {
vertex v;
v.p = p;
lv.push_back(v);
}
// we could improve a little by only storing unique normals
lv[i].normal.push_back(n);
};
// Make a new point - type doesn't matter since we will make a copy later
static hEntity newPoint(EntityList *el, int id, Vector p) {
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 = true;
en.forceHidden = false;
en.h.v = id + en.group.v*65536;
el->Add(&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+2;
newPoint(el, id, v);
}
}
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;
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);
uint32_t n;
uint32_t color;
f.read((char*)&n, 4);
dbp("%d triangles", n);
float x,y,z;
float xn,yn,zn;
//add the STL origin as an entity
addVertex(el, Vector::From(0.0, 0.0, 0.0));
std::vector<vertex> verts = {};
for(uint32_t i = 0; i<n; i++) {
STriangle tr = {};
// read the triangle normal
f.read((char*)&xn, 4);
f.read((char*)&yn, 4);
f.read((char*)&zn, 4);
tr.an = Vector::From(xn,yn,zn);
tr.bn = tr.an;
tr.cn = tr.an;
f.read((char*)&x, 4);
f.read((char*)&y, 4);
f.read((char*)&z, 4);
tr.a.x = x;
tr.a.y = y;
tr.a.z = z;
f.read((char*)&x, 4);
f.read((char*)&y, 4);
f.read((char*)&z, 4);
tr.b.x = x;
tr.b.y = y;
tr.b.z = z;
f.read((char*)&x, 4);
f.read((char*)&y, 4);
f.read((char*)&z, 4);
tr.c.x = x;
tr.c.y = y;
tr.c.z = z;
f.read((char*)&color,2);
if(color & 0x8000) {
tr.meta.color.red = (color >> 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 verticies", verts.size());
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<verts.size(); i++) {
box.Include(verts[i].p);
}
hEntity p[8];
int id = el->n+2;
p[0] = newPoint(el, id++, Vector::From(box.minp.x, box.minp.y, box.minp.z));
p[1] = newPoint(el, id++, Vector::From(box.maxp.x, box.minp.y, box.minp.z));
p[2] = newPoint(el, id++, Vector::From(box.minp.x, box.maxp.y, box.minp.z));
p[3] = newPoint(el, id++, Vector::From(box.maxp.x, box.maxp.y, box.minp.z));
p[4] = newPoint(el, id++, Vector::From(box.minp.x, box.minp.y, box.maxp.z));
p[5] = newPoint(el, id++, Vector::From(box.maxp.x, box.minp.y, box.maxp.z));
p[6] = newPoint(el, id++, Vector::From(box.minp.x, box.maxp.y, box.maxp.z));
p[7] = newPoint(el, id++, Vector::From(box.maxp.x, box.maxp.y, box.maxp.z));
newLine(el, id++, p[0], p[1]);
newLine(el, id++, p[0], p[2]);
newLine(el, id++, p[3], p[1]);
newLine(el, id++, p[3], p[2]);
newLine(el, id++, p[4], p[5]);
newLine(el, id++, p[4], p[6]);
newLine(el, id++, p[7], p[5]);
newLine(el, id++, p[7], p[6]);
newLine(el, id++, p[0], p[4]);
newLine(el, id++, p[1], p[5]);
newLine(el, id++, p[2], p[6]);
newLine(el, id++, p[3], p[7]);
for(unsigned int i=0; i<verts.size(); i++) {
// create point entities for edge vertexes
if(isEdgeVertex(verts[i])) {
addVertex(el, verts[i].p);
}
}
return true;
}
}

View File

@ -131,11 +131,15 @@ 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_ARC_ARC_LEN_RATIO: t = Constraint::Type::ARC_ARC_LEN_RATIO; break;
case SLVS_C_ARC_LINE_LEN_RATIO: t = Constraint::Type::ARC_LINE_LEN_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_ARC_ARC_DIFFERENCE: t = Constraint::Type::ARC_ARC_DIFFERENCE; break;
case SLVS_C_ARC_LINE_DIFFERENCE:t = Constraint::Type::ARC_LINE_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;

View File

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

View File

@ -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;
@ -188,32 +193,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(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);
MakeSelected(dragEntity);
}
if(e->type == Entity::Type::CIRCLE && selection.n <= 1) {
// Drag the radius.
pending.circle = dragEntity;
pending.operation = Pending::DRAGGING_RADIUS;
} else if(e->IsNormal()) {
pending.normal = dragEntity;
pending.operation = Pending::DRAGGING_NORMAL;
} else {
StartDraggingBySelection();
if(!hoverWasSelectedOnMousedown) {
// And then clear the selection again, since they
// probably didn't want that selected if they just
// were dragging it.
ClearSelection();
}
hover.Clear();
pending.operation = Pending::DRAGGING_POINTS;
}
@ -425,6 +421,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
SK.GetEntity(circle->distance)->DistanceForceTo(r);
SS.MarkGroupDirtyByEntity(pending.circle);
SS.ScheduleShowTW();
break;
}
@ -526,11 +523,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;
}
@ -714,11 +716,12 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
if(gs.points == 1) {
Entity *p = SK.GetEntity(gs.point[0]);
Constraint *c;
Constraint *c = nullptr;
IdList<Constraint,hConstraint> *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;
}
}
@ -728,11 +731,10 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
SS.UndoRemember();
SK.constraint.ClearTags();
Constraint *c;
for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) {
if(c->type != Constraint::Type::POINTS_COINCIDENT) continue;
if(c->ptA == p->h || c->ptB == p->h) {
c->tag = 1;
for(Constraint &c : SK.constraint) {
if(c.type != Constraint::Type::POINTS_COINCIDENT) continue;
if(c.ptA == p->h || c.ptB == p->h) {
c.tag = 1;
}
}
SK.constraint.RemoveTagged();
@ -1306,15 +1308,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;
@ -1369,12 +1376,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
@ -1430,7 +1437,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.
@ -1444,6 +1453,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());

View File

@ -86,8 +86,10 @@ std::vector<FileFilter> SolveSpaceModelFileFilters = {
};
std::vector<FileFilter> 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<FileFilter> RasterFileFilters = {

View File

@ -33,7 +33,18 @@
#if defined(HAVE_SPACEWARE)
# include <spnav.h>
# include <gdk/gdk.h>
# if defined(GDK_WINDOWING_X11)
# include <gdk/gdkx.h>
# endif
# if defined(GDK_WINDOWING_WAYLAND)
# include <gdk/gdkwayland.h>
# endif
# if GTK_CHECK_VERSION(3, 20, 0)
# include <gdkmm/seat.h>
# else
# include <gdkmm/devicemanager.h>
# endif
#endif
#include "solvespace.h"
@ -217,6 +228,10 @@ public:
}
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);
}
};
@ -1038,16 +1053,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 = {};
@ -1058,8 +1065,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);
}
@ -1075,17 +1082,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<Gdk::Window> 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<Gdk::Device> device = gdkWindow->get_display()->get_default_seat()->get_pointer();
#else
Glib::RefPtr<Gdk::Device> 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;
spnav_event spnavEvent;
while(spnav_poll_event(&spnavEvent)) {
ProcessSpnavEvent(window, spnavEvent, shiftDown, controlDown);
}
return TRUE;
}
void Request3DConnexionEventsForWindow(WindowRef window) {
@ -1093,11 +1135,26 @@ void Request3DConnexionEventsForWindow(WindowRef window) {
std::static_pointer_cast<WindowImplGtk>(window);
Glib::RefPtr<Gdk::Window> gdkWindow = windowImpl->gtkWindow.get_window();
#if defined(GDK_WINDOWING_X11)
if(GDK_IS_X11_DISPLAY(gdkWindow->get_display()->gobj())) {
if(spnav_x11_open(gdk_x11_get_default_xdisplay(),
gdk_x11_window_get_xid(gdkWindow->gobj())) != -1) {
gdkWindow->add_filter(GdkSpnavFilter, windowImpl.get());
spnav_x11_open(gdk_x11_get_default_xdisplay(),
gdk_x11_window_get_xid(gdkWindow->gobj()));
} 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) {}
@ -1303,7 +1360,7 @@ public:
return;
Platform::Path path = GetFilename();
if(gtkChooser->get_action() != GTK_FILE_CHOOSER_ACTION_OPEN) {
if(gtkChooser->get_action() != Gtk::FILE_CHOOSER_ACTION_OPEN) {
SetCurrentName(path.WithExtension(extension).FileName());
}
}

View File

@ -286,7 +286,8 @@ public:
}
void PopUp() override {
[NSMenu popUpContextMenu:nsMenu withEvent:[NSApp currentEvent] forView:nil];
NSEvent* event = [NSApp currentEvent];
[NSMenu popUpContextMenu:nsMenu withEvent:event forView:event.window.contentView];
}
void Clear() override {
@ -358,18 +359,25 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
- (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;
Platform::Window::Kind kind;
}
@synthesize acceptsFirstResponder;
- (id)initWithFrame:(NSRect)frameRect {
- (id)initWithKind:(Platform::Window::Kind)aKind {
NSOpenGLPixelFormatAttribute attrs[] = {
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAColorSize, 24,
@ -377,7 +385,7 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
0
};
NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
if(self = [super initWithFrame:frameRect pixelFormat:pixelFormat]) {
if(self = [super initWithFrame:NSMakeRect(0, 0, 0, 0) pixelFormat:pixelFormat]) {
self.wantsBestResolutionOpenGLSurface = YES;
self.wantsLayer = YES;
editor = [[NSTextField alloc] init];
@ -387,6 +395,18 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
editor.bezeled = NO;
editor.target = self;
editor.action = @selector(didEdit:);
inTrackpadScrollGesture = false;
kind = aKind;
if(kind == Platform::Window::Kind::TOPLEVEL) {
NSGestureRecognizer *mag = [[NSMagnificationGestureRecognizer alloc] initWithTarget:self
action:@selector(magnifyGesture:)];
[self addGestureRecognizer:mag];
NSRotationGestureRecognizer* rot = [[NSRotationGestureRecognizer alloc] initWithTarget:self
action:@selector(rotateGesture:)];
[self addGestureRecognizer:rot];
}
}
return self;
}
@ -427,9 +447,9 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
- (Platform::MouseEvent)convertMouseEvent:(NSEvent *)nsEvent {
Platform::MouseEvent event = {};
NSPoint nsPoint = [self convertPoint:nsEvent.locationInWindow fromView:self];
NSPoint nsPoint = [self convertPoint:nsEvent.locationInWindow fromView:nil];
event.x = nsPoint.x;
event.y = self.bounds.size.height - nsPoint.y;
event.y = nsPoint.y;
NSUInteger nsFlags = [nsEvent modifierFlags];
if(nsFlags & NSEventModifierFlagShift) event.shiftDown = true;
@ -553,14 +573,57 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
using Platform::MouseEvent;
MouseEvent event = [self convertMouseEvent:nsEvent];
if(nsEvent.subtype == NSEventSubtypeTabletPoint && kind == Platform::Window::Kind::TOPLEVEL) {
// This is how Cocoa represents 2 finger trackpad drag gestures, rather than going via
// NSPanGestureRecognizer which is how you might expect this to work... We complicate this
// further by also handling shift-two-finger-drag to mean rotate. Fortunately we're using
// shift in the same way as right-mouse-button MouseEvent does (to converts a pan to a
// rotate) so we get the rotate support for free. It's a bit ugly having to fake mouse
// events and track the deviation from the actual mouse cursor with trackpadPositionShift,
// but in lieu of an event API that allows us to request a rotate/pan with relative
// coordinates, it's the best we can do.
event.button = MouseEvent::Button::RIGHT;
// Make sure control (actually cmd) isn't passed through, ctrl-right-click-drag has special
// meaning as rotate which we don't want to inadvertently trigger.
event.controlDown = false;
if(nsEvent.scrollingDeltaX == 0 && nsEvent.scrollingDeltaY == 0) {
// Cocoa represents the point where the user lifts their fingers off (and any inertial
// scrolling has finished) by an event with scrollingDeltaX and scrollingDeltaY both 0.
// Sometimes you also get a zero scroll at the start of a two-finger-rotate (probably
// reflecting the internal implementation of that being a cancelled possible pan
// gesture), which is why this conditional is structured the way it is.
if(inTrackpadScrollGesture) {
event.x += trackpadPositionShift.x;
event.y += trackpadPositionShift.y;
event.type = MouseEvent::Type::RELEASE;
receiver->onMouseEvent(event);
inTrackpadScrollGesture = false;
trackpadPositionShift = Point2d::From(0, 0);
}
return;
} else if(!inTrackpadScrollGesture) {
inTrackpadScrollGesture = true;
trackpadPositionShift = Point2d::From(0, 0);
event.type = MouseEvent::Type::PRESS;
receiver->onMouseEvent(event);
// And drop through
}
trackpadPositionShift.x += nsEvent.scrollingDeltaX;
trackpadPositionShift.y += nsEvent.scrollingDeltaY;
event.type = MouseEvent::Type::MOTION;
event.x += trackpadPositionShift.x;
event.y += trackpadPositionShift.y;
receiver->onMouseEvent(event);
return;
}
event.type = MouseEvent::Type::SCROLL_VERT;
bool isPrecise = [nsEvent hasPreciseScrollingDeltas];
event.scrollDelta = [nsEvent scrollingDeltaY] / (isPrecise ? 50 : 5);
if(receiver->onMouseEvent) {
receiver->onMouseEvent(event);
}
}
- (void)mouseExited:(NSEvent *)nsEvent {
@ -638,6 +701,50 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
[super keyUp:nsEvent];
}
- (void)magnifyGesture:(NSMagnificationGestureRecognizer *)gesture {
// The onSixDofEvent API doesn't allow us to specify the scaling's origin, so for expediency
// we fake out a scrollwheel MouseEvent with a suitably-scaled scrollDelta with a bit of
// absolute-to-relative positioning conversion tracked using magnificationGestureCurrentZ.
if(gesture.state == NSGestureRecognizerStateBegan) {
magnificationGestureCurrentZ = 0.0;
}
// Magic number to make gesture.magnification align roughly with what scrollDelta expects
constexpr double kScale = 10.0;
double z = ((double)gesture.magnification * kScale);
double zdelta = z - magnificationGestureCurrentZ;
magnificationGestureCurrentZ = z;
using Platform::MouseEvent;
MouseEvent event = {};
event.type = MouseEvent::Type::SCROLL_VERT;
NSPoint nsPoint = [gesture locationInView:self];
event.x = nsPoint.x;
event.y = nsPoint.y;
event.scrollDelta = zdelta;
if(receiver->onMouseEvent) {
receiver->onMouseEvent(event);
}
}
- (void)rotateGesture:(NSRotationGestureRecognizer *)gesture {
if(gesture.state == NSGestureRecognizerStateBegan) {
rotationGestureCurrent = 0.0;
}
double rotation = gesture.rotation;
double rotationDelta = rotation - rotationGestureCurrent;
rotationGestureCurrent = rotation;
using Platform::SixDofEvent;
SixDofEvent event = {};
event.type = SixDofEvent::Type::MOTION;
event.rotationZ = rotationDelta;
if(receiver->onSixDofEvent) {
receiver->onSixDofEvent(event);
}
}
@synthesize editing;
- (void)startEditing:(NSString *)text at:(NSPoint)origin withHeight:(double)fontHeight
@ -698,11 +805,27 @@ MenuBarRef GetOrCreateMainMenu(bool *unique) {
}
@synthesize scrollerMin;
@synthesize scrollerMax;
@synthesize scrollerSize;
@synthesize pageSize;
- (void)didScroll:(NSScroller *)sender {
double pos;
switch(sender.hitPart) {
case NSScrollerKnob:
case NSScrollerKnobSlot:
pos = receiver->GetScrollbarPosition();
break;
case NSScrollerDecrementPage:
pos = receiver->GetScrollbarPosition() - pageSize;
break;
case NSScrollerIncrementPage:
pos = receiver->GetScrollbarPosition() + pageSize;
break;
default:
return;
}
if(receiver->onScrollbarAdjusted) {
double pos = scrollerMin + [sender doubleValue] * (scrollerMax - scrollerMin);
receiver->onScrollbarAdjusted(pos);
}
}
@ -769,7 +892,7 @@ public:
NSString *nsToolTip;
WindowImplCocoa(Window::Kind kind, std::shared_ptr<WindowImplCocoa> parentWindow) {
ssView = [[SSView alloc] init];
ssView = [[SSView alloc] initWithKind:kind];
ssView.translatesAutoresizingMaskIntoConstraints = NO;
ssView.receiver = this;
@ -962,21 +1085,22 @@ public:
void ConfigureScrollbar(double min, double max, double pageSize) override {
ssView.scrollerMin = min;
ssView.scrollerMax = max - pageSize;
[nsScroller setKnobProportion:(pageSize / (ssView.scrollerMax - ssView.scrollerMin))];
ssView.scrollerSize = max + 1 - min;
ssView.pageSize = pageSize;
nsScroller.knobProportion = pageSize / ssView.scrollerSize;
nsScroller.hidden = pageSize >= ssView.scrollerSize;
}
double GetScrollbarPosition() override {
// Platform::Window scrollbar positions are in the range [min, max+1 - pageSize] inclusive,
// and Cocoa scrollbars are from 0.0 to 1.0 inclusive, so we have to apply some scaling and
// transforming. (scrollerSize is max+1-min, see ConfigureScrollbar above)
return ssView.scrollerMin +
[nsScroller doubleValue] * (ssView.scrollerMax - ssView.scrollerMin);
nsScroller.doubleValue * (ssView.scrollerSize - ssView.pageSize);
}
void SetScrollbarPosition(double pos) override {
if(pos > ssView.scrollerMax)
pos = ssView.scrollerMax;
if(GetScrollbarPosition() == pos)
return;
[nsScroller setDoubleValue:(pos / (ssView.scrollerMax - ssView.scrollerMin))];
nsScroller.doubleValue = (pos - ssView.scrollerMin) / ( ssView.scrollerSize - ssView.pageSize);
}
void Invalidate() override {
@ -1426,9 +1550,22 @@ void OpenInBrowser(const std::string &url) {
- (IBAction)preferences:(id)sender;
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
@property BOOL exiting;
@end
@implementation SSApplicationDelegate
@synthesize exiting;
- (id)init {
if (self = [super init]) {
self.exiting = false;
}
return self;
}
- (IBAction)preferences:(id)sender {
if (!SS.GW.showTextWindow) {
SolveSpace::SS.GW.MenuView(SolveSpace::Command::SHOW_TEXT_WND);
@ -1443,12 +1580,27 @@ void OpenInBrowser(const std::string &url) {
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
[[[NSApp mainWindow] delegate] windowShouldClose:[NSApp mainWindow]];
return NSTerminateCancel;
if(!SS.unsaved) {
return NSTerminateNow;
} else {
[self performSelectorOnMainThread:@selector(applicationTerminatePrompt) withObject:nil
waitUntilDone:NO modes:@[NSDefaultRunLoopMode, NSModalPanelRunLoopMode]];
return NSTerminateLater;
}
}
- (void)applicationWillTerminate:(NSNotification *)notification {
if(!exiting) {
// Prevent the Platform::ExitGui() call from SolveSpaceUI::Exit()
// triggering another terminate
exiting = true;
// Now let SS save settings etc
SS.Exit();
}
}
- (void)applicationTerminatePrompt {
SolveSpace::SS.MenuFile(SolveSpace::Command::EXIT);
[NSApp replyToApplicationShouldTerminate:SS.OkayToStartNewFile()];
}
@end
@ -1469,6 +1621,14 @@ std::vector<std::string> InitGui(int argc, char **argv) {
ssDelegate = [[SSApplicationDelegate alloc] init];
NSApplication.sharedApplication.delegate = ssDelegate;
// Setting this prevents "Show Tab Bar" and "Show All Tabs" items from being
// automagically added to the View menu
NSWindow.allowsAutomaticWindowTabbing = NO;
// And this prevents the duplicate "Enter Full Screen" menu item, see
// https://stackoverflow.com/questions/52154977/how-to-get-rid-of-enter-full-screen-menu-item
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"];
[NSBundle.mainBundle loadNibNamed:@"MainMenu" owner:nil topLevelObjects:nil];
NSArray *languages = NSLocale.preferredLanguages;
@ -1487,8 +1647,10 @@ void RunGui() {
}
void ExitGui() {
[NSApp setDelegate:nil];
if(!ssDelegate.exiting) {
ssDelegate.exiting = true;
[NSApp terminate:nil];
}
}
void ClearGui() {}

View File

@ -1229,7 +1229,7 @@ public:
sscheck(GetMonitorInfo(MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST), &mi));
// If it somehow ended up off-screen, then put it back.
// and make it visible by at least this portion of the scrren
// and make it visible by at least this portion of the screen
const LONG movein = 40;
RECT mrc = mi.rcMonitor;
@ -1583,11 +1583,6 @@ public:
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 {
@ -1640,13 +1635,14 @@ public:
}
bool RunModal() override {
if(isSaveDialog) {
SetTitle(C_("title", "Save File"));
if(GetFilename().IsEmpty()) {
SetFilename(Path::From(_("untitled")));
}
if(isSaveDialog) {
return GetSaveFileNameW(&ofn) == TRUE;
} else {
SetTitle(C_("title", "Open File"));
return GetOpenFileNameW(&ofn) == TRUE;
}
}

View File

@ -190,11 +190,11 @@ void SurfaceRenderer::DrawFaces(const SMesh &m, const std::vector<uint32_t> &fac
void SurfaceRenderer::DrawPixmap(std::shared_ptr<const Pixmap> 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<const Pixmap> pm) {
ssassert(false, "Not implemented");
dbp("Not implemented");
}
//-----------------------------------------------------------------------------

View File

@ -198,6 +198,9 @@ public:
// 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
@ -266,6 +269,7 @@ public:
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
@ -323,6 +327,7 @@ public:
void DrawPolyError(Canvas *canvas);
void DrawFilledPaths(Canvas *canvas);
void DrawContourAreaLabels(Canvas *canvas);
bool ShouldDrawExploded() const;
SPolygon GetPolygon();
@ -368,6 +373,7 @@ public:
std::string font;
Platform::Path file;
double aspectRatio;
int groupRequestIndex;
static hParam AddParam(ParamList *param, hParam hp);
void Generate(EntityList *entity, ParamList *param);
@ -591,6 +597,10 @@ public:
beziers.l.Clear();
edges.l.Clear();
}
bool ShouldDrawExploded() const;
Vector ExplodeOffset() const;
Vector PointGetDrawNum() const;
};
class EntReqTable {
@ -673,7 +683,10 @@ public:
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
};
@ -757,7 +770,7 @@ public:
Vector p0, Vector p1, Vector pt, double salient);
void DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
Vector a0, Vector da, Vector b0, Vector db,
Vector offset, Vector *ref, bool trim);
Vector offset, Vector *ref, bool trim, Vector explodeOffset);
void DoArrow(Canvas *canvas, Canvas::hStroke hcs,
Vector p, Vector dir, Vector n, double width, double angle, double da);
void DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs,
@ -779,6 +792,8 @@ public:
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);
@ -884,11 +899,14 @@ public:
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);
@ -918,7 +936,11 @@ public:
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;

View File

@ -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
@ -104,6 +105,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);
@ -315,6 +317,7 @@ void SolveSpaceUI::ScheduleAutosave() {
double SolveSpaceUI::MmPerUnit() {
switch(viewUnits) {
case Unit::INCHES: return 25.4;
case Unit::FEET_INCHES: return 25.4; // The 'unit' is still inches
case Unit::METERS: return 1000.0;
case Unit::MM: return 1.0;
}
@ -323,14 +326,47 @@ double SolveSpaceUI::MmPerUnit() {
const char *SolveSpaceUI::UnitName() {
switch(viewUnits) {
case Unit::INCHES: return "in";
case Unit::FEET_INCHES: return "in";
case Unit::METERS: return "m";
case Unit::MM: return "mm";
}
return "";
}
std::string SolveSpaceUI::MmToString(double v) {
std::string SolveSpaceUI::MmToString(double v, bool editable) {
v /= MmPerUnit();
// The syntax 2' 6" for feet and inches is not something we can (currently)
// parse back from a string so if editable is true, we treat FEET_INCHES the
// same as INCHES and just return the unadorned decimal number of inches.
if(viewUnits == Unit::FEET_INCHES && !editable) {
// Now convert v from inches to 64'ths of an inch, to make rounding easier.
v = floor((v + (1.0 / 128.0)) * 64.0);
int feet = (int)(v / (12.0 * 64.0));
v = v - (feet * 12.0 * 64.0);
// v is now the feet-less remainder in 1/64 inches
int inches = (int)(v / 64.0);
int numerator = (int)(v - ((double)inches * 64.0));
int denominator = 64;
// Divide down to smallest denominator where the numerator is still a whole number
while ((numerator != 0) && ((numerator & 1) == 0)) {
numerator /= 2;
denominator /= 2;
}
std::ostringstream str;
if(feet != 0) {
str << feet << "'-";
}
// For something like 0.5, show 1/2" rather than 0 1/2"
if(!(feet == 0 && inches == 0 && numerator != 0)) {
str << inches;
}
if(numerator != 0) {
str << " " << numerator << "/" << denominator;
}
str << "\"";
return str.str();
}
int digits = UnitDigitsAfterDecimal();
double minimum = 0.5 * pow(10,-digits);
while ((v < minimum) && (v > LENGTH_EPS)) {
@ -349,7 +385,7 @@ static const char *DimToString(int dim) {
}
static std::pair<int, std::string> SelectSIPrefixMm(int ord, int dim) {
// decide what units to use depending on the order of magnitude of the
// measure in meters and the dimmension (1,2,3 lenear, area, volume)
// measure in meters and the dimension (1,2,3 lenear, area, volume)
switch(dim) {
case 0:
case 1:
@ -394,17 +430,22 @@ std::string SolveSpaceUI::MmToStringSI(double v, int dim) {
dim = 1;
}
v /= pow((viewUnits == Unit::INCHES) ? 25.4 : 1000, dim);
bool inches = (viewUnits == Unit::INCHES) || (viewUnits == Unit::FEET_INCHES);
v /= pow(inches ? 25.4 : 1000, dim);
int vdeg = (int)(log10(fabs(v)));
std::string unit;
if(fabs(v) > 0.0) {
int sdeg = 0;
std::tie(sdeg, unit) =
(viewUnits == Unit::INCHES)
inches
? SelectSIPrefixInch(vdeg/dim)
: SelectSIPrefixMm(vdeg, dim);
v /= pow(10.0, sdeg * dim);
}
if(viewUnits == Unit::FEET_INCHES && fabs(v) > pow(12.0, dim)) {
unit = "ft";
v /= pow(12.0, dim);
}
int pdeg = (int)ceil(log10(fabs(v) + 1e-10));
return ssprintf("%.*g%s%s%s", pdeg + UnitDigitsAfterDecimal(), v,
compact ? "" : " ", unit.c_str(), DimToString(dim));
@ -434,10 +475,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;
@ -764,7 +806,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;
@ -1026,12 +1072,14 @@ void SolveSpaceUI::Clear() {
GW.showGridMenuItem = NULL;
GW.dimSolidModelMenuItem = NULL;
GW.perspectiveProjMenuItem = NULL;
GW.explodeMenuItem = NULL;
GW.showToolbarMenuItem = NULL;
GW.showTextWndMenuItem = NULL;
GW.fullScreenMenuItem = NULL;
GW.unitsMmMenuItem = NULL;
GW.unitsMetersMenuItem = NULL;
GW.unitsInchesMenuItem = NULL;
GW.unitsFeetInchesMenuItem = NULL;
GW.inWorkplaneMenuItem = NULL;
GW.in3dMenuItem = NULL;
GW.undoMenuItem = NULL;

View File

@ -138,7 +138,8 @@ enum class Command : uint32_t;
enum class Unit : uint32_t {
MM = 0,
INCHES,
METERS
METERS,
FEET_INCHES
};
template<class Key, class T>
@ -597,6 +598,7 @@ public:
} exportCanvas;
struct {
double depth;
double safeHeight;
int passes;
double feed;
double plungeFeed;
@ -608,8 +610,10 @@ public:
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);
@ -812,6 +816,7 @@ public:
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;

View File

@ -29,7 +29,7 @@ void SCurve::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const {
}
}
// We will be inserting other curve verticies into our curves to split them.
// We will be inserting other curve vertices into our curves to split them.
// This is helpful when curved surfaces become tangent along a trim and the
// usual tests for curve-surface intersection don't split the curve at a vertex.
// This is faster than the previous version that split at surface corners and
@ -521,20 +521,19 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent,
SEdgeList inter = {};
SSurface *ss;
SCurve *sc;
for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) {
if(sc->source != SCurve::Source::INTERSECTION) continue;
for(SCurve &sc : into->curve) {
if(sc.source != SCurve::Source::INTERSECTION) continue;
if(opA) {
if(sc->surfA != h) continue;
ss = shb->surface.FindById(sc->surfB);
if(sc.surfA != h) continue;
ss = shb->surface.FindById(sc.surfB);
} else {
if(sc->surfB != h) continue;
ss = sha->surface.FindById(sc->surfA);
if(sc.surfB != h) continue;
ss = sha->surface.FindById(sc.surfA);
}
int i;
for(i = 1; i < sc->pts.n; i++) {
Vector a = sc->pts[i-1].p,
b = sc->pts[i].p;
for(i = 1; i < sc.pts.n; i++) {
Vector a = sc.pts[i-1].p,
b = sc.pts[i].p;
Point2d auv, buv;
ss->ClosestPointTo(a, &(auv.x), &(auv.y));
@ -560,9 +559,9 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent,
bkwds = !bkwds;
}
if(bkwds) {
inter.AddEdge(tb, ta, sc->h.v, 1);
inter.AddEdge(tb, ta, sc.h.v, 1);
} else {
inter.AddEdge(ta, tb, sc->h.v, 0);
inter.AddEdge(ta, tb, sc.h.v, 0);
}
}
}
@ -711,20 +710,18 @@ void SShell::MakeIntersectionCurvesAgainst(SShell *agnst, SShell *into) {
for(int i = 0; i< surface.n; i++) {
SSurface *sa = &surface[i];
SSurface *sb;
for(sb = agnst->surface.First(); sb; sb = agnst->surface.NextAfter(sb)){
for(SSurface &sb : agnst->surface){
// Intersect every surface from our shell against every surface
// from agnst; this will add zero or more curves to the curve
// list for into.
sa->IntersectAgainst(sb, this, agnst, into);
sa->IntersectAgainst(&sb, this, agnst, into);
}
}
}
void SShell::CleanupAfterBoolean() {
SSurface *ss;
for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) {
ss->edges.Clear();
for(SSurface &ss : surface) {
ss.edges.Clear();
}
}
@ -734,10 +731,9 @@ void SShell::CleanupAfterBoolean() {
// by their new IDs.
//-----------------------------------------------------------------------------
void SShell::RewriteSurfaceHandlesForCurves(SShell *a, SShell *b) {
SCurve *sc;
for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) {
sc->surfA = sc->GetSurfaceA(a, b)->newH,
sc->surfB = sc->GetSurfaceB(a, b)->newH;
for(SCurve &sc : curve) {
sc.surfA = sc.GetSurfaceA(a, b)->newH,
sc.surfB = sc.GetSurfaceB(a, b)->newH;
}
}
@ -759,32 +755,32 @@ void SShell::MakeFromAssemblyOf(SShell *a, SShell *b) {
// First, copy over all the curves. Note which shell (a or b) each curve
// came from, but assign it a new ID.
curve.ReserveMore(a->curve.n + b->curve.n);
SCurve *c, cn;
SCurve cn;
for(i = 0; i < 2; i++) {
ab = (i == 0) ? a : b;
for(c = ab->curve.First(); c; c = ab->curve.NextAfter(c)) {
cn = SCurve::FromTransformationOf(c, t, q, 1.0);
for(SCurve &c : ab->curve) {
cn = SCurve::FromTransformationOf(&c, t, q, 1.0);
cn.source = (i == 0) ? SCurve::Source::A : SCurve::Source::B;
// surfA and surfB are wrong now, and we can't fix them until
// we've assigned IDs to the surfaces. So we'll get that later.
c->newH = curve.AddAndAssignId(&cn);
c.newH = curve.AddAndAssignId(&cn);
}
}
// Likewise copy over all the surfaces.
surface.ReserveMore(a->surface.n + b->surface.n);
SSurface *s, sn;
SSurface sn;
for(i = 0; i < 2; i++) {
ab = (i == 0) ? a : b;
for(s = ab->surface.First(); s; s = ab->surface.NextAfter(s)) {
sn = SSurface::FromTransformationOf(s, t, q, 1.0, /*includingTrims=*/true);
for(SSurface &s : ab->surface) {
sn = SSurface::FromTransformationOf(&s, t, q, 1.0, /*includingTrims=*/true);
// All the trim curve IDs get rewritten; we know the new handles
// to the curves since we recorded them in the previous step.
STrimBy *stb;
for(stb = sn.trim.First(); stb; stb = sn.trim.NextAfter(stb)) {
stb->curve = ab->curve.FindById(stb->curve)->newH;
}
s->newH = surface.AddAndAssignId(&sn);
s.newH = surface.AddAndAssignId(&sn);
}
}
@ -800,7 +796,7 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type) {
b->MakeClassifyingBsps(NULL);
// Copy over all the original curves, splitting them so that a
// piecwise linear segment never crosses a surface from the other
// piecewise linear segment never crosses a surface from the other
// shell.
a->CopyCurvesSplitAgainst(/*opA=*/true, b, this);
b->CopyCurvesSplitAgainst(/*opA=*/false, a, this);
@ -809,12 +805,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

View File

@ -817,7 +817,7 @@ void SCurve::RemoveShortSegments(SSurface *srfA, SSurface *srfB) {
continue;
}
// if the curve is exact and points are >0.05 appart wrt t, point is there
// if the curve is exact and points are >0.05 apart wrt t, point is there
// deliberately regardless of chord tolerance (ex: small circles)
tprev = t = tnext = 0;
if (isExact) {

View File

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

View File

@ -381,9 +381,8 @@ void SShell::AllPointsIntersecting(Vector a, Vector b,
List<SInter> *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);
}
}
@ -434,11 +433,10 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir,
// First, check for edge-on-edge
int edge_inters = 0;
Vector inter_surf_n[2], inter_edge_n[2];
SSurface *srf;
for(srf = surface.First(); srf; srf = surface.NextAfter(srf)) {
if(srf->LineEntirelyOutsideBbox(ea, eb, /*asSegment=*/true)) continue;
for(SSurface &srf : surface) {
if(srf.LineEntirelyOutsideBbox(ea, eb, /*asSegment=*/true)) continue;
SEdgeList *sel = &(srf->edges);
SEdgeList *sel = &(srf.edges);
SEdge *se;
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
if((ea.Equals(se->a) && eb.Equals(se->b)) ||
@ -448,9 +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.
@ -520,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)
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);

View File

@ -1038,35 +1038,31 @@ void SShell::MakeFromTransformationOf(SShell *a,
{
booleanFailed = false;
surface.ReserveMore(a->surface.n);
SSurface *s;
for(s = a->surface.First(); s; s = a->surface.NextAfter(s)) {
for(SSurface &s : a->surface) {
SSurface n;
n = SSurface::FromTransformationOf(s, t, q, scale, /*includingTrims=*/true);
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)) {
for(SCurve &c : a->curve) {
SCurve n;
n = SCurve::FromTransformationOf(c, t, q, scale);
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);
for(SSurface &s : surface) {
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);
for(SSurface &s : surface) {
if(s.CoincidentWithPlane(n, d)) {
s.MakeSectionEdgesInto(this, sel, sbl);
}
}
}
@ -1088,15 +1084,13 @@ bool SShell::IsEmpty() const {
}
void SShell::Clear() {
SSurface *s;
for(s = surface.First(); s; s = surface.NextAfter(s)) {
s->Clear();
for(SSurface &s : surface) {
s.Clear();
}
surface.Clear();
SCurve *c;
for(c = curve.First(); c; c = curve.NextAfter(c)) {
c->Clear();
for(SCurve &c : curve) {
c.Clear();
}
curve.Clear();
}

View File

@ -23,20 +23,20 @@ void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB,
// Now we have to piecewise linearize the curve. If there's already an
// identical curve in the shell, then follow that pwl exactly, otherwise
// calculate from scratch.
SCurve split, *existing = NULL, *se;
SCurve split, *existing = NULL;
SBezier sbrev = *sb;
sbrev.Reverse();
bool backwards = false;
#pragma omp critical(into)
{
for(se = into->curve.First(); se; se = into->curve.NextAfter(se)) {
if(se->isExact) {
if(sb->Equals(&(se->exact))) {
existing = se;
for(SCurve &se : into->curve) {
if(se.isExact) {
if(sb->Equals(&(se.exact))) {
existing = &se;
break;
}
if(sbrev.Equals(&(se->exact))) {
existing = se;
if(sbrev.Equals(&(se.exact))) {
existing = &se;
backwards = true;
break;
}
@ -332,15 +332,14 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB,
shext = agnstA;
}
bool foundExact = false;
SCurve *sc;
for(sc = shext->curve.First(); sc; sc = shext->curve.NextAfter(sc)) {
if(sc->source == SCurve::Source::INTERSECTION) continue;
if(!sc->isExact) continue;
if((sc->surfA != sext->h) && (sc->surfB != sext->h)) continue;
for(SCurve &sc : shext->curve) {
if(sc.source == SCurve::Source::INTERSECTION) continue;
if(!sc.isExact) continue;
if((sc.surfA != sext->h) && (sc.surfB != sext->h)) continue;
// we have a curve belonging to the curved surface and not the plane.
// does it lie completely in the plane?
if(splane->ContainsPlaneCurve(sc)) {
SBezier bezier = sc->exact;
if(splane->ContainsPlaneCurve(&sc)) {
SBezier bezier = sc.exact;
AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into);
foundExact = true;
}
@ -571,10 +570,9 @@ bool SSurface::ContainsPlaneCurve(SCurve *sc) const {
void SShell::MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal,
SEdgeList *el, SShell *useCurvesFrom)
{
SSurface *ss;
for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) {
if(proto->CoincidentWith(ss, sameNormal)) {
ss->MakeEdgesInto(this, el, SSurface::MakeAs::XYZ, useCurvesFrom);
for(SSurface &ss : surface) {
if(proto->CoincidentWith(&ss, sameNormal)) {
ss.MakeEdgesInto(this, el, SSurface::MakeAs::XYZ, useCurvesFrom);
}
}

View File

@ -426,7 +426,7 @@ void SContour::UvTriangulateInto(SMesh *m, SSurface *srf) {
if (i == l.n-1) {
end = true;
}
if (end) { // triangulate the fan and tag the verticies
if (end) { // triangulate the fan and tag the vertices
if (j > 3) {
Vector center = l[pstart+1].p.Plus(l[pstart+j-1].p).ScaledBy(0.5);
for (int x=0; x<j; x++) {
@ -597,11 +597,11 @@ void SPolygon::UvGridTriangulateInto(SMesh *mesh, SSurface *srf) {
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 minimun
if ((li.n < 5) && (srf->degm>1)) { // 4 segments minimum
li.Clear();
li.Add(&v[0]);li.Add(&v[1]);li.Add(&v[2]);li.Add(&v[3]);li.Add(&v[4]);
}
if ((lj.n < 5) && (srf->degn>1)) { // 4 segments minimun
if ((lj.n < 5) && (srf->degn>1)) { // 4 segments minimum
lj.Clear();
lj.Add(&v[0]);lj.Add(&v[1]);lj.Add(&v[2]);lj.Add(&v[3]);lj.Add(&v[4]);
}
@ -681,7 +681,7 @@ void SPolygon::UvGridTriangulateInto(SMesh *mesh, SSurface *srf) {
if (!bottom[j]) // add our own bottom edge
holes.AddEdge(a, b);
} else {
if (prev_flag) // add our left neighbots right edge
if (prev_flag) // add our left neighbors right edge
holes.AddEdge(a, d);
if (bottom[j]) // add our bottom neighbors top edge
holes.AddEdge(b, a);

View File

@ -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, true },
{ { CONSTRUCTION }, "Construction", RGBf(0.1, 0.7, 0.1), 1.5, 0, false },
{ { INACTIVE_GRP }, "InactiveGrp", RGBf(0.5, 0.3, 0.0), 1.5, 3, true },
{ { DATUM }, "Datum", RGBf(0.0, 0.8, 0.0), 1.5, 0, true },
{ { SOLID_EDGE }, "SolidEdge", RGBf(0.8, 0.8, 0.8), 1.0, 2, true },
{ { CONSTRAINT }, "Constraint", RGBf(1.0, 0.1, 1.0), 1.0, 0, true },
{ { SELECTED }, "Selected", RGBf(1.0, 0.0, 0.0), 1.5, 0, true },
{ { HOVERED }, "Hovered", RGBf(1.0, 1.0, 0.0), 1.5, 0, true },
{ { CONTOUR_FILL }, "ContourFill", RGBf(0.0, 0.1, 0.1), 1.0, 0, true },
{ { NORMALS }, "Normals", RGBf(0.0, 0.4, 0.4), 1.0, 0, true },
{ { ANALYZE }, "Analyze", RGBf(0.0, 1.0, 1.0), 3.0, 0, true },
{ { DRAW_ERROR }, "DrawError", RGBf(1.0, 0.0, 0.0), 8.0, 0, true },
{ { DIM_SOLID }, "DimSolid", RGBf(0.1, 0.1, 0.1), 1.0, 0, true },
{ { HIDDEN_EDGE }, "HiddenEdge", RGBf(0.8, 0.8, 0.8), 1.0, 1, true },
{ { OUTLINE }, "Outline", RGBf(0.8, 0.8, 0.8), 3.0, 5, true },
{ { 0 }, NULL, RGBf(0.0, 0.0, 0.0), 0.0, 0, true }
{ { 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,6 +32,12 @@ 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";
}
@ -105,9 +111,14 @@ void Style::FillDefaultStyle(Style *s, const Default *d, bool factory) {
: 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;
}
@ -125,6 +136,8 @@ 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));
}
@ -353,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) {
@ -389,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) {
@ -405,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;
}
@ -499,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;

View File

@ -123,13 +123,18 @@ void TextWindow::ShowListOfGroups() {
sprintf(sdof, "%-3d", dof);
}
}
std::string suffix;
if(g->forceToMesh || g->IsTriangleMeshAssembly()) {
suffix = " (∆)";
}
bool ref = (g->h == Group::HGROUP_REFERENCES);
Printf(false,
"%Bp%Fd "
"%Ft%s%Fb%D%f%Ll%s%E "
"%Fb%s%D%f%Ll%s%E "
"%Fp%D%f%s%Ll%s%E "
"%Fl%Ll%D%f%s",
"%Fp%Ll%D%f%s%E%s",
// Alternate between light and dark backgrounds, for readability
backgroundParity ? 'd' : 'a',
// Link that activates the group
@ -146,7 +151,9 @@ void TextWindow::ShowListOfGroups() {
ok ? ((warn && SS.checkClosedContour) ? "err" : sdof) : "",
ok ? "" : "ERR",
// Link to a screen that gives more details on the group
g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str());
g->suppress ? 'g' : 'l',
g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str(),
suffix.c_str());
if(active) afterActive = true;
backgroundParity = !backgroundParity;
@ -299,6 +306,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();
@ -398,6 +422,26 @@ void TextWindow::ShowGroupInfo() {
}
Printf(false, "");
if(g->type == Group::Type::HELIX) {
Printf(false, "%Ft pitch - length per turn%E");
if (fabs(g->valB) != 0.0) {
Printf(false, " %Ba %# %Fl%Ll%f%D[change]%E",
g->valB / SS.MmPerUnit(),
&TextWindow::ScreenChangeHelixPitch, g->h.v);
} else {
Printf(false, " %Ba %# %E",
SK.GetParam(g->h.param(7))->val * PI /
( (SK.GetParam(g->h.param(3))->val) * SS.MmPerUnit() ),
&TextWindow::ScreenChangeHelixPitch, g->h.v);
}
Printf(false, " %Fd%f%LP%s fixed",
&TextWindow::ScreenChangePitchOption,
g->valB != 0 ? CHECK_TRUE : CHECK_FALSE);
Printf(false, ""); // blank line
}
if(g->type == Group::Type::EXTRUDE || g->type == Group::Type::LATHE ||
g->type == Group::Type::REVOLVE || g->type == Group::Type::LINKED ||
g->type == Group::Type::HELIX) {
@ -451,7 +495,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);
@ -579,7 +623,7 @@ void TextWindow::ShowGroupSolveInfo() {
}
if(g->solved.timeout) {
Printf(true, "%FxSome items in list have been ommitted%Fd");
Printf(true, "%FxSome items in list have been omitted%Fd");
Printf(false, "%Fxbecause the operation timed out.%Fd");
}
@ -603,7 +647,7 @@ void TextWindow::ScreenStepDimFinish(int link, uint32_t v) {
SS.TW.edit.meaning = Edit::STEP_DIM_FINISH;
std::string edit_value;
if(SS.TW.stepDim.isDistance) {
edit_value = SS.MmToString(SS.TW.stepDim.finish);
edit_value = SS.MmToString(SS.TW.stepDim.finish, true);
} else {
edit_value = ssprintf("%.3f", SS.TW.stepDim.finish);
}
@ -690,7 +734,7 @@ void TextWindow::ScreenChangeTangentArc(int link, uint32_t v) {
switch(link) {
case 'r': {
SS.TW.edit.meaning = Edit::TANGENT_ARC_RADIUS;
SS.TW.ShowEditControl(3, SS.MmToString(SS.tangentArcRadius));
SS.TW.ShowEditControl(3, SS.MmToString(SS.tangentArcRadius, true));
break;
}
@ -789,6 +833,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) {

View File

@ -203,7 +203,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) }
};
@ -348,8 +348,8 @@ void TextWindow::ClearScreen() {
rows = 0;
}
// This message was addded when someone had too many fonts for the text window
// Scrolling seemed to be broken, but was actaully at the MAX_ROWS.
// This message was added when someone had too many fonts for the text window
// Scrolling seemed to be broken, but was actually at the MAX_ROWS.
static const char* endString = " **** End of Text Screen ****";
void TextWindow::Printf(bool halfLine, const char *fmt, ...) {

View File

@ -153,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 = 32*18 + 3*16 + fudge;
int h = 32*18 + 3*16 + fudge; // Toolbar height = 18 icons * 32 pixels + 3 dividers * 16 pixels + fudge
if(h < y) {
// If there is enough vertical space leave up to 32 pixels between the menu bar and the toolbar.
y -= ((y - h) < 32) ? y - h : 32;
}
int aleft = 0, aright = 66, atop = y+16+fudge/2, abot = y+16-h;
bool withinToolbar =

View File

@ -82,6 +82,7 @@ enum class Command : uint32_t {
SHOW_GRID,
DIM_SOLID_MODEL,
PERSPECTIVE_PROJ,
EXPLODE_SKETCH,
ONTO_WORKPLANE,
NEAREST_ORTHO,
NEAREST_ISO,
@ -89,6 +90,7 @@ enum class Command : uint32_t {
SHOW_TOOLBAR,
SHOW_TEXT_WND,
UNITS_INCHES,
UNITS_FEET_INCHES,
UNITS_MM,
UNITS_METERS,
FULL_SCREEN,
@ -312,12 +314,14 @@ public:
EXPORT_OFFSET = 110,
CANVAS_SIZE = 111,
G_CODE_DEPTH = 112,
G_CODE_PASSES = 113,
G_CODE_FEED = 114,
G_CODE_PLUNGE_FEED = 115,
AUTOSAVE_INTERVAL = 116,
LIGHT_AMBIENT = 117,
FIND_CONSTRAINT_TIMEOUT = 118,
G_CODE_SAFE_HEIGHT = 113,
G_CODE_PASSES = 114,
G_CODE_FEED = 115,
G_CODE_PLUNGE_FEED = 116,
AUTOSAVE_INTERVAL = 117,
LIGHT_AMBIENT = 118,
FIND_CONSTRAINT_TIMEOUT = 119,
EXPLODE_DISTANCE = 120,
// For TTF text
TTF_TEXT = 300,
// For the step dimension screen
@ -342,7 +346,9 @@ public:
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;
@ -473,6 +479,8 @@ public:
static void ScreenChangeExprA(int link, uint32_t v);
static void ScreenChangeGroupName(int link, uint32_t v);
static void ScreenChangeGroupScale(int link, uint32_t v);
static void ScreenChangeHelixPitch(int link, uint32_t v);
static void ScreenChangePitchOption(int link, uint32_t v);
static void ScreenChangeLightDirection(int link, uint32_t v);
static void ScreenChangeLightIntensity(int link, uint32_t v);
static void ScreenChangeLightAmbient(int link, uint32_t v);
@ -483,6 +491,7 @@ public:
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);
@ -535,6 +544,7 @@ public:
Platform::MenuItemRef showGridMenuItem;
Platform::MenuItemRef dimSolidModelMenuItem;
Platform::MenuItemRef perspectiveProjMenuItem;
Platform::MenuItemRef explodeMenuItem;
Platform::MenuItemRef showToolbarMenuItem;
Platform::MenuItemRef showTextWndMenuItem;
Platform::MenuItemRef fullScreenMenuItem;
@ -542,6 +552,7 @@ public:
Platform::MenuItemRef unitsMmMenuItem;
Platform::MenuItemRef unitsMetersMenuItem;
Platform::MenuItemRef unitsInchesMenuItem;
Platform::MenuItemRef unitsFeetInchesMenuItem;
Platform::MenuItemRef inWorkplaneMenuItem;
Platform::MenuItemRef in3dMenuItem;
@ -798,6 +809,8 @@ public:
bool showEdges;
bool showOutlines;
bool showFaces;
bool showFacesDrawing;
bool showFacesNonDrawing;
bool showMesh;
void ToggleBool(bool *v);

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

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