refactor: excel parse
This commit is contained in:
@@ -0,0 +1,576 @@
|
||||
# Copyright (c) 2021-2022, Manfred Moitzi
|
||||
# License: MIT License
|
||||
# Debugging tools to analyze DXF entities.
|
||||
from __future__ import annotations
|
||||
from typing import Iterable, Sequence, Optional
|
||||
import textwrap
|
||||
|
||||
import ezdxf
|
||||
from ezdxf import colors
|
||||
from ezdxf.math import Vec2
|
||||
from ezdxf.lldxf import const
|
||||
from ezdxf.enums import TextEntityAlignment
|
||||
from ezdxf.tools.debug import print_bitmask
|
||||
from ezdxf.render.mleader import MLeaderStyleOverride, OVERRIDE_FLAG
|
||||
from ezdxf.entities import (
|
||||
EdgePath,
|
||||
PolylinePath,
|
||||
LineEdge,
|
||||
ArcEdge,
|
||||
EllipseEdge,
|
||||
SplineEdge,
|
||||
mleader,
|
||||
)
|
||||
from ezdxf.entities.polygon import DXFPolygon
|
||||
|
||||
EDGE_START_MARKER = "EDGE_START_MARKER"
|
||||
EDGE_END_MARKER = "EDGE_END_MARKER"
|
||||
HATCH_LAYER = "HATCH"
|
||||
POLYLINE_LAYER = "POLYLINE_MARKER"
|
||||
LINE_LAYER = "LINE_MARKER"
|
||||
ARC_LAYER = "ARC_MARKER"
|
||||
ELLIPSE_LAYER = "ELLIPSE_MARKER"
|
||||
SPLINE_LAYER = "SPLINE_MARKER"
|
||||
|
||||
|
||||
class HatchAnalyzer:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
marker_size: float = 1.0,
|
||||
angle: float = 45,
|
||||
):
|
||||
self.marker_size = marker_size
|
||||
self.angle = angle
|
||||
self.doc = ezdxf.new()
|
||||
self.msp = self.doc.modelspace()
|
||||
self.init_layers()
|
||||
self.init_markers()
|
||||
|
||||
def init_layers(self):
|
||||
self.doc.layers.add(POLYLINE_LAYER, color=colors.YELLOW)
|
||||
self.doc.layers.add(LINE_LAYER, color=colors.RED)
|
||||
self.doc.layers.add(ARC_LAYER, color=colors.GREEN)
|
||||
self.doc.layers.add(ELLIPSE_LAYER, color=colors.MAGENTA)
|
||||
self.doc.layers.add(SPLINE_LAYER, color=colors.CYAN)
|
||||
self.doc.layers.add(HATCH_LAYER)
|
||||
|
||||
def init_markers(self):
|
||||
blk = self.doc.blocks.new(EDGE_START_MARKER)
|
||||
attribs = {"layer": "0"} # from INSERT
|
||||
radius = self.marker_size / 2.0
|
||||
height = radius
|
||||
|
||||
# start marker: 0-- name
|
||||
blk.add_circle(
|
||||
center=(0, 0),
|
||||
radius=radius,
|
||||
dxfattribs=attribs,
|
||||
)
|
||||
text_start = radius * 4
|
||||
blk.add_line(
|
||||
start=(radius, 0),
|
||||
end=(text_start - radius / 2.0, 0),
|
||||
dxfattribs=attribs,
|
||||
)
|
||||
text = blk.add_attdef(
|
||||
tag="NAME",
|
||||
dxfattribs=attribs,
|
||||
)
|
||||
text.dxf.height = height
|
||||
text.set_placement(
|
||||
(text_start, 0), align=TextEntityAlignment.MIDDLE_LEFT
|
||||
)
|
||||
|
||||
# end marker: name --X
|
||||
blk = self.doc.blocks.new(EDGE_END_MARKER)
|
||||
attribs = {"layer": "0"} # from INSERT
|
||||
blk.add_line(
|
||||
start=(-radius, -radius),
|
||||
end=(radius, radius),
|
||||
dxfattribs=attribs,
|
||||
)
|
||||
blk.add_line(
|
||||
start=(-radius, radius),
|
||||
end=(radius, -radius),
|
||||
dxfattribs=attribs,
|
||||
)
|
||||
text_start = -radius * 4
|
||||
blk.add_line(
|
||||
start=(-radius, 0),
|
||||
end=(text_start + radius / 2.0, 0),
|
||||
dxfattribs=attribs,
|
||||
)
|
||||
text = blk.add_attdef(
|
||||
tag="NAME",
|
||||
dxfattribs=attribs,
|
||||
)
|
||||
text.dxf.height = height
|
||||
text.set_placement(
|
||||
(text_start, 0), align=TextEntityAlignment.MIDDLE_RIGHT
|
||||
)
|
||||
|
||||
def export(self, name: str) -> None:
|
||||
self.doc.saveas(name)
|
||||
|
||||
def add_hatch(self, hatch: DXFPolygon) -> None:
|
||||
hatch.dxf.discard("extrusion")
|
||||
hatch.dxf.layer = HATCH_LAYER
|
||||
self.msp.add_foreign_entity(hatch)
|
||||
|
||||
def add_boundary_markers(self, hatch: DXFPolygon) -> None:
|
||||
hatch.dxf.discard("extrusion")
|
||||
path_num: int = 0
|
||||
|
||||
for p in hatch.paths:
|
||||
path_num += 1
|
||||
if isinstance(p, PolylinePath):
|
||||
self.add_polyline_markers(p, path_num)
|
||||
elif isinstance(p, EdgePath):
|
||||
self.add_edge_markers(p, path_num)
|
||||
else:
|
||||
raise TypeError(f"unknown boundary path type: {type(p)}")
|
||||
|
||||
def add_start_marker(self, location: Vec2, name: str, layer: str) -> None:
|
||||
self.add_marker(EDGE_START_MARKER, location, name, layer)
|
||||
|
||||
def add_end_marker(self, location: Vec2, name: str, layer: str) -> None:
|
||||
self.add_marker(EDGE_END_MARKER, location, name, layer)
|
||||
|
||||
def add_marker(
|
||||
self, blk_name: str, location: Vec2, name: str, layer: str
|
||||
) -> None:
|
||||
blkref = self.msp.add_blockref(
|
||||
name=blk_name,
|
||||
insert=location,
|
||||
dxfattribs={
|
||||
"layer": layer,
|
||||
"rotation": self.angle,
|
||||
},
|
||||
)
|
||||
blkref.add_auto_attribs({"NAME": name})
|
||||
|
||||
def add_polyline_markers(self, p: PolylinePath, num: int) -> None:
|
||||
self.add_start_marker(
|
||||
Vec2(p.vertices[0]), f"Poly-S({num})", POLYLINE_LAYER
|
||||
)
|
||||
self.add_end_marker(
|
||||
Vec2(p.vertices[0]), f"Poly-E({num})", POLYLINE_LAYER
|
||||
)
|
||||
|
||||
def add_edge_markers(self, p: EdgePath, num: int) -> None:
|
||||
edge_num: int = 0
|
||||
for edge in p.edges:
|
||||
edge_num += 1
|
||||
name = f"({num}.{edge_num})"
|
||||
if isinstance(
|
||||
edge,
|
||||
LineEdge,
|
||||
):
|
||||
self.add_line_edge_markers(edge, name)
|
||||
elif isinstance(edge, ArcEdge):
|
||||
self.add_arc_edge_markers(edge, name)
|
||||
elif isinstance(edge, EllipseEdge):
|
||||
self.add_ellipse_edge_markers(edge, name)
|
||||
elif isinstance(edge, SplineEdge):
|
||||
self.add_spline_edge_markers(edge, name)
|
||||
else:
|
||||
raise TypeError(f"unknown edge type: {type(edge)}")
|
||||
|
||||
def add_line_edge_markers(self, line: LineEdge, name: str) -> None:
|
||||
self.add_start_marker(line.start, "Line-S" + name, LINE_LAYER)
|
||||
self.add_end_marker(line.end, "Line-E" + name, LINE_LAYER)
|
||||
|
||||
def add_arc_edge_markers(self, arc: ArcEdge, name: str) -> None:
|
||||
self.add_start_marker(arc.start_point, "Arc-S" + name, ARC_LAYER)
|
||||
self.add_end_marker(arc.end_point, "Arc-E" + name, ARC_LAYER)
|
||||
|
||||
def add_ellipse_edge_markers(self, ellipse: EllipseEdge, name: str) -> None:
|
||||
self.add_start_marker(
|
||||
ellipse.start_point, "Ellipse-S" + name, ELLIPSE_LAYER
|
||||
)
|
||||
self.add_end_marker(
|
||||
ellipse.end_point, "Ellipse-E" + name, ELLIPSE_LAYER
|
||||
)
|
||||
|
||||
def add_spline_edge_markers(self, spline: SplineEdge, name: str) -> None:
|
||||
if len(spline.control_points):
|
||||
# Assuming a clamped B-spline, because this is the only practical
|
||||
# usable B-spline for edges.
|
||||
self.add_start_marker(
|
||||
spline.start_point, "SplineS" + name, SPLINE_LAYER
|
||||
)
|
||||
self.add_end_marker(
|
||||
spline.end_point, "SplineE" + name, SPLINE_LAYER
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def report(hatch: DXFPolygon) -> list[str]:
|
||||
return hatch_report(hatch)
|
||||
|
||||
@staticmethod
|
||||
def print_report(hatch: DXFPolygon) -> None:
|
||||
print("\n".join(hatch_report(hatch)))
|
||||
|
||||
|
||||
def hatch_report(hatch: DXFPolygon) -> list[str]:
|
||||
dxf = hatch.dxf
|
||||
style = const.ISLAND_DETECTION[dxf.hatch_style]
|
||||
pattern_type = const.HATCH_PATTERN_TYPE[dxf.pattern_type]
|
||||
text = [
|
||||
f"{str(hatch)}",
|
||||
f" solid fill: {bool(dxf.solid_fill)}",
|
||||
f" pattern type: {pattern_type}",
|
||||
f" pattern name: {dxf.pattern_name}",
|
||||
f" associative: {bool(dxf.associative)}",
|
||||
f" island detection: {style}",
|
||||
f" has pattern data: {hatch.pattern is not None}",
|
||||
f" has gradient data: {hatch.gradient is not None}",
|
||||
f" seed value count: {len(hatch.seeds)}",
|
||||
f" boundary path count: {len(hatch.paths)}",
|
||||
]
|
||||
num = 0
|
||||
for path in hatch.paths:
|
||||
num += 1
|
||||
if isinstance(path, PolylinePath):
|
||||
text.extend(polyline_path_report(path, num))
|
||||
elif isinstance(path, EdgePath):
|
||||
text.extend(edge_path_report(path, num))
|
||||
return text
|
||||
|
||||
|
||||
def polyline_path_report(p: PolylinePath, num: int) -> list[str]:
|
||||
path_type = ", ".join(const.boundary_path_flag_names(p.path_type_flags))
|
||||
return [
|
||||
f"{num}. Polyline Path, vertex count: {len(p.vertices)}",
|
||||
f" path type: {path_type}",
|
||||
]
|
||||
|
||||
|
||||
def edge_path_report(p: EdgePath, num: int) -> list[str]:
|
||||
closed = False
|
||||
connected = False
|
||||
path_type = ", ".join(const.boundary_path_flag_names(p.path_type_flags))
|
||||
edges = p.edges
|
||||
if len(edges):
|
||||
closed = edges[0].start_point.isclose(edges[-1].end_point)
|
||||
connected = all(
|
||||
e1.end_point.isclose(e2.start_point)
|
||||
for e1, e2 in zip(edges, edges[1:])
|
||||
)
|
||||
|
||||
return [
|
||||
f"{num}. Edge Path, edge count: {len(p.edges)}",
|
||||
f" path type: {path_type}",
|
||||
f" continuously connected edges: {connected}",
|
||||
f" closed edge loop: {closed}",
|
||||
]
|
||||
|
||||
|
||||
MULTILEADER = "MULTILEADER_MARKER"
|
||||
POINT_MARKER = "POINT_MARKER"
|
||||
|
||||
|
||||
class MultileaderAnalyzer:
|
||||
"""Multileader can not be added as foreign entity to a new document.
|
||||
Annotations have to be added to the source document.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
multileader: mleader.MultiLeader,
|
||||
*,
|
||||
marker_size: float = 1.0,
|
||||
report_width: int = 79,
|
||||
):
|
||||
self.marker_size = marker_size
|
||||
self.report_width = report_width
|
||||
self.multileader = multileader
|
||||
assert self.multileader.doc is not None, "valid DXF document required"
|
||||
self.doc = self.multileader.doc
|
||||
self.msp = self.doc.modelspace()
|
||||
self.init_layers()
|
||||
self.init_markers()
|
||||
|
||||
def init_layers(self):
|
||||
if self.doc.layers.has_entry(MULTILEADER):
|
||||
return
|
||||
self.doc.layers.add(MULTILEADER, color=colors.RED)
|
||||
|
||||
def init_markers(self):
|
||||
if POINT_MARKER not in self.doc.blocks:
|
||||
blk = self.doc.blocks.new(POINT_MARKER)
|
||||
attribs = {"layer": "0"} # from INSERT
|
||||
size = self.marker_size
|
||||
size_2 = size / 2.0
|
||||
radius = size / 4.0
|
||||
blk.add_circle(
|
||||
center=(0, 0),
|
||||
radius=radius,
|
||||
dxfattribs=attribs,
|
||||
)
|
||||
blk.add_line((-size_2, 0), (size_2, 0), dxfattribs=attribs)
|
||||
blk.add_line((0, -size_2), (0, size_2), dxfattribs=attribs)
|
||||
|
||||
@property
|
||||
def context(self) -> mleader.MLeaderContext:
|
||||
return self.multileader.context
|
||||
|
||||
@property
|
||||
def mleaderstyle(self) -> Optional[mleader.MLeaderStyle]:
|
||||
handle = self.multileader.dxf.get("style_handle")
|
||||
return self.doc.entitydb.get(handle) # type: ignore
|
||||
|
||||
def divider_line(self, symbol="-") -> str:
|
||||
return symbol * self.report_width
|
||||
|
||||
def shorten_lines(self, lines: Iterable[str]) -> list[str]:
|
||||
return [
|
||||
textwrap.shorten(line, width=self.report_width) for line in lines
|
||||
]
|
||||
|
||||
def report(self) -> list[str]:
|
||||
report = [
|
||||
str(self.multileader),
|
||||
self.divider_line(),
|
||||
"Existing DXF attributes:",
|
||||
self.divider_line(),
|
||||
]
|
||||
report.extend(self.multileader_attributes())
|
||||
report.extend(self.context_attributes())
|
||||
if self.context.mtext is not None:
|
||||
report.extend(self.mtext_attributes())
|
||||
if self.context.block is not None:
|
||||
report.extend(self.block_attributes())
|
||||
if self.multileader.block_attribs:
|
||||
report.extend(self.block_reference_attribs())
|
||||
if self.multileader.context.leaders:
|
||||
report.extend(self.leader_attributes())
|
||||
if self.multileader.arrow_heads:
|
||||
report.extend(self.arrow_heads())
|
||||
return self.shorten_lines(report)
|
||||
|
||||
def print_report(self) -> None:
|
||||
width = self.report_width
|
||||
print()
|
||||
print("=" * width)
|
||||
print("\n".join(self.report()))
|
||||
print("=" * width)
|
||||
|
||||
def print_overridden_properties(self):
|
||||
print("\n".join(self.overridden_attributes()))
|
||||
|
||||
def overridden_attributes(self) -> list[str]:
|
||||
multileader = self.multileader
|
||||
style = self.mleaderstyle
|
||||
if style is not None:
|
||||
report = [
|
||||
self.divider_line(),
|
||||
f"Override attributes of {str(style)}: '{style.dxf.name}'",
|
||||
self.divider_line(),
|
||||
]
|
||||
|
||||
override = MLeaderStyleOverride(style, multileader)
|
||||
for name in OVERRIDE_FLAG.keys():
|
||||
if override.is_overridden(name):
|
||||
report.append(f"{name}: {override.get(name)}")
|
||||
if override.use_mtext_default_content:
|
||||
report.append("use_mtext_default_content: 1")
|
||||
else:
|
||||
handle = self.multileader.dxf.get("style_handle")
|
||||
report = [
|
||||
self.divider_line(),
|
||||
f"MLEADERSTYLE(#{handle}) not found",
|
||||
]
|
||||
return report
|
||||
|
||||
def multileader_attributes(self) -> list[str]:
|
||||
attribs = self.multileader.dxf.all_existing_dxf_attribs()
|
||||
keys = sorted(attribs.keys())
|
||||
return [f"{key}: {attribs[key]}" for key in keys]
|
||||
|
||||
def print_override_state(self):
|
||||
flags = self.multileader.dxf.property_override_flags
|
||||
print(f"\nproperty_override_flags:")
|
||||
print(f"dec: {flags}")
|
||||
print(f"hex: {hex(flags)}")
|
||||
print_bitmask(flags)
|
||||
|
||||
def print_context_attributes(self):
|
||||
print("\n".join(self.context_attributes()))
|
||||
|
||||
def context_attributes(self) -> list[str]:
|
||||
context = self.context
|
||||
if context is None:
|
||||
return []
|
||||
report = [
|
||||
self.divider_line(),
|
||||
"CONTEXT object attributes:",
|
||||
self.divider_line(),
|
||||
f"has MTEXT content: {yes_or_no(context.mtext)}",
|
||||
f"has BLOCK content: {yes_or_no(context.block)}",
|
||||
self.divider_line(),
|
||||
]
|
||||
keys = [
|
||||
key
|
||||
for key in context.__dict__.keys()
|
||||
if key not in ("mtext", "block", "leaders")
|
||||
]
|
||||
attributes = [f"{name}: {getattr(context, name)}" for name in keys]
|
||||
attributes.sort()
|
||||
report.extend(attributes)
|
||||
return self.shorten_lines(report)
|
||||
|
||||
def print_mtext_attributes(self):
|
||||
print("\n".join(self.mtext_attributes()))
|
||||
|
||||
def mtext_attributes(self) -> list[str]:
|
||||
report = [
|
||||
self.divider_line(),
|
||||
"MTEXT content attributes:",
|
||||
self.divider_line(),
|
||||
]
|
||||
mtext = self.context.mtext
|
||||
if mtext is not None:
|
||||
report.extend(_content_attributes(mtext))
|
||||
return self.shorten_lines(report)
|
||||
|
||||
def print_block_attributes(self):
|
||||
print("\n".join(self.block_attributes()))
|
||||
|
||||
def block_attributes(self) -> list[str]:
|
||||
report = [
|
||||
self.divider_line(),
|
||||
"BLOCK content attributes:",
|
||||
self.divider_line(),
|
||||
]
|
||||
block = self.context.block
|
||||
if block is not None:
|
||||
report.extend(_content_attributes(block))
|
||||
return self.shorten_lines(report)
|
||||
|
||||
def print_leader_attributes(self):
|
||||
print("\n".join(self.leader_attributes()))
|
||||
|
||||
def leader_attributes(self) -> list[str]:
|
||||
report = []
|
||||
leaders = self.context.leaders
|
||||
if leaders is not None:
|
||||
for index, leader in enumerate(leaders):
|
||||
report.extend(self._leader_attributes(index, leader))
|
||||
return self.shorten_lines(report)
|
||||
|
||||
def _leader_attributes(
|
||||
self, index: int, leader: mleader.LeaderData
|
||||
) -> list[str]:
|
||||
report = [
|
||||
self.divider_line(),
|
||||
f"{index+1}. LEADER attributes:",
|
||||
self.divider_line(),
|
||||
]
|
||||
report.extend(_content_attributes(leader, exclude=("lines", "breaks")))
|
||||
s = ", ".join(map(str, leader.breaks))
|
||||
report.append(f"breaks: [{s}]")
|
||||
if leader.lines:
|
||||
report.extend(self._leader_lines(leader.lines))
|
||||
return report
|
||||
|
||||
def _leader_lines(self, lines) -> list[str]:
|
||||
report = []
|
||||
for num, line in enumerate(lines):
|
||||
report.extend(
|
||||
[
|
||||
self.divider_line(),
|
||||
f"{num + 1}. LEADER LINE attributes:",
|
||||
self.divider_line(),
|
||||
]
|
||||
)
|
||||
for name, value in line.__dict__.items():
|
||||
if name in ("vertices", "breaks"):
|
||||
vstr = ""
|
||||
if value is not None:
|
||||
vstr = ", ".join(map(str, value))
|
||||
vstr = f"[{vstr}]"
|
||||
else:
|
||||
vstr = str(value)
|
||||
report.append(f"{name}: {vstr}")
|
||||
return report
|
||||
|
||||
def print_block_attribs(self):
|
||||
print("\n".join(self.block_reference_attribs()))
|
||||
|
||||
def block_reference_attribs(self) -> list[str]:
|
||||
report = [
|
||||
self.divider_line(),
|
||||
f"BLOCK reference attributes:",
|
||||
self.divider_line(),
|
||||
]
|
||||
for index, attr in enumerate(self.multileader.block_attribs):
|
||||
report.extend(
|
||||
[
|
||||
f"{index+1}. Attributes",
|
||||
self.divider_line(),
|
||||
]
|
||||
)
|
||||
report.append(f"handle: {attr.handle}")
|
||||
report.append(f"index: {attr.index}")
|
||||
report.append(f"width: {attr.width}")
|
||||
report.append(f"text: {attr.text}")
|
||||
return report
|
||||
|
||||
def arrow_heads(self) -> list[str]:
|
||||
report = [
|
||||
self.divider_line(),
|
||||
f"ARROW HEAD attributes:",
|
||||
self.divider_line(),
|
||||
]
|
||||
for index, attr in enumerate(self.multileader.arrow_heads):
|
||||
report.extend(
|
||||
[
|
||||
f"{index+1}. Arrow Head",
|
||||
self.divider_line(),
|
||||
]
|
||||
)
|
||||
report.append(f"handle: {attr.handle}")
|
||||
report.append(f"index: {attr.index}")
|
||||
return report
|
||||
|
||||
def print_mleaderstyle(self):
|
||||
print("\n".join(self.mleaderstyle_attributes()))
|
||||
|
||||
def mleaderstyle_attributes(self) -> list[str]:
|
||||
report = []
|
||||
style = self.mleaderstyle
|
||||
if style is not None:
|
||||
report.extend(
|
||||
[
|
||||
self.divider_line("="),
|
||||
str(style),
|
||||
self.divider_line("="),
|
||||
]
|
||||
)
|
||||
attribs = style.dxf.all_existing_dxf_attribs()
|
||||
keys = sorted(attribs.keys())
|
||||
report.extend([f"{name}: {attribs[name]}" for name in keys])
|
||||
else:
|
||||
handle = self.multileader.dxf.get("style_handle")
|
||||
report.append(f"MLEADERSTYLE(#{handle}) not found")
|
||||
return self.shorten_lines(report)
|
||||
|
||||
|
||||
def _content_attributes(
|
||||
entity, exclude: Optional[Sequence[str]] = None
|
||||
) -> list[str]:
|
||||
exclude = exclude or []
|
||||
if entity is not None:
|
||||
return [
|
||||
f"{name}: {value}"
|
||||
for name, value in entity.__dict__.items()
|
||||
if name not in exclude
|
||||
]
|
||||
return []
|
||||
|
||||
|
||||
def yes_or_no(data) -> str:
|
||||
return "yes" if data else "no"
|
||||
Reference in New Issue
Block a user