utils enthält jetzt generelle Routinen der verschiedenen Programme. create_example refactored. utils wird jetzt entsprechend überall importiert
This commit is contained in:
+381
@@ -171,6 +171,387 @@ def get_dxf_file(filepath: Path):
|
||||
return doc
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# ezdxf Operations (High-Level DXF Helpers)
|
||||
# ============================================================================
|
||||
|
||||
def extract_insert_attributes(insert, error_collector=None) -> dict:
|
||||
"""
|
||||
Extrahiert alle Attribute aus einem INSERT-Block.
|
||||
Unterstützt zweistufige Blockstruktur:
|
||||
- Äußerer Block enthält LWPOLYLINE und INSERT zu Attribut-Block
|
||||
- Innerer Attribut-Block enthält die eigentlichen Attribute
|
||||
|
||||
Args:
|
||||
insert: INSERT-Entity des Blocks
|
||||
error_collector: Optional ErrorCollector für Warnungen
|
||||
|
||||
Returns:
|
||||
Dictionary mit allen gefundenen Attributen {tag: text}
|
||||
"""
|
||||
attributes = {}
|
||||
if insert.dxftype() != 'INSERT':
|
||||
return attributes
|
||||
|
||||
# Prüfe zuerst ob der Block direkt Attribute hat (alte Struktur)
|
||||
direct_attribs = list(insert.attribs)
|
||||
if direct_attribs:
|
||||
for attrib in direct_attribs:
|
||||
tag = attrib.dxf.tag
|
||||
value = attrib.dxf.text
|
||||
attributes[tag] = value
|
||||
return attributes
|
||||
|
||||
# Neue zweistufige Struktur: Suche im Block nach einem INSERT mit Attributen
|
||||
# Dies erfordert das doc-Objekt, daher nur wenn verfügbar
|
||||
if hasattr(insert, 'doc') and insert.doc:
|
||||
try:
|
||||
block_layout = insert.doc.blocks.get(insert.dxf.name)
|
||||
for entity in block_layout:
|
||||
if entity.dxftype() == 'INSERT':
|
||||
inner_attribs = list(entity.attribs)
|
||||
if inner_attribs:
|
||||
# Gefunden: innerer Block mit Attributen
|
||||
for attrib in inner_attribs:
|
||||
tag = attrib.dxf.tag
|
||||
value = attrib.dxf.text
|
||||
attributes[tag] = value
|
||||
break # Nur das erste INSERT mit Attributen verwenden
|
||||
except Exception as e:
|
||||
if error_collector:
|
||||
error_msg = f"Fehler beim Extrahieren der Attribute aus Block '{insert.dxf.name}': {e}"
|
||||
if hasattr(error_collector, 'add_warnings'):
|
||||
error_collector.add_warnings({"attribute_extraction_error": error_msg})
|
||||
|
||||
return attributes
|
||||
|
||||
|
||||
def extract_insert_attributes_with_doc(doc, insert, error_collector=None) -> dict:
|
||||
"""
|
||||
Extrahiert alle Attribute aus einem INSERT-Block mit explizitem doc-Parameter.
|
||||
Empfohlen für neue Code, da es zweistufige Blockstrukturen besser unterstützt.
|
||||
|
||||
Args:
|
||||
doc: DXF-Dokument
|
||||
insert: INSERT-Entity des Blocks
|
||||
error_collector: Optional ErrorCollector für Warnungen
|
||||
|
||||
Returns:
|
||||
Dictionary mit allen gefundenen Attributen {tag: text}
|
||||
"""
|
||||
attributes = {}
|
||||
if insert.dxftype() != 'INSERT':
|
||||
return attributes
|
||||
|
||||
# Prüfe zuerst ob der Block direkt Attribute hat (alte Struktur)
|
||||
direct_attribs = list(insert.attribs)
|
||||
if direct_attribs:
|
||||
for attrib in direct_attribs:
|
||||
tag = attrib.dxf.tag
|
||||
value = attrib.dxf.text
|
||||
attributes[tag] = value
|
||||
return attributes
|
||||
|
||||
# Neue zweistufige Struktur: Suche im Block nach einem INSERT mit Attributen
|
||||
try:
|
||||
block_layout = doc.blocks.get(insert.dxf.name)
|
||||
for entity in block_layout:
|
||||
if entity.dxftype() == 'INSERT':
|
||||
inner_attribs = list(entity.attribs)
|
||||
if inner_attribs:
|
||||
# Gefunden: innerer Block mit Attributen
|
||||
for attrib in inner_attribs:
|
||||
tag = attrib.dxf.tag
|
||||
value = attrib.dxf.text
|
||||
attributes[tag] = value
|
||||
break # Nur das erste INSERT mit Attributen verwenden
|
||||
except Exception as e:
|
||||
if error_collector:
|
||||
error_msg = f"Fehler beim Extrahieren der Attribute aus Block '{insert.dxf.name}': {e}"
|
||||
if hasattr(error_collector, 'add_warnings'):
|
||||
error_collector.add_warnings({"attribute_extraction_error": error_msg})
|
||||
|
||||
return attributes
|
||||
|
||||
|
||||
def ensure_layer_exists(doc, layer_name: str, color: int = None, linetype: str = None,
|
||||
lineweight: int = None, description: str = None):
|
||||
"""
|
||||
Stellt sicher, dass ein Layer existiert und erstellt ihn falls nötig.
|
||||
|
||||
Args:
|
||||
doc: DXF-Dokument
|
||||
layer_name: Name des Layers
|
||||
color: ACI Color Index (1-255), optional
|
||||
linetype: Name des Linientyps (z.B. 'CONTINUOUS', 'DASHED'), optional
|
||||
lineweight: Linienbreite in mm*100 (z.B. 25 für 0.25mm), optional
|
||||
description: Layer-Beschreibung, optional
|
||||
|
||||
Returns:
|
||||
Layer-Objekt (existierend oder neu erstellt)
|
||||
"""
|
||||
if layer_name in doc.layers:
|
||||
layer = doc.layers.get(layer_name)
|
||||
else:
|
||||
layer = doc.layers.add(layer_name)
|
||||
|
||||
# Setze optionale Attribute
|
||||
if color is not None:
|
||||
layer.color = color
|
||||
if linetype is not None:
|
||||
layer.dxf.linetype = linetype
|
||||
if lineweight is not None:
|
||||
layer.dxf.lineweight = lineweight
|
||||
if description is not None:
|
||||
layer.dxf.description = description
|
||||
|
||||
return layer
|
||||
|
||||
|
||||
def add_rectangle_lwpolyline(msp, x: float, y: float, width: float, height: float,
|
||||
layer: str = '0', color: int = None, rgb: tuple = None,
|
||||
closed: bool = True):
|
||||
"""
|
||||
Fügt eine rechteckige LWPOLYLINE hinzu.
|
||||
|
||||
Args:
|
||||
msp: Modelspace des DXF-Dokuments
|
||||
x: X-Koordinate der unteren linken Ecke
|
||||
y: Y-Koordinate der unteren linken Ecke
|
||||
width: Breite des Rechtecks
|
||||
height: Höhe des Rechtecks
|
||||
layer: Layer-Name (default: '0')
|
||||
color: ACI Color Index (1-255), optional
|
||||
rgb: RGB-Tuple (r, g, b) mit Werten 0-255, optional
|
||||
closed: Ob die Polylinie geschlossen sein soll (default: True)
|
||||
|
||||
Returns:
|
||||
LWPOLYLINE-Entity
|
||||
"""
|
||||
points = [
|
||||
(x, y),
|
||||
(x + width, y),
|
||||
(x + width, y + height),
|
||||
(x, y + height)
|
||||
]
|
||||
|
||||
polyline = msp.add_lwpolyline(points, close=closed)
|
||||
polyline.dxf.layer = layer
|
||||
|
||||
if color is not None:
|
||||
polyline.dxf.color = color
|
||||
if rgb is not None:
|
||||
polyline.rgb = rgb
|
||||
|
||||
return polyline
|
||||
|
||||
|
||||
def add_text_with_alignment(msp, text: str, x: float, y: float,
|
||||
height: float = 50, layer: str = '0',
|
||||
color: int = None, halign: int = 0, valign: int = 0,
|
||||
rotation: float = 0.0, style: str = 'Standard'):
|
||||
"""
|
||||
Fügt einen Text mit Ausrichtung hinzu.
|
||||
|
||||
Args:
|
||||
msp: Modelspace des DXF-Dokuments
|
||||
text: Textinhalt
|
||||
x: X-Koordinate
|
||||
y: Y-Koordinate
|
||||
height: Texthöhe (default: 50)
|
||||
layer: Layer-Name (default: '0')
|
||||
color: ACI Color Index (1-255), optional
|
||||
halign: Horizontale Ausrichtung (0=links, 1=mitte, 2=rechts)
|
||||
valign: Vertikale Ausrichtung (0=basis, 1=unten, 2=mitte, 3=oben)
|
||||
rotation: Rotation in Grad (default: 0.0)
|
||||
style: Textstil-Name (default: 'Standard')
|
||||
|
||||
Returns:
|
||||
TEXT-Entity
|
||||
"""
|
||||
text_entity = msp.add_text(text, height=height)
|
||||
text_entity.dxf.layer = layer
|
||||
text_entity.dxf.style = style
|
||||
text_entity.dxf.rotation = rotation
|
||||
|
||||
if color is not None:
|
||||
text_entity.dxf.color = color
|
||||
|
||||
# Setze Ausrichtung
|
||||
text_entity.set_placement((x, y), align=f"{'LEFT' if halign == 0 else 'CENTER' if halign == 1 else 'RIGHT'}_{'BASELINE' if valign == 0 else 'BOTTOM' if valign == 1 else 'MIDDLE' if valign == 2 else 'TOP'}")
|
||||
|
||||
return text_entity
|
||||
|
||||
|
||||
def extract_attributes_with_positions(insert_iterable, round_decimals: int = 1):
|
||||
"""
|
||||
Wandelt eine Iterable von INSERT-Objekten in zwei Listen um.
|
||||
Kompatibilität mit getpositions.py attribs_to_dicts().
|
||||
|
||||
Args:
|
||||
insert_iterable: Iterable von INSERT-Entities (z.B. msp oder iterdxf.modelspace())
|
||||
round_decimals: Anzahl Nachkommastellen für Positions-Rundung (default: 1)
|
||||
|
||||
Returns:
|
||||
Tuple von (all_inserts, all_positions):
|
||||
- all_inserts: Liste von Dicts mit Attribut-Tags und deren Textwerten
|
||||
- all_positions: Liste von Dicts mit Attribut-Tags und deren (x, y, z)-Positionen
|
||||
|
||||
Note:
|
||||
Blöcke ohne Attribute werden übersprungen.
|
||||
Diese Funktion ist kompatibel mit der alten attribs_to_dicts() aus getpositions.py
|
||||
"""
|
||||
all_inserts = []
|
||||
all_positions = []
|
||||
|
||||
for insert in insert_iterable:
|
||||
if insert.dxftype() != 'INSERT':
|
||||
continue
|
||||
|
||||
itemdata = {}
|
||||
positions = {}
|
||||
|
||||
# Direkte Attribute (alte Struktur)
|
||||
for attrib in insert.attribs:
|
||||
if len(insert.attribs) == 0:
|
||||
continue # Überspringe Blöcke ohne Attribute
|
||||
attr_tag = attrib.dxf.tag
|
||||
attr_text = attrib.dxf.text
|
||||
pos = attrib.dxf.insert
|
||||
itemdata[attr_tag] = attr_text
|
||||
positions[attr_tag] = (
|
||||
round(pos.x, round_decimals),
|
||||
round(pos.y, round_decimals),
|
||||
round(pos.z, round_decimals)
|
||||
)
|
||||
|
||||
if len(itemdata) > 0:
|
||||
all_inserts.append(itemdata)
|
||||
all_positions.append(positions)
|
||||
|
||||
return all_inserts, all_positions
|
||||
|
||||
|
||||
def add_blockref_with_attributes(msp, block_name: str, insert_point: tuple,
|
||||
attributes: dict = None, layer: str = '0',
|
||||
scale: float = 1.0, rotation: float = 0.0):
|
||||
"""
|
||||
Fügt eine Block-Referenz (INSERT) mit Attributen hinzu.
|
||||
|
||||
Args:
|
||||
msp: Modelspace des DXF-Dokuments
|
||||
block_name: Name des Blocks
|
||||
insert_point: Einfügepunkt als (x, y) oder (x, y, z) Tuple
|
||||
attributes: Dictionary mit Attribut-Namen und -Werten (optional)
|
||||
layer: Layer-Name (default: '0')
|
||||
scale: Skalierungsfaktor (default: 1.0)
|
||||
rotation: Rotation in Grad (default: 0.0)
|
||||
|
||||
Returns:
|
||||
INSERT-Entity mit gesetzten Attributen
|
||||
"""
|
||||
# Füge Block-Referenz hinzu
|
||||
blockref = msp.add_blockref(block_name, insert_point)
|
||||
blockref.dxf.layer = layer
|
||||
|
||||
if scale != 1.0:
|
||||
blockref.dxf.xscale = scale
|
||||
blockref.dxf.yscale = scale
|
||||
blockref.dxf.zscale = scale
|
||||
|
||||
if rotation != 0.0:
|
||||
blockref.dxf.rotation = rotation
|
||||
|
||||
# Füge Attribute hinzu falls vorhanden
|
||||
if attributes:
|
||||
blockref.add_auto_attribs(attributes)
|
||||
|
||||
return blockref
|
||||
|
||||
|
||||
def query_entities_by_layer(msp, entity_type: str = None, layers: list = None,
|
||||
exclude_layers: list = None):
|
||||
"""
|
||||
Filtert Entities nach Typ und Layer.
|
||||
|
||||
Args:
|
||||
msp: Modelspace des DXF-Dokuments
|
||||
entity_type: Entity-Typ (z.B. 'INSERT', 'LINE', 'TEXT'), None für alle
|
||||
layers: Liste der zu inkludierenden Layer (None = alle außer exclude_layers)
|
||||
exclude_layers: Liste der zu exkludierenden Layer (None = keine Exklusion)
|
||||
|
||||
Returns:
|
||||
Liste der gefilterten Entities
|
||||
|
||||
Examples:
|
||||
# Alle INSERTs auf Layer 'ILS_MOTOR'
|
||||
query_entities_by_layer(msp, 'INSERT', layers=['ILS_MOTOR'])
|
||||
|
||||
# Alle Entities außer auf Layer '0'
|
||||
query_entities_by_layer(msp, exclude_layers=['0'])
|
||||
|
||||
# Alle LINEs auf ILS_* Layern (manuelles Filtern nötig)
|
||||
all_lines = query_entities_by_layer(msp, 'LINE')
|
||||
ils_lines = [e for e in all_lines if e.dxf.layer.startswith('ILS_')]
|
||||
"""
|
||||
# Baue Query-String
|
||||
if entity_type:
|
||||
entities = msp.query(entity_type)
|
||||
else:
|
||||
entities = msp
|
||||
|
||||
# Filtere nach Layern
|
||||
result = []
|
||||
for entity in entities:
|
||||
layer = entity.dxf.layer
|
||||
|
||||
# Prüfe Exklusion
|
||||
if exclude_layers and layer in exclude_layers:
|
||||
continue
|
||||
|
||||
# Prüfe Inklusion
|
||||
if layers is None or layer in layers:
|
||||
result.append(entity)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def create_layers_from_config(doc, layer_definitions: dict):
|
||||
"""
|
||||
Erstellt mehrere Layer aus einer Konfiguration.
|
||||
|
||||
Args:
|
||||
doc: DXF-Dokument
|
||||
layer_definitions: Dictionary mit Layer-Definitionen
|
||||
Format: {
|
||||
'layer_name': {
|
||||
'color': int (optional),
|
||||
'linetype': str (optional),
|
||||
'lineweight': int (optional),
|
||||
'description': str (optional)
|
||||
},
|
||||
...
|
||||
}
|
||||
|
||||
Example:
|
||||
layer_config = {
|
||||
'ILS_MOTOR': {'color': 1, 'description': 'Motor layer'},
|
||||
'ILS_SENSOR': {'color': 3, 'lineweight': 25},
|
||||
'ILS_CABLE': {'color': 5, 'linetype': 'DASHED'}
|
||||
}
|
||||
create_layers_from_config(doc, layer_config)
|
||||
"""
|
||||
for layer_name, props in layer_definitions.items():
|
||||
ensure_layer_exists(
|
||||
doc,
|
||||
layer_name,
|
||||
color=props.get('color'),
|
||||
linetype=props.get('linetype'),
|
||||
lineweight=props.get('lineweight'),
|
||||
description=props.get('description')
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Dictionary Operations
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user