Implement Phase 10a - Prop Scaling
- Added specifications for Phase 10a, introducing permanent size changes to `.prop` documents and reversible uniform instance scaling in Sandbox EDIT mode. - Updated `PropCanvas`, `PropAnchorData`, `PropEditor`, `StageGizmos`, `SandboxStage`, and `StageSelection` classes to support new scaling features. - Implemented document transactions for scaling, including normalization to a 96 px maximum dimension. - Introduced a new **Transform Whole Prop** tool in the Prop Editor for moving, rotating, and resizing props. - Created unit tests for the new scaling functionality, validating shape scaling, anchor transformations, and whole-prop interactions. - Ensured compatibility with existing prop formats and maintained the integrity of the document structure during scaling operations.
This commit is contained in:
@@ -71,6 +71,48 @@ static func translated_non_origin(anchors: Dictionary, delta: Vector2) -> Dictio
|
||||
normalized[ORIGIN_ID] = _origin()
|
||||
return normalized
|
||||
|
||||
static func try_translate_non_origin(anchors: Dictionary, delta: Vector2) -> Dictionary:
|
||||
if not is_finite(delta.x) or not is_finite(delta.y):
|
||||
return {"ok": false, "anchors": {}}
|
||||
var result: Dictionary = normalize_anchors(anchors, false)
|
||||
for id: String in editable_ids(result):
|
||||
var anchor: Dictionary = result[id].duplicate(true)
|
||||
var pos: Vector2 = anchor.get("position", Vector2.ZERO) + delta
|
||||
if not is_finite(pos.x) or not is_finite(pos.y): return {"ok": false, "anchors": {}}
|
||||
anchor["position"] = pos; result[id] = anchor
|
||||
result[ORIGIN_ID] = _origin()
|
||||
return {"ok": true, "anchors": result}
|
||||
|
||||
static func try_rotate_non_origin(anchors: Dictionary, angle_rad: float) -> Dictionary:
|
||||
if not is_finite(angle_rad): return {"ok": false, "anchors": {}}
|
||||
var result: Dictionary = normalize_anchors(anchors, false)
|
||||
for id: String in editable_ids(result):
|
||||
var anchor: Dictionary = result[id].duplicate(true)
|
||||
var pos: Vector2 = (anchor.get("position", Vector2.ZERO) as Vector2).rotated(angle_rad)
|
||||
if not is_finite(pos.x) or not is_finite(pos.y): return {"ok": false, "anchors": {}}
|
||||
anchor["position"] = pos
|
||||
anchor["rotation_deg"] = _wrap_deg(float(anchor.get("rotation_deg", 0.0)) + rad_to_deg(angle_rad))
|
||||
result[id] = anchor
|
||||
result[ORIGIN_ID] = _origin()
|
||||
return {"ok": true, "anchors": result}
|
||||
|
||||
|
||||
## Returns a fully normalized scaled copy, or {"ok": false} without changing
|
||||
## the caller's data when the operation cannot be represented safely.
|
||||
static func try_scale_non_origin(anchors: Dictionary, factor: float) -> Dictionary:
|
||||
if not is_finite(factor) or factor <= 0.0:
|
||||
return {"ok": false, "anchors": {}}
|
||||
var normalized: Dictionary = normalize_anchors(anchors, false)
|
||||
for id: String in editable_ids(normalized):
|
||||
var anchor: Dictionary = normalized[id].duplicate(true)
|
||||
var position: Vector2 = anchor.get("position", Vector2.ZERO) * factor
|
||||
if not is_finite(position.x) or not is_finite(position.y):
|
||||
return {"ok": false, "anchors": {}}
|
||||
anchor["position"] = position
|
||||
normalized[id] = anchor
|
||||
normalized[ORIGIN_ID] = _origin()
|
||||
return {"ok": true, "anchors": normalized}
|
||||
|
||||
static func normalize_id(candidate: String, anchors: Dictionary) -> String:
|
||||
var id: String = candidate.strip_edges().to_lower()
|
||||
var regex := RegEx.new(); regex.compile("[^a-z0-9]+")
|
||||
|
||||
Reference in New Issue
Block a user