Skip to content

Commit 2198c1f

Browse files
committed
release: SketchForge 1.0.8
1 parent 62502b7 commit 2198c1f

12 files changed

Lines changed: 621 additions & 120 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<a href="https://github.com/Formsmith746/SketchForge-3D/stargazers"><img alt="Star SketchForge on GitHub" src="https://img.shields.io/github/stars/Formsmith746/SketchForge-3D?style=flat&logo=github"></a>
2020
<a href="https://github.com/sponsors/Formsmith746"><img alt="Sponsor SketchForge on GitHub" src="https://img.shields.io/badge/GitHub-Sponsor-ea4aaa?logo=githubsponsors&logoColor=white"></a>
2121
<img alt="Local first" src="https://img.shields.io/badge/local--first-no%20account-0ea5e9">
22-
<img alt="Version v1.0.7" src="https://img.shields.io/badge/version-v1.0.7-2563eb">
22+
<img alt="Version v1.0.8" src="https://img.shields.io/badge/version-v1.0.8-2563eb">
2323
</p>
2424
</div>
2525

apps/web/src/components/SketchForgeEditor.tsx

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import {
8686
import { cloneWorkplaneShapeSnapshot, compactEdgeTreatmentHistory, edgeTreatmentAppliedFrame, restoreShapeBeforeEdgeTreatment } from "@/lib/edgeTreatmentHistory";
8787
import { appendEditorHistorySnapshot, boundedEditorHistoryState, editorHistoryEntry, editorHistoryForExport, hydrateEditorHistoryState, projectShapesFingerprint, type EditorHistoryEntry, type EditorHistoryExportLimit, type EditorHistoryState } from "@/lib/editorHistory";
8888
import { snapShapeFootprintToVisibleGrid, visibleGridStep } from "@/lib/gridSnap";
89+
import { geometryRotationDegreesForShortcut, geometryRotationDelta, rotatedGeometryShapePatch } from "@/lib/geometryRotation";
8990
import { createLocalId } from "@/lib/localIds";
9091
import { projectExportFileName } from "@/lib/exportNames";
9192
import { exportMeshesToObj } from "@/lib/objExport";
@@ -8868,10 +8869,14 @@ export function SketchForgeEditor({
88688869
[commitShapes, hasSelection, placementWorkplane, selectedIds, selectedShapes.length, shapes],
88698870
);
88708871

8871-
const rotateSelected45 = useCallback(() => {
8872+
const rotateSelectedBy = useCallback((angleDegrees: number) => {
88728873
if (!hasSelection) {
88738874
return;
88748875
}
8876+
if (projectInteractionActiveRef.current) {
8877+
setNotice("Finish the current drag or transform before rotating");
8878+
return;
8879+
}
88758880

88768881
const selected = new Set(selectedIds);
88778882
const rotatableShapes = selectedShapes.filter((shape) => !shape.locked);
@@ -8880,46 +8885,23 @@ export function SketchForgeEditor({
88808885
return;
88818886
}
88828887

8883-
const axis = new THREE.Vector3(
8884-
placementWorkplane.normal.x,
8885-
placementWorkplane.normal.y,
8886-
placementWorkplane.normal.z,
8887-
);
8888-
if (axis.lengthSq() < 0.000001) {
8889-
axis.set(0, 1, 0);
8890-
} else {
8891-
axis.normalize();
8892-
}
8893-
const rotationDelta = new THREE.Quaternion().setFromAxisAngle(axis, THREE.MathUtils.degToRad(45));
8888+
const rotationDelta = geometryRotationDelta(placementWorkplane, angleDegrees);
88948889
const pivot = rotatableShapes.length > 1 ? selectionCenterOnWorkplane(rotatableShapes, placementWorkplane) : null;
88958890

88968891
const nextShapes = shapes.map((shape) => {
88978892
if (!selected.has(shape.id) || shape.locked) {
88988893
return shape;
88998894
}
89008895

8901-
const nextQuaternion = rotationDelta.clone().multiply(quaternionForShape(shape));
8902-
const rotationPatch = rotationFromQuaternion(nextQuaternion);
8903-
let rotated = canonicalizeShape({ ...shape, ...rotationPatch });
8904-
8905-
if (pivot) {
8906-
const startCenter = new THREE.Vector3(shape.x, (shape.elevation ?? 0) + shape.height / 2, shape.z);
8907-
const nextCenter = pivot.clone().add(startCenter.sub(pivot).applyQuaternion(rotationDelta));
8908-
rotated = canonicalizeShape({
8909-
...rotated,
8910-
x: cleanNearZero(nextCenter.x, 0.0005),
8911-
z: cleanNearZero(nextCenter.z, 0.0005),
8912-
elevation: cleanNearZero(nextCenter.y - shape.height / 2, 0.0005),
8913-
});
8914-
}
8915-
8896+
const rotated = canonicalizeShape({ ...shape, ...rotatedGeometryShapePatch(shape, rotationDelta, pivot) });
89168897
return canonicalizeShape(bakeShapeTransformIntoMesh(rotated));
89178898
});
89188899

8900+
const angleLabel = Number(angleDegrees.toFixed(1));
89198901
commitShapes(
89208902
nextShapes,
89218903
selectedIds,
8922-
`Rotated ${rotatableShapes.length} shape${rotatableShapes.length === 1 ? "" : "s"} by 45°`,
8904+
`Rotated ${rotatableShapes.length} shape${rotatableShapes.length === 1 ? "" : "s"} by ${angleLabel}°`,
89238905
);
89248906
}, [commitShapes, hasSelection, placementWorkplane, selectedIds, selectedShapes, shapes]);
89258907

@@ -9052,6 +9034,13 @@ export function SketchForgeEditor({
90529034
return;
90539035
}
90549036

9037+
const geometryRotationDegrees = geometryRotationDegreesForShortcut(event);
9038+
if (geometryRotationDegrees !== null && hasSelection) {
9039+
event.preventDefault();
9040+
rotateSelectedBy(geometryRotationDegrees);
9041+
return;
9042+
}
9043+
90559044
const step = event.shiftKey ? 5 : 1;
90569045
if (shortcut && event.key === "ArrowUp") {
90579046
event.preventDefault();
@@ -9103,6 +9092,7 @@ export function SketchForgeEditor({
91039092
pasteShape,
91049093
raiseSelected,
91059094
redo,
9095+
rotateSelectedBy,
91069096
rotateSelectedClosedSketch45,
91079097
sketchActive,
91089098
sketchRedo,

0 commit comments

Comments
 (0)