""" drafting unittests name: test_drafting.py by: Gumyr date: September 17th, 2023 desc: This python module contains the unittests for the drafting functionality. license: Copyright 2023 Gumyr Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import math import unittest from datetime import date import pytest from build123d import ( IN, Axis, BuildLine, BuildSketch, Color, Edge, Face, FontStyle, GeomType, HeadType, Mode, NumberDisplay, Polyline, RadiusArc, Rectangle, Sketch, Unit, add, make_face, offset, ) from build123d.drafting import ( ArrowHead, DimensionLine, Draft, ExtensionLine, TechnicalDrawing, ) metric = Draft( font_size=3.0, font="Arial", font_style=FontStyle.REGULAR, head_type=HeadType.CURVED, arrow_length=3.0, line_width=0.25, unit=Unit.MM, number_display=NumberDisplay.DECIMAL, display_units=True, decimal_precision=2, fractional_precision=64, extension_gap=2.0, ) imperial = Draft( font_size=5.0, font="Arial", font_style=FontStyle.REGULAR, head_type=HeadType.CURVED, arrow_length=3.0, line_width=0.25, unit=Unit.IN, number_display=NumberDisplay.FRACTION, display_units=True, decimal_precision=2, fractional_precision=64, extension_gap=2.0, ) def create_test_sketch() -> tuple[Sketch, Sketch, Sketch]: with BuildSketch() as sketchy: with BuildLine(): l1 = Polyline((10, 20), (-20, 20), (-20, -20), (10, -20)) RadiusArc(l1 @ 0, l1 @ 1, 25) make_face() outside = sketchy.sketch inside = offset(amount=-0.5, mode=Mode.SUBTRACT) return (sketchy.sketch, outside, inside) class TestClassInstantiation(unittest.TestCase): """Test Draft class instantiation""" def test_draft_instantiation(self): """Parameter parsing""" with self.assertRaises(ValueError): Draft(fractional_precision=37) class TestDraftFunctionality(unittest.TestCase): """Test core drafting functionality""" def test_number_with_units(self): metric_drawing = Draft(decimal_precision=2) self.assertEqual(metric_drawing._number_with_units(3.141), "3.14mm") self.assertEqual(metric_drawing._number_with_units(3.149), "3.15mm") self.assertEqual(metric_drawing._number_with_units(0), "0.00mm") self.assertEqual( metric_drawing._number_with_units(3.14, tolerance=0.01), "3.14 ±0.01mm" ) self.assertEqual( metric_drawing._number_with_units(3.14, tolerance=(0.01, 0)), "3.14 +0.01 -0.00mm", ) whole_number_drawing = Draft(decimal_precision=-1) self.assertEqual(whole_number_drawing._number_with_units(314.1), "310mm") imperial_drawing = Draft(unit=Unit.IN) self.assertEqual(imperial_drawing._number_with_units((5 / 8) * IN), '0.62"') imperial_fractional_drawing = Draft( unit=Unit.IN, number_display=NumberDisplay.FRACTION, fractional_precision=64 ) self.assertEqual( imperial_fractional_drawing._number_with_units((5 / 8) * IN), '5/8"' ) self.assertEqual( imperial_fractional_drawing._number_with_units(math.pi * IN), '3 9/64"' ) imperial_fractional_drawing.fractional_precision = 16 self.assertEqual( imperial_fractional_drawing._number_with_units(math.pi * IN), '3 1/8"' ) def test_label_to_str(self): metric_drawing = Draft(decimal_precision=0) line = Edge.make_line((0, 0, 0), (100, 0, 0)) with self.assertRaises(ValueError): metric_drawing._label_to_str( label=None, line_wire=line, label_angle=True, tolerance=0, ) arc1 = Edge.make_circle(100, start_angle=0, end_angle=30) angle_str = metric_drawing._label_to_str( label=None, line_wire=arc1, label_angle=True, tolerance=0, ) self.assertEqual(angle_str, "30°") class ArrowHeadTests(unittest.TestCase): def test_arrowhead_types(self): arrow = ArrowHead(10, HeadType.CURVED) bbox = arrow.bounding_box() self.assertEqual(len(arrow.edges().filter_by(GeomType.CIRCLE)), 2) self.assertAlmostEqual(bbox.size.X, 10, 5) arrow = ArrowHead(10, HeadType.FILLETED) bbox = arrow.bounding_box() self.assertEqual(len(arrow.edges().filter_by(GeomType.CIRCLE)), 5) self.assertLess(bbox.size.X, 10) arrow = ArrowHead(10, HeadType.STRAIGHT) self.assertEqual(len(arrow.edges().filter_by(GeomType.CIRCLE)), 0) bbox = arrow.bounding_box() self.assertAlmostEqual(bbox.size.X, 10, 5) class DimensionLineTestCase(unittest.TestCase): def test_two_points(self): d_line = DimensionLine([(0, 0, 0), (100, 0, 0)], draft=metric) bbox = d_line.bounding_box() self.assertAlmostEqual(bbox.max.X, 100, 5) self.assertAlmostEqual(d_line.dimension, 100, 5) self.assertEqual(len(d_line.faces()), 10) def test_three_points(self): with self.assertRaises(ValueError): DimensionLine([(0, 0, 0), (50, 0, 0), (50, 50, 0)], draft=metric) def test_edge(self): d_line = DimensionLine(Edge.make_line((0, 0), (100, 0)), draft=metric) bbox = d_line.bounding_box() self.assertAlmostEqual(bbox.max.X, 100, 5) self.assertEqual(len(d_line.faces()), 10) def test_vertices(self): d_line = DimensionLine( Edge.make_line((0, 0), (100, 0)).vertices(), draft=metric ) bbox = d_line.bounding_box() self.assertAlmostEqual(bbox.max.X, 100, 5) self.assertEqual(len(d_line.faces()), 10) def test_label(self): d_line = DimensionLine([(0, 0, 0), (100, 0, 0)], label="Test", draft=metric) bbox = d_line.bounding_box() self.assertAlmostEqual(bbox.max.X, 100, 5) self.assertEqual(len(d_line.faces()), 6) def test_face(self): with self.assertRaises(ValueError): DimensionLine(Face.make_rect(100, 100), draft=metric) def test_builder_mode(self): with BuildSketch() as s1: Rectangle(100, 100) hole = offset(amount=-5, mode=Mode.SUBTRACT) d_line = DimensionLine( [ hole.vertices().group_by(Axis.Y)[-1].sort_by(Axis.X)[-1], hole.vertices().group_by(Axis.Y)[0].sort_by(Axis.X)[0], ], draft=metric, ) self.assertGreater(hole.intersect(d_line).area, 0) def test_outside_arrows(self): d_line = DimensionLine([(0, 0, 0), (15, 0, 0)], draft=metric) bbox = d_line.bounding_box() self.assertAlmostEqual(bbox.size.X, 15 + 4 * metric.arrow_length, 5) self.assertAlmostEqual(d_line.dimension, 15, 5) self.assertEqual(len(d_line.faces()), 9) def test_outside_label(self): d_line = DimensionLine([(0, 0, 0), (5, 0, 0)], draft=metric) bbox = d_line.bounding_box() self.assertGreater(bbox.size.X, 5 + 4 * metric.arrow_length) self.assertAlmostEqual(d_line.dimension, 5, 5) self.assertEqual(len(d_line.faces()), 8) def test_single_outside_label(self): d_line = DimensionLine( [(0, 0, 0), (5, 0, 0)], draft=metric, arrows=(False, True) ) bbox = d_line.bounding_box() self.assertAlmostEqual(bbox.min.X, 5, 5) self.assertAlmostEqual(d_line.dimension, 5, 5) self.assertEqual(len(d_line.faces()), 7) def test_no_arrows(self): with self.assertRaises(ValueError): DimensionLine([(0, 0, 0), (5, 0, 0)], draft=metric, arrows=(False, False)) class ExtensionLineTestCase(unittest.TestCase): def test_min_x(self): shape, outer, inner = create_test_sketch() e_line = ExtensionLine( outer.edges().sort_by(Axis.X)[0], offset=10, draft=metric ) bbox = e_line.bounding_box() self.assertAlmostEqual(bbox.size.Y, 40 + metric.line_width, 5) self.assertAlmostEqual(bbox.size.X, 10, 5) with self.assertRaises(ValueError): ExtensionLine(outer.edges().sort_by(Axis.X)[0], offset=0, draft=metric) def test_builder_mode(self): shape, outer, inner = create_test_sketch() with BuildSketch() as test: add(shape) e_line = ExtensionLine( outer.edges().sort_by(Axis.Y)[0], offset=10, draft=metric ) bbox = e_line.bounding_box() self.assertAlmostEqual(bbox.size.X, 30 + metric.line_width, 5) self.assertAlmostEqual(bbox.size.Y, 10, 5) def test_not_implemented(self): shape, outer, inner = create_test_sketch() with self.assertRaises(NotImplementedError): ExtensionLine( outer.edges().sort_by(Axis.Y)[0], offset=10, project_line=(1, 0, 0), draft=metric, ) @pytest.mark.parametrize("design_date", [date(2023, 9, 17), None]) def test_basic_drawing(design_date): drawing = TechnicalDrawing(design_date=design_date, sheet_number=1) bbox = drawing.bounding_box() assert bbox.size.X > 280 assert bbox.size.Y > 195 assert len(drawing.faces()) > 110 if __name__ == "__main__": unittest.main()