feat: thumbnail grid, aspect-aware templates, and UI hierarchy polish
Material list: - Switch asset list from one-per-row to a wrapping thumbnail grid (IconMode cards: thumbnail + corner checkbox + elided name, subfolder moved to tooltip). Templates: - Template.to_transform_state now contains-fits the print inside the target box by the print's native aspect ratio (no stretch); falls back to the box when print size is unknown. - Feed print native size into template_panel for aspect-aware fitting. - Shrink built-in template boxes to realistic print proportions. UI hierarchy: - Promote 打开文件夹 buttons to primary (filled accent). - Add inline 归零 (reset angle) and a 重置位置/尺寸/角度 button to the transform panel; wire it to re-apply the selected template. - Hide 保存 for built-in templates instead of leaving it greyed. Docs: record template box/contain behavior (PRD) and the grid layout, button hierarchy, and reset/save-visibility rules (UI design). Tests: add 3 aspect-fit cases for to_transform_state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -192,6 +192,48 @@ class TestTemplateToTransformState(unittest.TestCase):
|
||||
self.assertAlmostEqual(state.width, 100.0)
|
||||
self.assertAlmostEqual(state.height, 200.0)
|
||||
|
||||
def test_aspect_fit_square_box_wide_print(self):
|
||||
"""A wide print in a square box fits by width and stays undistorted."""
|
||||
from core.models import Template
|
||||
# Box: 200x200 at (100,100) on a 400x400 garment
|
||||
t = Template(name="t", x_ratio=0.25, y_ratio=0.25,
|
||||
width_ratio=0.5, height_ratio=0.5)
|
||||
state = t.to_transform_state(400, 400, print_width=100, print_height=50)
|
||||
# Wide print (2:1) → limited by width: 200 wide, 100 tall
|
||||
self.assertAlmostEqual(state.width, 200.0)
|
||||
self.assertAlmostEqual(state.height, 100.0)
|
||||
# Aspect ratio preserved
|
||||
self.assertAlmostEqual(state.width / state.height, 100 / 50)
|
||||
# Centered in the box (centre at 200,200)
|
||||
self.assertAlmostEqual(state.x + state.width / 2, 200.0)
|
||||
self.assertAlmostEqual(state.y + state.height / 2, 200.0)
|
||||
|
||||
def test_aspect_fit_square_box_tall_print(self):
|
||||
"""A tall print in a square box fits by height and stays undistorted."""
|
||||
from core.models import Template
|
||||
t = Template(name="t", x_ratio=0.25, y_ratio=0.25,
|
||||
width_ratio=0.5, height_ratio=0.5)
|
||||
state = t.to_transform_state(400, 400, print_width=50, print_height=100)
|
||||
# Tall print (1:2) → limited by height: 100 wide, 200 tall
|
||||
self.assertAlmostEqual(state.width, 100.0)
|
||||
self.assertAlmostEqual(state.height, 200.0)
|
||||
self.assertAlmostEqual(state.x + state.width / 2, 200.0)
|
||||
self.assertAlmostEqual(state.y + state.height / 2, 200.0)
|
||||
|
||||
def test_aspect_fit_never_exceeds_box(self):
|
||||
"""Fitted print must never exceed the template box bounds."""
|
||||
from core.models import Template
|
||||
t = Template(name="t", x_ratio=0.34, y_ratio=0.22,
|
||||
width_ratio=0.32, height_ratio=0.32)
|
||||
# Portrait garment, square print
|
||||
state = t.to_transform_state(800, 1200, print_width=300, print_height=300)
|
||||
box_w = 0.32 * 800
|
||||
box_h = 0.32 * 1200
|
||||
self.assertLessEqual(state.width, box_w + 1e-6)
|
||||
self.assertLessEqual(state.height, box_h + 1e-6)
|
||||
# Square print stays square (no stretch despite portrait garment)
|
||||
self.assertAlmostEqual(state.width, state.height)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# File scanning
|
||||
|
||||
Reference in New Issue
Block a user