Validate DXF geometry and splines and improve viewer maintenance

This commit is contained in:
eglee committed 2026-10-22 11:28:16 +09:00
1 parent 11fd3badf6
commit 9cee9bf791
6 files changed
+560 -77

No files matched your search

+130
View File
@@ -78,6 +78,8 @@ class DrawingReconstructionTests(unittest.TestCase):
self.assertNotIn('__INITIAL_SCENE__', source)
self.assertNotIn('__MODEL_JSON__', source)
self.assertNotIn('__DRAWING_SVG__', source)
self.assertNotIn('__VIEWER_JS__', source)
self.assertIn('function drawingRegions()', source)
before_script = source.split('<script')[0]
self.assertGreater(before_script.count('<polygon'), 900)
self.assertTrue((out / 'drawing' / r2c.DEFAULT_DXF.name).is_file())
@@ -110,6 +112,134 @@ class DrawingReconstructionTests(unittest.TestCase):
self.assertIn(status, response.split(b'\r\n')[0])
self.assertIn(content, response)
def test_every_table_specification_has_its_own_closed_mesh(self):
for row in self.model['table']:
with self.subTest(diameter=row['D']):
variant = r2c.specification_model(self.model, row)
self.assertEqual(variant['dimensions'], row)
mesh = variant['meshes']['r2c']
for axis, key in enumerate(['L', 'B', 'H']):
self.assertAlmostEqual(max(p[axis] for p in mesh['vertices']) - min(p[axis] for p in mesh['vertices']), row[key], places=5)
self.assertAlmostEqual(variant['axisHeight'], row['h6'])
self.assertAlmostEqual(variant['profiles'][0]['halfWidth'] * 2, row['b3'])
self.assertAlmostEqual(variant['profiles'][2]['radius'] * 2, row['D'])
edges = Counter()
for face in mesh['faces']:
for a, b in zip(face, face[1:] + face[:1]):
edges[tuple(sorted((a, b)))] += 1
self.assertTrue(all(n == 2 for n in edges.values()))
self.assertTrue(all(math.isfinite(n) for point in mesh['vertices'] for n in point))
def test_drawn_specification_retains_exact_source_profiles(self):
v = r2c.specification_model(self.model, self.model['dimensions'])
self.assertEqual(v['basis'], 'drawn')
self.assertEqual(v['profiles'], self.model['profiles'])
self.assertEqual(v['meshes']['r2c'], self.meshes['r2c'])
self.assertEqual(v['warnings'], [])
def test_nominal_specifications_report_conflicting_socket_dimensions(self):
v = r2c.specification_model(self.model, self.model['table'][0])
self.assertEqual(v['basis'], 'table')
self.assertTrue(v['warnings'])
self.assertAlmostEqual(v['profiles'][3]['x'], v['length'] - v['dimensions']['L2'])
self.assertAlmostEqual(v['profiles'][-2]['x'], v['length'] - v['dimensions']['L3'])
self.assertNotAlmostEqual(v['length'] / self.model['length'], v['width'] / self.model['width'])
def test_invalid_table_is_rejected(self):
bad = dict(self.model['table'][0], B=100)
with self.assertRaises(ValueError):
r2c.specification_model(self.model, bad)
def api_request(self, raw, filename='upload.dxf', length=None, path='/api/convert'):
class Handler(r2c.ViewerHandler):
def log_message(self, *args):
pass
handler = object.__new__(Handler)
handler.path = path
handler.command = 'POST'
handler.request_version = 'HTTP/1.1'
handler.requestline = 'POST ' + path + ' HTTP/1.1'
handler.headers = {'Content-Length': str(len(raw) if length is None else length), 'X-Filename': quote(filename)}
handler.rfile = io.BytesIO(raw)
handler.wfile = io.BytesIO()
handler.do_POST()
header, body = handler.wfile.getvalue().split(b'\r\n\r\n', 1)
return int(header.split()[1]), json.loads(body)
def test_upload_converts_actual_file_and_provides_all_specs(self):
status, payload = self.api_request(self.raw, '첨부 도면.dxf')
self.assertEqual(status, 200)
self.assertEqual(payload['model']['source'], '첨부 도면.dxf')
self.assertEqual(len(payload['model']['variants']), 7)
self.assertEqual(payload['model']['sha256'], self.model['sha256'])
self.assertTrue(payload['drawingSvg'].startswith('<svg'))
self.assertEqual(r2c.DEFAULT_DXF.read_bytes(), self.raw)
def test_second_upload_uses_uploaded_table_instead_of_default_file(self):
changed = self.raw.replace(b'1,605', b'1,607')
self.assertNotEqual(changed, self.raw)
status, payload = self.api_request(changed, 'another.dxf')
self.assertEqual(status, 200)
self.assertEqual(payload['model']['variants'][0]['length'], 1607)
self.assertNotEqual(payload['model']['sha256'], self.model['sha256'])
self.assertEqual(r2c.DEFAULT_DXF.read_bytes(), self.raw)
def test_upload_rejects_invalid_oversized_or_truncated_input(self):
for raw, filename, length, expected in [
(b'', 'empty.dxf', None, 400),
(b'not a dxf', 'wrong.dxf', None, 422),
(b'AutoCAD Binary DXF', 'binary.dxf', None, 422),
(b'data', 'wrong.pdf', None, 415),
(b'x', 'large.dxf', r2c.MAX_UPLOAD_BYTES + 1, 413),
(b'x', 'missing.dxf', 10, 400),
]:
with self.subTest(expected=expected, filename=filename):
status, payload = self.api_request(raw, filename, length)
self.assertEqual(status, expected)
self.assertIn('error', payload)
status, payload = self.api_request(b'x', path='/wrong')
self.assertEqual(status, 404)
def test_inner_profiles_must_leave_positive_wall_thickness(self):
for changes in ({'D2': 5000}, {'D1': 5000}, {'b1': 5000},
{'h4': 5000}, {'D1': 10}):
with self.subTest(changes=changes):
row = dict(self.model['table'][0], **changes)
with self.assertRaisesRegex(ValueError, '관로 단면'):
r2c.specification_model(self.model, row)
def test_spline_validation_and_valid_linear_interpolation(self):
def spline(weights=('1', '1'), knots=('0', '0', '1', '1'), degree='1'):
return {'type': 'SPLINE', 'tags': [
(71, degree), (10, '0'), (20, '0'), (10, '10'), (20, '20'),
*[(41, w) for w in weights], *[(40, k) for k in knots]]}
points = r2c.spline_points(spline())
self.assertEqual(points[0], [0, 0])
self.assertEqual(points[32], [5, 10])
self.assertEqual(points[-1], [10, 20])
for entity in (spline(weights=('0', '0')), spline(weights=('1',)),
spline(knots=('0', '1', '0', '1')),
spline(knots=('0', '0', '0', '0')), spline(degree='-1')):
with self.subTest(entity=entity), self.assertRaises(ValueError):
r2c.spline_points(entity)
def test_upload_with_invalid_spline_returns_json_error(self):
lines = self.raw.decode('utf-8-sig').splitlines()
start = next(i + 2 for i in range(0, len(lines), 2)
if lines[i].strip() == '2' and lines[i + 1].strip() == 'ENTITIES')
end = next(i for i in range(start, len(lines), 2)
if lines[i].strip() == '0' and lines[i + 1].strip() == 'ENDSEC')
extra = ('0 SPLINE 71 1 10 0 20 0 41 0 10 1 20 1 41 0 '
'40 0 40 0 40 1 40 1').split()
raw = '\n'.join(lines[:end] + extra + lines[end:]).encode('utf-8')
status, payload = self.api_request(raw)
self.assertEqual(status, 422)
self.assertIn('SPLINE', payload['error'])
def test_nonfinite_coordinates_are_rejected_before_geometry(self):
with self.assertRaisesRegex(ValueError, '유효하지 않은 좌표'):
r2c.read_dxf_bytes(b'10\nNaN\n')
def test_unrelated_drawing_does_not_produce_a_guessed_model(self):
with self.assertRaisesRegex(ValueError, '변화치수표'):
r2c.extract_model([], b'')