Validate DXF geometry and splines and improve viewer maintenance
This commit is contained in:
1 parent
11fd3badf6
commit
9cee9bf791
6 files changed
+560
-77
No files matched your search
@@ -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'')
|
||||
|
||||
Reference in new issue
Block a user