1# Copyright (c) 2018-2020 Manfred Moitzi 2# License: MIT License 3from typing import cast 4import pytest 5import math 6from unittest.mock import MagicMock 7 8import ezdxf 9 10from ezdxf.audit import Auditor 11from ezdxf.lldxf import const 12from ezdxf.lldxf.tagwriter import TagCollector 13from ezdxf.lldxf.tags import Tags 14from ezdxf.entities.mline import MLineVertex, MLine, MLineStyle 15from ezdxf.math import Matrix44, Vec3 16 17 18# noinspection PyUnresolvedReferences 19class TestMLine: 20 @pytest.fixture(scope='class') 21 def msp(self): 22 return ezdxf.new().modelspace() 23 24 @pytest.fixture 25 def mline_mock_update_geometry(self): 26 mline = MLine() 27 mline.update_geometry = MagicMock() 28 return mline 29 30 def test_unbounded_mline(self): 31 mline = MLine() 32 assert mline.dxf.style_handle is None 33 assert mline.dxf.style_name == 'Standard' 34 assert mline.style is None 35 36 def test_generic_mline(self, msp): 37 mline = msp.add_mline() 38 assert mline.dxftype() == 'MLINE' 39 assert mline.dxf.style_name == 'Standard' 40 assert mline.dxf.count == 0 41 assert mline.dxf.start_location == (0, 0, 0) 42 43 def test_set_justification(self, mline_mock_update_geometry): 44 mline = mline_mock_update_geometry 45 mline.set_justification(mline.BOTTOM) 46 assert mline.dxf.justification == mline.BOTTOM 47 mline.update_geometry.assert_called_once() 48 49 def test_set_scale_factor(self, mline_mock_update_geometry): 50 mline = mline_mock_update_geometry 51 mline.set_scale_factor(17) 52 assert mline.dxf.scale_factor == 17 53 mline.update_geometry.assert_called_once() 54 55 def test_close_state(self, mline_mock_update_geometry): 56 mline = mline_mock_update_geometry 57 assert mline.is_closed is False 58 mline.close(True) 59 assert mline.is_closed is True 60 mline.update_geometry.assert_called_once() 61 62 def test_point_count_management(self): 63 mline = MLine() 64 mline.load_vertices(Tags.from_text(VTX_2)) 65 assert len(mline.vertices) == 2 66 assert len(mline) == 2 67 assert mline.dxf.count == 2, 'should be a callback to __len__()' 68 69 def test_add_first_vertex(self): 70 mline = MLine() 71 mline.extend([(0, 0, 0)]) 72 assert mline.start_location() == (0, 0, 0) 73 assert len(mline) == 1 74 75 def test_add_two_vertices(self, msp): 76 # MLineStyle is required 77 mline = msp.add_mline([(0, 0), (10, 0)]) 78 assert mline.start_location() == (0, 0, 0) 79 assert len(mline) == 2 80 assert mline.vertices[0].line_direction.isclose((1, 0)) 81 assert mline.vertices[0].miter_direction.isclose((0, 1)) 82 assert mline.vertices[1].line_direction.isclose((1, 0)), \ 83 'continue last line segment' 84 assert mline.vertices[1].miter_direction.isclose((0, 1)) 85 86 def test_x_rotation(self, msp): 87 mline = msp.add_mline([(0, 5), (10, 5)]) 88 m = Matrix44.x_rotate(math.pi / 2) 89 mline.transform(m) 90 assert mline.start_location().isclose((0, 0, 5)) 91 assert mline.dxf.extrusion.isclose((0, -1, 0)) 92 assert mline.dxf.scale_factor == 1 93 94 def test_translate(self, msp): 95 mline = msp.add_mline([(0, 5), (10, 5)]) 96 m = Matrix44.translate(1, 1, 1) 97 mline.transform(m) 98 assert mline.start_location().isclose((1, 6, 1)) 99 assert mline.dxf.scale_factor == 1 100 101 def test_uniform_scale(self, msp): 102 mline = msp.add_mline([(0, 5), (10, 5)]) 103 m = Matrix44.scale(2, 2, 2) 104 mline.transform(m) 105 assert mline.start_location().isclose((0, 10, 0)) 106 assert mline.dxf.scale_factor == 2 107 108 def test_non_uniform_scale(self, msp): 109 mline = msp.add_mline([(1, 2, 3), (3, 4, 3)]) 110 m = Matrix44.scale(2, 1, 3) 111 mline.transform(m) 112 assert mline.start_location().isclose((2, 2, 9)) 113 assert mline.dxf.scale_factor == 1, 'ignore non-uniform scaling' 114 115 116class TestMLineStyle: 117 @pytest.fixture(scope='class') 118 def doc(self): 119 return ezdxf.new() 120 121 def test_standard_mline_style(self, doc): 122 mline_style = cast('MLineStyle', doc.mline_styles.get('Standard')) 123 assert mline_style.dxftype() == 'MLINESTYLE' 124 125 elements = mline_style.elements 126 assert len(elements) == 2 127 assert elements[0].offset == 0.5 128 assert elements[0].color == 256 129 assert elements[0].linetype == 'BYLAYER' 130 assert elements[1].offset == -0.5 131 assert elements[1].color == 256 132 assert elements[1].linetype == 'BYLAYER' 133 134 def test_set_defined_style(self, doc): 135 style = doc.mline_styles.new('DefinedStyle') 136 mline = doc.modelspace().add_mline() 137 mline.set_style('DefinedStyle') 138 assert mline.dxf.style_name == 'DefinedStyle' 139 assert mline.dxf.style_handle == style.dxf.handle 140 141 def test_set_undefined_style(self, doc): 142 mline = doc.modelspace().add_mline() 143 with pytest.raises(const.DXFValueError): 144 mline.set_style('UndefinedStyle') 145 146 def test_ordered_indices(self): 147 style = MLineStyle() 148 style.elements.append(5) # top order 149 style.elements.append(-5) # bottom border 150 style.elements.append(0) 151 style.elements.append(1) 152 assert style.ordered_indices() == [1, 2, 3, 0] 153 154 def test_invalid_element_count(self, doc): 155 style = doc.mline_styles.new('InvalidMLineStyle') 156 assert len(style.elements) == 0 157 auditor = Auditor(doc) 158 style.audit(auditor) 159 assert auditor.has_errors is True, 'invalid element count' 160 161 162class TestMLineVertex: 163 def test_load_tags(self): 164 tags = Tags.from_text(VTX_1) 165 vtx = MLineVertex.load(tags) 166 assert isinstance(vtx.location, Vec3) 167 assert vtx.location == (0, 0, 0) 168 assert vtx.line_direction == (1, 0, 0) 169 assert vtx.miter_direction == (0, 1, 0) 170 assert len(vtx.line_params) == 3 171 p1, p2, p3 = vtx.line_params 172 assert p1 == (0.5, 0.0) 173 assert p2 == (0.0, 0.0) 174 assert p3 == (-0.5, 0.0) 175 assert len(vtx.fill_params) == 3 176 assert sum(len(p) for p in vtx.fill_params) == 0 177 178 def test_new(self): 179 vtx = MLineVertex.new( 180 (1, 1), (1, 0), (0, 1), 181 [(0.5, 0), (0, 0)], 182 [tuple(), tuple()], 183 ) 184 assert vtx.location == (1, 1, 0) 185 assert vtx.line_direction == (1, 0, 0) 186 assert vtx.miter_direction == (0, 1, 0) 187 assert len(vtx.line_params) == 2 188 p1, p2 = vtx.line_params 189 assert p1 == (0.5, 0) 190 assert p2 == (0, 0) 191 assert len(vtx.fill_params) == 2 192 assert sum(len(p) for p in vtx.fill_params) == 0 193 194 def test_export_dxf(self): 195 t = Tags.from_text(VTX_1) 196 vtx = MLineVertex.load(t) 197 collector = TagCollector() 198 vtx.export_dxf(collector) 199 200 tags = Tags(collector.tags) 201 assert tags[0] == (11, vtx.location[0]) 202 assert tags[1] == (21, vtx.location[1]) 203 assert tags[2] == (31, vtx.location[2]) 204 205 assert tags[3] == (12, vtx.line_direction[0]) 206 assert tags[4] == (22, vtx.line_direction[1]) 207 assert tags[5] == (32, vtx.line_direction[2]) 208 209 assert tags[6] == (13, vtx.miter_direction[0]) 210 assert tags[7] == (23, vtx.miter_direction[1]) 211 assert tags[8] == (33, vtx.miter_direction[2]) 212 213 # line- and fill parameters 214 assert tags[9:] == t[3:] 215 216 217class TestMLineAudit: 218 @pytest.fixture(scope='class') 219 def doc(self): 220 d = ezdxf.new() 221 new_style = d.mline_styles.new('NewStyle1') 222 new_style.elements.append(0.5) 223 new_style.elements.append(0) 224 return d 225 226 @pytest.fixture(scope='class') 227 def msp(self, doc): 228 return doc.modelspace() 229 230 @pytest.fixture 231 def auditor(self, doc): 232 return Auditor(doc) 233 234 @pytest.fixture 235 def mline1(self, msp): 236 return msp.add_mline([(0, 0), (1, 1)]) 237 238 def test_valid_mline(self, mline1, auditor): 239 mline1.audit(auditor) 240 assert auditor.has_errors is False 241 assert auditor.has_fixes is False 242 243 def test_fix_invalid_style_name(self, mline1, auditor): 244 mline1.dxf.style_name = 'test' 245 mline1.audit(auditor) 246 assert mline1.dxf.style_name == 'Standard' 247 assert auditor.has_fixes is False, 'silent fix' 248 249 def test_fix_invalid_style_handle(self, mline1, auditor): 250 mline1.dxf.style_name = 'test' 251 mline1.dxf.style_handle = '0' 252 mline1.audit(auditor) 253 assert mline1.dxf.style_name == 'Standard' 254 assert mline1.dxf.style_handle == auditor.doc.mline_styles[ 255 'Standard'].dxf.handle 256 assert auditor.has_fixes is True 257 258 def test_fix_invalid_style_handle_by_name(self, mline1, doc, auditor): 259 new_style = doc.mline_styles.get('NewStyle1') 260 mline1.dxf.style_name = 'NewStyle1' 261 mline1.dxf.style_handle = '0' 262 mline1.audit(auditor) 263 assert mline1.dxf.style_name == new_style.dxf.name 264 assert mline1.dxf.style_handle == new_style.dxf.handle 265 assert auditor.has_fixes is True 266 267 def test_fix_invalid_line_direction(self, mline1, auditor): 268 mline1.vertices[0].line_direction = (0, 0, 0) 269 mline1.audit(auditor) 270 assert auditor.has_fixes is True 271 272 def test_fix_invalid_miter_direction(self, mline1, auditor): 273 mline1.vertices[0].miter_direction = (0, 0, 0) 274 mline1.audit(auditor) 275 assert auditor.has_fixes is True 276 277 def test_fix_invalid_line_parameters(self, mline1, auditor): 278 mline1.vertices[0].line_params = [] 279 mline1.audit(auditor) 280 assert auditor.has_fixes is True 281 282 283VTX_1 = """11 2840.0 28521 2860.0 28731 2880.0 28912 2901.0 29122 2920.0 29332 2940.0 29513 2960.0 29723 2981.0 29933 3000.0 30174 3022 30341 3040.5 30541 3060.0 30775 3080 30974 3102 31141 3120.0 31341 3140.0 31575 3160 31774 3182 31941 320-0.5 32141 3220.0 32375 3240 325""" 326 327VTX_2 = """11 3280.0 32921 3300.0 33131 3320.0 33311 33410.0 33521 3360.0 33731 3380.0 339""" 340