254 lines
11 KiB
Python
254 lines
11 KiB
Python
import logging
|
|
import re
|
|
import numpy as np
|
|
import math
|
|
from typing import Dict, Any, Tuple, Optional
|
|
|
|
# Group inheritance behavior:
|
|
# - Group prefixes always override individual element prefixes
|
|
# - Group suffixes always override individual element suffixes
|
|
# - This ensures consistent behavior for all elements within a group
|
|
|
|
# Import necessary functions/modules
|
|
from . import svg_math
|
|
from . import svg_utils
|
|
from . import json_builder
|
|
from . import element_mapper
|
|
from .geometry_extractor import GeometryExtractor, get_element_geometry, DEFAULT_GEOMETRY
|
|
from .prefix_resolver import PrefixResolver
|
|
from .position_utils import extract_position_from_attributes
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def process_element(element, element_count, svg_type, svg_dimensions, custom_options,
|
|
debug=False, group_prefix=None, group_suffix=None, center_mode='bbox') -> Dict[str, Any]:
|
|
"""Process a single SVG element and return its JSON representation.
|
|
|
|
Args:
|
|
element: The SVG element
|
|
element_count: A counter for this element type
|
|
svg_type: The tag name (e.g., 'rect')
|
|
svg_dimensions: (width, height) of the parent SVG
|
|
custom_options: Configuration options including mappings
|
|
debug: Enable debug logging
|
|
group_prefix: Prefix inherited from a parent group
|
|
group_suffix: Suffix inherited from a parent group
|
|
center_mode: How to calculate the center for paths ('bbox' or 'centroid')
|
|
|
|
Returns:
|
|
dict: The JSON representation of the element, or default on error
|
|
"""
|
|
try:
|
|
debug_buffer = []
|
|
|
|
if debug:
|
|
logger.debug(f"Processing {svg_type} #{element_count} (Group context: prefix='{group_prefix}', suffix='{group_suffix}')")
|
|
debug_buffer.append(f"Processing {svg_type} #{element_count}")
|
|
|
|
# --- Get element name and identifiers ---
|
|
element_id = element.getAttribute('id') or ""
|
|
element_label = element.getAttribute('inkscape:label') or element_id
|
|
element_name = element_label or f"{svg_type}{element_count}"
|
|
original_name = element_name
|
|
|
|
# --- Mapping Logic ---
|
|
element_mappings = custom_options.get('element_mappings', [])
|
|
element_candidate_prefix = PrefixResolver.get_element_prefix(element_label)
|
|
|
|
if debug and element_candidate_prefix:
|
|
logger.debug(f"Element '{element_label}' has candidate prefix: '{element_candidate_prefix}'")
|
|
|
|
# Determine which prefix to use for mapping lookup
|
|
prefix_to_find, prefix_source = PrefixResolver.determine_prefix_to_use(
|
|
element_candidate_prefix, group_prefix, element_mappings, svg_type
|
|
)
|
|
|
|
if debug:
|
|
if prefix_source == "element":
|
|
logger.debug(f"Using element's own prefix '{prefix_to_find}' (no group prefix override)")
|
|
elif prefix_source == "group":
|
|
logger.debug(f"Group prefix '{prefix_to_find}' overrides element prefix '{element_candidate_prefix}'")
|
|
else:
|
|
logger.debug(f"No valid prefix found, will look for fallback mapping")
|
|
|
|
# Find the appropriate mapping
|
|
mapping_to_use, match_type = element_mapper.find_mapping_for_element(
|
|
svg_type, prefix_to_find, element_mappings, debug
|
|
)
|
|
|
|
# Determine label prefix for JSON meta
|
|
label_prefix = ""
|
|
has_prefix_mapping = False
|
|
|
|
if prefix_source == "element" and mapping_to_use:
|
|
label_prefix = mapping_to_use.get('label_prefix', '')
|
|
has_prefix_mapping = True
|
|
elif prefix_source == "group" and mapping_to_use:
|
|
label_prefix = group_prefix
|
|
has_prefix_mapping = True
|
|
if debug:
|
|
logger.debug(f"Element '{element_label}' inherited mapping via group prefix '{prefix_to_find}'.")
|
|
|
|
if debug:
|
|
logger.debug(f"Mapping result: type='{match_type}', mapping={mapping_to_use}")
|
|
|
|
# --- Geometry and Transformation ---
|
|
geometry = get_element_geometry(element, svg_type, center_mode)
|
|
orig_center_x = geometry['center_x']
|
|
orig_center_y = geometry['center_y']
|
|
geom_width = geometry['width']
|
|
geom_height = geometry['height']
|
|
logger.debug(f"Geometric Center=({orig_center_x:.2f}, {orig_center_y:.2f}), Size=({geom_width:.2f}x{geom_height:.2f})")
|
|
|
|
# Get transformation matrix and apply it
|
|
transform_matrix = svg_math.get_combined_transform_matrix(element)
|
|
logger.debug(f"Transform matrix:\n{transform_matrix}")
|
|
|
|
transformed_center_x, transformed_center_y = svg_math.apply_transform(
|
|
(orig_center_x, orig_center_y), transform_matrix
|
|
)
|
|
logger.debug(f"Transformed Center=({transformed_center_x:.2f}, {transformed_center_y:.2f})")
|
|
|
|
# Extract rotation angle
|
|
rotation_angle = svg_math.extract_rotation_from_transform(element)
|
|
|
|
# --- Determine Final Size ---
|
|
# Start with mapped values, then fall back to geometric size
|
|
element_width = mapping_to_use.get('width') if mapping_to_use else None
|
|
element_height = mapping_to_use.get('height') if mapping_to_use else None
|
|
|
|
if element_width is not None or element_height is not None:
|
|
logger.debug(f"Using dimensions from {match_type} mapping: W={element_width}, H={element_height}")
|
|
|
|
# Use geometric size if not specified in mapping
|
|
if element_width is None:
|
|
element_width = geom_width
|
|
if debug:
|
|
logger.debug(f"Using geometric width as fallback: {element_width:.2f}")
|
|
|
|
if element_height is None:
|
|
element_height = geom_height
|
|
if debug:
|
|
logger.debug(f"Using geometric height as fallback: {element_height:.2f}")
|
|
|
|
# Ensure final dimensions are non-zero
|
|
element_width = max(element_width, 1.0)
|
|
element_height = max(element_height, 1.0)
|
|
logger.debug(f"Final dimensions: {element_width:.2f}x{element_height:.2f}")
|
|
|
|
# --- Calculate Final Position ---
|
|
# Calculate top-left from transformed center and final size
|
|
final_x = transformed_center_x - element_width / 2
|
|
final_y = transformed_center_y - element_height / 2
|
|
logger.debug(f"Calculated initial centered position: ({final_x:.2f}, {final_y:.2f})")
|
|
|
|
# Check for suspicious default position (-10,-10)
|
|
if abs(final_x + 10.0) < 1e-6 and abs(final_y + 10.0) < 1e-6:
|
|
logger.warning(f"Position is suspiciously close to (-10,-10) for {element_name}.")
|
|
logger.debug(f"SVG type={svg_type}, element_id={element_id}")
|
|
logger.debug(f"Original geometry: {geometry}")
|
|
logger.debug(f"Transform attribute: {element.getAttribute('transform')}")
|
|
|
|
if abs(final_x + 10.0) < 1e-9 and abs(final_y + 10.0) < 1e-9:
|
|
# Try to use the original center coordinates if they're valid
|
|
if abs(orig_center_x) > 1e-6 or abs(orig_center_y) > 1e-6:
|
|
logger.info(f"Overriding suspicious position with original center")
|
|
final_x = orig_center_x - element_width / 2
|
|
final_y = orig_center_y - element_height / 2
|
|
else:
|
|
# Try to extract position from element attributes
|
|
extracted_x, extracted_y = extract_position_from_attributes(element, svg_type)
|
|
if extracted_x is not None and extracted_y is not None:
|
|
logger.info(f"Overriding suspicious position with attribute-based position")
|
|
final_x = extracted_x
|
|
final_y = extracted_y
|
|
|
|
# Apply offsets from mapping
|
|
x_offset = mapping_to_use.get('x_offset', 0) if mapping_to_use else 0
|
|
y_offset = mapping_to_use.get('y_offset', 0) if mapping_to_use else 0
|
|
|
|
if x_offset != 0 or y_offset != 0:
|
|
final_x += x_offset
|
|
final_y += y_offset
|
|
if debug:
|
|
logger.debug(f"Applied offsets: x={x_offset}, y={y_offset} -> ({final_x}, {final_y})")
|
|
|
|
# --- Suffix Handling (Rotation Override) ---
|
|
suffix = None
|
|
suffix_source = None
|
|
|
|
# First check group suffix since it should take precedence
|
|
if group_suffix:
|
|
suffix = group_suffix
|
|
suffix_source = "group"
|
|
if debug:
|
|
logger.debug(f"Using group suffix '{suffix}' for element {element_name}")
|
|
# Only check element suffix if no group suffix exists
|
|
elif element_name and len(element_name) >= 2:
|
|
last_char = element_name[-1].lower()
|
|
if last_char in ['r', 'd', 'l', 'u']:
|
|
suffix = last_char
|
|
suffix_source = "element"
|
|
if debug:
|
|
logger.debug(f"Using element's own suffix '{suffix}'")
|
|
|
|
# Apply rotation override if a suffix was found
|
|
if suffix:
|
|
original_rotation = rotation_angle
|
|
rotation_map = {'r': 0, 'd': 90, 'l': 180, 'u': 270}
|
|
rotation_angle = rotation_map.get(suffix, rotation_angle)
|
|
|
|
if debug:
|
|
logger.debug(f"SUFFIX ROTATION OVERRIDE ({suffix_source}): '{suffix}' "
|
|
f"changed {original_rotation:.1f}deg to {rotation_angle}deg")
|
|
|
|
if debug:
|
|
logger.debug(f"Final state: Pos=({final_x:.2f}, {final_y:.2f}), "
|
|
f"Size=({element_width:.2f}x{element_height:.2f}), "
|
|
f"Rot={rotation_angle:.1f}deg, Prefix='{label_prefix}', Suffix={suffix}")
|
|
|
|
# --- Name Cleaning ---
|
|
cleaned_name = svg_utils.clean_element_name(
|
|
element_name,
|
|
label_prefix if has_prefix_mapping else None,
|
|
suffix,
|
|
has_prefix_mapping,
|
|
mapping_to_use
|
|
)
|
|
|
|
if debug and cleaned_name != element_name:
|
|
logger.debug(f"Cleaned name: '{element_name}' -> '{cleaned_name}'")
|
|
|
|
element_name = cleaned_name
|
|
|
|
# --- JSON Construction ---
|
|
return json_builder.create_element_json(
|
|
element_name=element_name,
|
|
element_id=element_id,
|
|
element_label=element_label,
|
|
element_count=element_count,
|
|
x=final_x,
|
|
y=final_y,
|
|
svg_type=svg_type,
|
|
label_prefix=label_prefix,
|
|
rotation_angle=rotation_angle,
|
|
element_width=element_width,
|
|
element_height=element_height,
|
|
x_offset=x_offset,
|
|
y_offset=y_offset,
|
|
original_name=original_name,
|
|
debug_buffer=debug_buffer,
|
|
has_prefix_mapping=has_prefix_mapping,
|
|
custom_options=custom_options,
|
|
debug=debug
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed processing {svg_type} #{element_count}: {str(e)}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return svg_utils.create_default_element(element_count, svg_type, str(e))
|
|
|
|
# Placeholder for process_element function (to be moved next)
|
|
# def process_element(...):
|
|
# pass |