File size: 6,746 Bytes
7c72eb2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import tempfile
from pathlib import Path

import numpy as np
import pytest

from server.preprocessor import (
    normalize_shape,
    sample_surface_points,
    voxelize,
    preprocess_from_code,
    _occ_to_trimesh,
)


class TestNormalizeShape:
    def test_centered_at_origin(self, flat_plate_shape):
        normalized, info = normalize_shape(flat_plate_shape)
        bb = normalized.BoundingBox()
        cx = (bb.xmin + bb.xmax) / 2
        cy = (bb.ymin + bb.ymax) / 2
        cz = (bb.zmin + bb.zmax) / 2
        assert abs(cx) < 0.02
        assert abs(cy) < 0.02
        assert abs(cz) < 0.02

    def test_longest_axis_is_x(self, flat_plate_shape):
        normalized, info = normalize_shape(flat_plate_shape)
        bb = normalized.BoundingBox()
        assert bb.xlen >= bb.ylen
        assert bb.xlen >= bb.zlen

    def test_preserves_volume(self, flat_plate_shape):
        original_vol = flat_plate_shape.Volume()
        normalized, _ = normalize_shape(flat_plate_shape)
        new_vol = normalized.Volume()
        assert abs(original_vol - new_vol) / original_vol < 0.01

    def test_transform_info(self, flat_plate_shape):
        _, info = normalize_shape(flat_plate_shape)
        assert "units" in info
        assert info["units"] == "mm"
        assert "translation_to_origin" in info
        assert "longest_axis" in info
        assert info["longest_axis"] == "X"

    def test_box_with_hole_normalized(self, box_with_hole_shape):
        normalized, info = normalize_shape(box_with_hole_shape)
        bb = normalized.BoundingBox()
        assert bb.xlen >= bb.ylen >= bb.zlen


class TestOccToTrimesh:
    def test_returns_trimesh(self, flat_plate_shape):
        normalized, _ = normalize_shape(flat_plate_shape)
        mesh = _occ_to_trimesh(normalized)
        import trimesh
        assert isinstance(mesh, trimesh.Trimesh)
        assert len(mesh.vertices) > 0
        assert len(mesh.faces) > 0

    def test_mesh_is_valid(self, flat_plate_shape):
        normalized, _ = normalize_shape(flat_plate_shape)
        mesh = _occ_to_trimesh(normalized)
        assert not mesh.is_empty


class TestSampleSurfacePoints:
    def test_returns_correct_shape(self, flat_plate_shape):
        normalized, _ = normalize_shape(flat_plate_shape)
        points = sample_surface_points(normalized, n_points=1024)
        assert points.shape == (1024, 3)
        assert points.dtype == np.float32

    def test_points_near_surface(self, flat_plate_shape):
        normalized, _ = normalize_shape(flat_plate_shape)
        bb = normalized.BoundingBox()
        points = sample_surface_points(normalized, n_points=2048)
        assert np.all(points[:, 0] >= bb.xmin - 0.1)
        assert np.all(points[:, 0] <= bb.xmax + 0.1)
        assert np.all(points[:, 1] >= bb.ymin - 0.1)
        assert np.all(points[:, 1] <= bb.ymax + 0.1)
        assert np.all(points[:, 2] >= bb.zmin - 0.1)
        assert np.all(points[:, 2] <= bb.zmax + 0.1)

    def test_different_n_points(self, flat_plate_shape):
        normalized, _ = normalize_shape(flat_plate_shape)
        for n in [256, 512, 2048]:
            pts = sample_surface_points(normalized, n_points=n)
            assert pts.shape[0] == n


class TestVoxelize:
    def test_returns_correct_shape(self, flat_plate_shape):
        normalized, _ = normalize_shape(flat_plate_shape)
        grid = voxelize(normalized, resolution=32)
        assert grid.shape == (32, 32, 32)
        assert grid.dtype == bool

    def test_has_filled_voxels(self, flat_plate_shape):
        normalized, _ = normalize_shape(flat_plate_shape)
        grid = voxelize(normalized, resolution=32)
        assert grid.sum() > 0

    def test_not_all_filled(self, flat_plate_shape):
        normalized, _ = normalize_shape(flat_plate_shape)
        grid = voxelize(normalized, resolution=32)
        assert grid.sum() < 32 * 32 * 32

    def test_hollow_shape_has_fewer_voxels(self, flat_plate_shape, box_with_hole_shape):
        norm_plate, _ = normalize_shape(flat_plate_shape)
        norm_hole, _ = normalize_shape(box_with_hole_shape)
        grid_plate = voxelize(norm_plate, resolution=32)
        grid_hole = voxelize(norm_hole, resolution=32)
        plate_fill = grid_plate.sum() / (32**3)
        hole_fill = grid_hole.sum() / (32**3)
        assert plate_fill > 0
        assert hole_fill > 0

    def test_resolution_64(self, flat_plate_shape):
        normalized, _ = normalize_shape(flat_plate_shape)
        grid = voxelize(normalized, resolution=64)
        assert grid.shape == (64, 64, 64)
        assert grid.sum() > 0


class TestPreprocessFromCode:
    def test_generates_all_files(self, flat_plate_code):
        with tempfile.TemporaryDirectory() as tmpdir:
            gt = preprocess_from_code(flat_plate_code, tmpdir, task_id="test_flat_plate")
            output_path = Path(tmpdir)
            assert (output_path / "ground_truth.json").exists()
            assert (output_path / "ground_truth.step").exists()
            assert (output_path / "ground_truth_normalized.step").exists()
            assert (output_path / "surface_points.npy").exists()
            assert (output_path / "voxels_64.npy").exists()

    def test_ground_truth_json_contents(self, flat_plate_code):
        with tempfile.TemporaryDirectory() as tmpdir:
            gt = preprocess_from_code(flat_plate_code, tmpdir, task_id="test_flat_plate")
            assert "volume_mm3" in gt
            assert "bbox_mm" in gt
            assert "face_count" in gt
            assert "dominant_face_type" in gt
            assert "surface_points_file" in gt
            assert "voxels_file" in gt
            assert gt["volume_mm3"] > 0
            assert gt["face_count"] == 6

    def test_surface_points_file(self, flat_plate_code):
        with tempfile.TemporaryDirectory() as tmpdir:
            preprocess_from_code(flat_plate_code, tmpdir)
            pts = np.load(str(Path(tmpdir) / "surface_points.npy"))
            assert pts.shape == (2048, 3)

    def test_voxels_file(self, flat_plate_code):
        with tempfile.TemporaryDirectory() as tmpdir:
            preprocess_from_code(flat_plate_code, tmpdir)
            vox = np.load(str(Path(tmpdir) / "voxels_64.npy"))
            assert vox.shape == (64, 64, 64)
            assert vox.sum() > 0

    def test_invalid_code_raises(self, invalid_code):
        with tempfile.TemporaryDirectory() as tmpdir:
            with pytest.raises(Exception):
                preprocess_from_code(invalid_code, tmpdir)

    def test_no_result_raises(self, no_result_code):
        with tempfile.TemporaryDirectory() as tmpdir:
            with pytest.raises(ValueError, match="result"):
                preprocess_from_code(no_result_code, tmpdir)