fix(task-2): COM block_explode carries the ATTRIB frame and style onto the TEXT
The BURST path on the live engine read TextString/InsertionPoint/Height/ Rotation/Layer/Invisible and nothing else, so the +Z TEXT that AddText builds got the ATTRIB's OCS Rotation with no Normal: a mirrored reference's tag (Normal (0,0,-1), the same premise the ezdxf regression pins) came back reading theta instead of 180deg-theta, un-mirrored, on the wrong side of its anchor at theta=0, with ok: True. Style members (StyleName, ObliqueAngle, ScaleFactor, Backward/UpsideDown, Alignment + TextAlignmentPoint, Color) were dropped the same way. _capture_attrib_text_members reads the ActiveX twins of the headless _ATTRIB_TO_TEXT carry, guarded per member and never written as a guess; a +Z Normal is not carried and a left-aligned ATTRIB drops its TextAlignmentPoint (ActiveX refuses it). Writes go Normal/Thickness/ style -> Rotation -> InsertionPoint re-asserted in WCS when the frame changed -> Alignment -> TextAlignmentPoint -> Layer -> Color -> Visible. Two fake-surface tests: the mirrored/styled ATTRIB pins every write and the Normal-before-Rotation and Normal-before-InsertionPoint order; the bare ATTRIB pins that absent members are skipped. Both fail on the previous commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5
parent
13851a3d20
commit
415de73137
@@ -109,6 +109,7 @@ class _FakeSpace:
|
||||
self.calls: list[tuple[str, tuple]] = []
|
||||
self.attributes = attributes or {}
|
||||
self.inserted: list[_FakeObject] = []
|
||||
self.texts: list[_FakeObject] = []
|
||||
self.Name = name
|
||||
self.IsLayout = True
|
||||
|
||||
@@ -126,7 +127,9 @@ class _FakeSpace:
|
||||
|
||||
def AddText(self, text, point, height):
|
||||
self.calls.append(("AddText", (text, tuple(point.value), height)))
|
||||
return _FakeObject("AcDbText", f"T{len(self.calls)}")
|
||||
obj = _FakeObject("AcDbText", f"T{len(self.calls)}")
|
||||
self.texts.append(obj)
|
||||
return obj
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -406,6 +409,121 @@ async def test_com_explode_refuses_a_nested_reference_before_explode(com):
|
||||
assert dispatched == [] and space.calls == [] and ref.deleted is False
|
||||
|
||||
|
||||
async def test_com_explode_carries_the_attrib_frame_and_style_onto_the_text(com):
|
||||
"""The COM twin of the mirrored-reference test. ``AddText`` builds a +Z
|
||||
TEXT; an ATTRIB reflected to ``Normal (0, 0, -1)`` has an OCS ``Rotation``
|
||||
and mirrored glyphs, so the TEXT must take the frame *before* the angle
|
||||
and have its WCS anchor re-asserted after the frame changes it. The
|
||||
style members ezdxf carries (style, oblique, width, generation flags,
|
||||
alignment + alignment point, colour) ride along the same way."""
|
||||
backend, document, space = com
|
||||
attrib = _FakeObject(
|
||||
"AcDbAttribute",
|
||||
"A1",
|
||||
TagString="TAG",
|
||||
TextString="P-101",
|
||||
InsertionPoint=(-25.0, 13.0, 0.0),
|
||||
Height=2.5,
|
||||
Rotation=0.5236,
|
||||
Layer="PID-TAG",
|
||||
Invisible=False,
|
||||
Normal=(0.0, 0.0, -1.0),
|
||||
Thickness=0.7,
|
||||
StyleName="ISO",
|
||||
ObliqueAngle=0.2618,
|
||||
ScaleFactor=0.8,
|
||||
Backward=True,
|
||||
UpsideDown=False,
|
||||
Alignment=4,
|
||||
TextAlignmentPoint=(-20.0, 13.0, 0.0),
|
||||
Color=3,
|
||||
)
|
||||
ref = _FakeObject(
|
||||
"AcDbBlockReference",
|
||||
"6D",
|
||||
Name="TB",
|
||||
OwnerID=1,
|
||||
GetAttributes=lambda: (attrib,),
|
||||
Explode=lambda: (),
|
||||
)
|
||||
document.objects["6D"] = ref
|
||||
|
||||
result = await backend.block_explode("6D")
|
||||
|
||||
assert result["ok"] is True and result["attribute_texts"] == ["T1"]
|
||||
assert space.calls == [("AddText", ("P-101", (-25.0, 13.0, 0.0), 2.5))]
|
||||
text = space.texts[0]
|
||||
# Point writes go through ``_apoint`` (a VARIANT); compare their payload.
|
||||
writes = [
|
||||
(name, tuple(value.value) if hasattr(value, "value") else value)
|
||||
for name, value in text.writes
|
||||
]
|
||||
names = [name for name, _ in writes]
|
||||
assert ("Normal", (0.0, 0.0, -1.0)) in writes, "the ATTRIB's frame is carried"
|
||||
assert names.index("Normal") < names.index("Rotation"), (
|
||||
"Rotation is the OCS angle: it only means the same thing inside the same frame"
|
||||
)
|
||||
assert names.index("Normal") < names.index("InsertionPoint"), (
|
||||
"the WCS anchor is re-asserted after the frame change moves the OCS origin"
|
||||
)
|
||||
assert names.index("Alignment") < names.index("TextAlignmentPoint"), (
|
||||
"ActiveX rejects an alignment point on a left-aligned text"
|
||||
)
|
||||
assert dict(writes) == {
|
||||
"Normal": (0.0, 0.0, -1.0),
|
||||
"Thickness": 0.7,
|
||||
"StyleName": "ISO",
|
||||
"ObliqueAngle": 0.2618,
|
||||
"ScaleFactor": 0.8,
|
||||
"Backward": True,
|
||||
"UpsideDown": False,
|
||||
"Rotation": 0.5236,
|
||||
"InsertionPoint": (-25.0, 13.0, 0.0),
|
||||
"Alignment": 4,
|
||||
"TextAlignmentPoint": (-20.0, 13.0, 0.0),
|
||||
"Layer": "PID-TAG",
|
||||
"Color": 3,
|
||||
}
|
||||
|
||||
|
||||
async def test_com_explode_skips_frame_members_the_attrib_does_not_expose(com):
|
||||
"""A bare ATTRIB (the shape the earlier fakes use) still explodes: an
|
||||
absent optional member is skipped, never written as a guess, and a
|
||||
left-aligned ATTRIB never gets a TextAlignmentPoint."""
|
||||
backend, document, space = com
|
||||
attrib = _FakeObject(
|
||||
"AcDbAttribute",
|
||||
"A1",
|
||||
TagString="TAG",
|
||||
TextString="P-101",
|
||||
InsertionPoint=(7.0, 15.2, 0.0),
|
||||
Height=5.0,
|
||||
Rotation=0.0,
|
||||
Layer="0",
|
||||
Invisible=False,
|
||||
Alignment=0,
|
||||
TextAlignmentPoint=(99.0, 99.0, 0.0),
|
||||
)
|
||||
ref = _FakeObject(
|
||||
"AcDbBlockReference",
|
||||
"7E",
|
||||
Name="TB",
|
||||
OwnerID=1,
|
||||
GetAttributes=lambda: (attrib,),
|
||||
Explode=lambda: (),
|
||||
)
|
||||
document.objects["7E"] = ref
|
||||
|
||||
result = await backend.block_explode("7E")
|
||||
|
||||
assert result["ok"] is True
|
||||
names = [name for name, _ in space.texts[0].writes]
|
||||
assert "Normal" not in names and "StyleName" not in names
|
||||
assert "InsertionPoint" not in names, "a +Z text keeps the anchor AddText was given"
|
||||
assert "TextAlignmentPoint" not in names, "left alignment has no alignment point"
|
||||
assert names == ["Rotation", "Alignment", "Layer"]
|
||||
|
||||
|
||||
async def test_explode_of_a_mirrored_reference_keeps_the_tag_where_the_attrib_was(backend):
|
||||
"""An ATTRIB is an OCS entity. Mirroring reflects it to extrusion
|
||||
``(0, 0, -1)`` while the INSERT keeps +Z with ``yscale=-1``; the TEXT
|
||||
|
||||
Reference in New Issue
Block a user