mirror of
https://github.com/OpenVGLab/OmniLottie.git
synced 2026-09-17 07:36:27 +00:00
8074 lines
338 KiB
Python
8074 lines
338 KiB
Python
from .layers import ShapeLayer, NullLayer, PreCompLayer, TextLayer, SolidColorLayer
|
|||
|
|
from .properties import MultiDimensional, ColorValue
|
||
|
|
from .nvector import NVector
|
||
|
|
from .color import Color
|
||
|
|
from .shapes import *
|
||
|
|
import json
|
||
|
|
import re
|
||
|
|
import ast
|
||
|
|
import sys
|
||
|
|
import os
|
||
|
|
import threading
|
||
|
|
import concurrent.futures
|
||
|
|
import multiprocessing
|
||
|
|
import torch
|
||
|
|
from .effects import Effect, EffectValueSlider, EffectValueColor, EffectValueAngle, EffectValuePoint, EffectValueCheckbox, IgnoredValue, EffectValueDropDown, EffectValueLayer, EffectNoValue
|
||
|
|
from .assets import *
|
||
|
|
from .text import *
|
||
|
|
|
||
|
|
|
||
|
|
class ElementType:
|
||
|
|
PRECOMP_LAYER = 0
|
||
|
|
SOLID_LAYER = 1
|
||
|
|
NULL_LAYER = 3
|
||
|
|
SHAPE_LAYER = 4
|
||
|
|
TEXT_LAYER = 5
|
||
|
|
|
||
|
|
GROUP = "gr"
|
||
|
|
PATH = "sh"
|
||
|
|
STROKE = "st"
|
||
|
|
FILL = "fl"
|
||
|
|
TRANSFORM = "tr"
|
||
|
|
TRIM = "tm"
|
||
|
|
RECT = "rc"
|
||
|
|
ELLIPSE = "el"
|
||
|
|
STAR = "sr"
|
||
|
|
REPEATER = "rp"
|
||
|
|
GRADIENT_FILL = "gf"
|
||
|
|
GRADIENT_STROKE = "gs" #
|
||
|
|
MERGE = "mm" #
|
||
|
|
NO_STYLE = "no" #
|
||
|
|
OFFSET_PATH = "op" #
|
||
|
|
PUCKER_BLOAT = "pb" #
|
||
|
|
ROUNDED_CORNERS = "rd" #
|
||
|
|
TWIST = "tw" #
|
||
|
|
ZIG_ZAG = "zz" #
|
||
|
|
|
||
|
|
class Keyframe:
|
||
|
|
"""Represents a single keyframe in an animation"""
|
||
|
|
def __init__(self, time, value):
|
||
|
|
self.time = time
|
||
|
|
self.value = value
|
||
|
|
self.in_tan = None # Incoming Bezier handle
|
||
|
|
self.out_tan = None # Outgoing Bezier handle
|
||
|
|
self.h = None # Hold keyframe flag
|
||
|
|
self.to = None
|
||
|
|
self.ti = None
|
||
|
|
self.n = None #
|
||
|
|
self.e = None #
|
||
|
|
|
||
|
|
class Value:
|
||
|
|
"""Class for single numeric values that can be animated"""
|
||
|
|
def __init__(self, value):
|
||
|
|
self.value = value
|
||
|
|
self.keyframes = []
|
||
|
|
|
||
|
|
def add_keyframe(self, time, value):
|
||
|
|
"""Add a keyframe to this property"""
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
self.keyframes.append(kf)
|
||
|
|
return kf
|
||
|
|
|
||
|
|
# NullLayer
|
||
|
|
|
||
|
|
def solid_layer_to_json(layer):
|
||
|
|
"""SolidColorLayerJSON"""
|
||
|
|
json_data = {
|
||
|
|
"ddd": 0,
|
||
|
|
"ind": layer.index,
|
||
|
|
"ty": ElementType.SOLID_LAYER,
|
||
|
|
"nm": layer.name,
|
||
|
|
"sr": 1,
|
||
|
|
"ks": {
|
||
|
|
"o": property_to_json(layer.transform.opacity, 100, "ix", 11),
|
||
|
|
"r": property_to_json(layer.transform.rotation, 0, "ix", 10),
|
||
|
|
"p": property_to_json(layer.transform.position, [0, 0, 0], "ix", 2, "l", 2),
|
||
|
|
"a": property_to_json(layer.transform.anchor, [0, 0], "ix", 1, "l", 2),
|
||
|
|
"s": property_to_json(layer.transform.scale, [100, 100, 100], "ix", 6, "l", 2)
|
||
|
|
},
|
||
|
|
"ao": 0,
|
||
|
|
"sc": layer.color,
|
||
|
|
"sw": layer.width,
|
||
|
|
"sh": layer.height,
|
||
|
|
"ip": layer.in_point,
|
||
|
|
"op": layer.out_point,
|
||
|
|
"st": layer.start_time,
|
||
|
|
"bm": 0
|
||
|
|
}
|
||
|
|
# FIX: Add hasMask and masksProperties
|
||
|
|
if hasattr(layer, 'hasMask'):
|
||
|
|
json_data["hasMask"] = layer.hasMask
|
||
|
|
else:
|
||
|
|
# Set default value if not present
|
||
|
|
json_data["hasMask"] = False
|
||
|
|
|
||
|
|
if hasattr(layer, 'masksProperties') and layer.masksProperties:
|
||
|
|
json_data["masksProperties"] = layer.masksProperties
|
||
|
|
|
||
|
|
# track matte
|
||
|
|
if hasattr(layer, 'tt') and layer.tt is not None:
|
||
|
|
json_data["tt"] = layer.tt
|
||
|
|
if hasattr(layer, 'tp') and layer.tp is not None:
|
||
|
|
json_data["tp"] = layer.tp
|
||
|
|
if hasattr(layer, 'td') and layer.td is not None:
|
||
|
|
json_data["td"] = layer.td
|
||
|
|
|
||
|
|
if hasattr(layer, 'ef'):
|
||
|
|
json_data["ef"] = effects_to_json(layer.ef)
|
||
|
|
|
||
|
|
if hasattr(layer, 'tm'):
|
||
|
|
json_data["tm"] = layer.tm
|
||
|
|
|
||
|
|
# parent
|
||
|
|
if hasattr(layer, 'parent_index') and layer.parent_index is not None:
|
||
|
|
json_data["parent"] = layer.parent_index
|
||
|
|
elif hasattr(layer, 'parent') and layer.parent is not None:
|
||
|
|
if isinstance(layer.parent, (int, float)):
|
||
|
|
json_data["parent"] = layer.parent
|
||
|
|
elif hasattr(layer.parent, 'index'):
|
||
|
|
json_data["parent"] = layer.parent.index
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
def text_layer_to_json(layer):
|
||
|
|
"""TextLayerJSON"""
|
||
|
|
json_data = {
|
||
|
|
"ddd": 0,
|
||
|
|
"ind": layer.index,
|
||
|
|
"ty": ElementType.TEXT_LAYER,
|
||
|
|
"nm": layer.name,
|
||
|
|
"sr": 1,
|
||
|
|
"ks": {
|
||
|
|
"o": property_to_json(layer.transform.opacity, 100, "ix", 11),
|
||
|
|
"r": property_to_json(layer.transform.rotation, 0, "ix", 10),
|
||
|
|
"p": property_to_json(layer.transform.position, [0, 0, 0], "ix", 2, "l", 2),
|
||
|
|
"a": property_to_json(layer.transform.anchor, [0, 0], "ix", 1, "l", 2),
|
||
|
|
"s": property_to_json(layer.transform.scale, [100, 100, 100], "ix", 6, "l", 2)
|
||
|
|
},
|
||
|
|
"ao": 0,
|
||
|
|
"t": {
|
||
|
|
"d": {"k": []},
|
||
|
|
"p": {},
|
||
|
|
"m": {"g": 1, "a": {"a": 0, "k": [0, 0], "ix": 2}},
|
||
|
|
"a": []
|
||
|
|
},
|
||
|
|
"ip": layer.in_point,
|
||
|
|
"op": layer.out_point,
|
||
|
|
"st": layer.start_time,
|
||
|
|
"bm": 0
|
||
|
|
}
|
||
|
|
|
||
|
|
# hasMaskTrue
|
||
|
|
if hasattr(layer, 'hasMask') and layer.hasMask:
|
||
|
|
json_data["hasMask"] = layer.hasMask
|
||
|
|
|
||
|
|
if hasattr(layer, 'masksProperties') and layer.masksProperties:
|
||
|
|
json_data["masksProperties"] = layer.masksProperties
|
||
|
|
|
||
|
|
# Text LayerctctNone
|
||
|
|
if hasattr(layer, 'ct'):
|
||
|
|
json_data["ct"] = layer.ct
|
||
|
|
|
||
|
|
#if hasattr(layer, 'ln') and layer.ln is not None:
|
||
|
|
# json_data["ln"] = layer.ln
|
||
|
|
|
||
|
|
if hasattr(layer, 'ef'):
|
||
|
|
json_data["ef"] = layer.ef
|
||
|
|
if hasattr(layer.data, "document") and hasattr(layer.data.document, "value"):
|
||
|
|
json_data["t"]["d"]["k"] = layer.data.document.value
|
||
|
|
|
||
|
|
if hasattr(layer.data, "path_option") and layer.data.path_option:
|
||
|
|
json_data["t"]["p"] = layer.data.path_option
|
||
|
|
|
||
|
|
if hasattr(layer.data, "more_options") and layer.data.more_options:
|
||
|
|
json_data["t"]["m"] = layer.data.more_options
|
||
|
|
|
||
|
|
if hasattr(layer.data, "animators") and layer.data.animators:
|
||
|
|
json_data["t"]["a"] = layer.data.animators
|
||
|
|
|
||
|
|
# track matte
|
||
|
|
if hasattr(layer, 'tt') and layer.tt is not None:
|
||
|
|
json_data["tt"] = layer.tt
|
||
|
|
if hasattr(layer, 'tp') and layer.tp is not None:
|
||
|
|
json_data["tp"] = layer.tp
|
||
|
|
if hasattr(layer, 'td') and layer.td is not None:
|
||
|
|
json_data["td"] = layer.td
|
||
|
|
|
||
|
|
# parent
|
||
|
|
if hasattr(layer, 'parent_index') and layer.parent_index is not None:
|
||
|
|
json_data["parent"] = layer.parent_index
|
||
|
|
elif hasattr(layer, 'parent') and layer.parent is not None:
|
||
|
|
if isinstance(layer.parent, (int, float)):
|
||
|
|
json_data["parent"] = layer.parent
|
||
|
|
elif hasattr(layer.parent, 'index'):
|
||
|
|
json_data["parent"] = layer.parent.index
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
# -
|
||
|
|
# -
|
||
|
|
# -
|
||
|
|
# -
|
||
|
|
# -
|
||
|
|
# -
|
||
|
|
# -
|
||
|
|
def shape_layer_to_json(layer):
|
||
|
|
"""Fixed version: All 2D layers should have 'l' parameter"""
|
||
|
|
json_data = {
|
||
|
|
"ddd": getattr(layer, 'ddd', 0),
|
||
|
|
"ind": layer.index,
|
||
|
|
"ty": ElementType.SHAPE_LAYER,
|
||
|
|
"nm": layer.name,
|
||
|
|
"sr": 1,
|
||
|
|
"ks": {},
|
||
|
|
"ao": getattr(layer, 'ao', 0),
|
||
|
|
"shapes": [],
|
||
|
|
"ip": layer.in_point,
|
||
|
|
"op": layer.out_point,
|
||
|
|
"st": layer.start_time,
|
||
|
|
"bm": 0
|
||
|
|
}
|
||
|
|
# hd
|
||
|
|
if hasattr(layer, 'hd') and layer.hd is not None:
|
||
|
|
json_data["hd"] = layer.hd
|
||
|
|
# cpFalse
|
||
|
|
if hasattr(layer, 'cp') and layer.cp is not None and layer.cp is not False:
|
||
|
|
json_data["cp"] = layer.cp
|
||
|
|
# cl
|
||
|
|
if hasattr(layer, 'cl') and layer.cl is not None:
|
||
|
|
json_data["cl"] = layer.cl
|
||
|
|
|
||
|
|
if hasattr(layer, 'ct'):
|
||
|
|
json_data["ct"] = layer.ct
|
||
|
|
|
||
|
|
if hasattr(layer, 'tm'):
|
||
|
|
json_data["tm"] = layer.tm
|
||
|
|
|
||
|
|
# hasMaskTrue
|
||
|
|
if hasattr(layer, 'hasMask') and layer.hasMask:
|
||
|
|
json_data["hasMask"] = layer.hasMask
|
||
|
|
if hasattr(layer, 'masksProperties'):
|
||
|
|
json_data["masksProperties"] = layer.masksProperties
|
||
|
|
|
||
|
|
|
||
|
|
# Build ks with correct parameters based on 3D flag
|
||
|
|
|
||
|
|
ks = {}
|
||
|
|
|
||
|
|
# Check if this is a 3D layer
|
||
|
|
is_3d = json_data["ddd"] == 1
|
||
|
|
|
||
|
|
if is_3d:
|
||
|
|
# For 3D layers - no ix or l parameters
|
||
|
|
ks["o"] = property_to_json(layer.transform.opacity, 100)
|
||
|
|
|
||
|
|
# For 3D layers, use separate rotation axes - FIX: Check for rx, ry, rz
|
||
|
|
if hasattr(layer.transform, 'rx') and layer.transform.rx is not None:
|
||
|
|
ks["rx"] = property_to_json(layer.transform.rx, 0)
|
||
|
|
else:
|
||
|
|
ks["rx"] = {"a": 0, "k": 0}
|
||
|
|
|
||
|
|
if hasattr(layer.transform, 'ry') and layer.transform.ry is not None:
|
||
|
|
ks["ry"] = property_to_json(layer.transform.ry, 0)
|
||
|
|
else:
|
||
|
|
ks["ry"] = {"a": 0, "k": 0}
|
||
|
|
|
||
|
|
if hasattr(layer.transform, 'rz') and layer.transform.rz is not None:
|
||
|
|
ks["rz"] = property_to_json(layer.transform.rz, 0)
|
||
|
|
elif hasattr(layer.transform, 'rotation'):
|
||
|
|
ks["rz"] = property_to_json(layer.transform.rotation, 0)
|
||
|
|
else:
|
||
|
|
ks["rz"] = {"a": 0, "k": 0}
|
||
|
|
|
||
|
|
|
||
|
|
# Orientation for 3D
|
||
|
|
if hasattr(layer.transform, 'orientation'):
|
||
|
|
ks["or"] = property_to_json(layer.transform.orientation, [0, 0, 0])
|
||
|
|
else:
|
||
|
|
ks["or"] = {"a": 0, "k": [0, 0, 0]}
|
||
|
|
|
||
|
|
# No 'l' parameter for 3D
|
||
|
|
ks["p"] = property_to_json(layer.transform.position, [0, 0, 0], "ix", 2)
|
||
|
|
ks["a"] = property_to_json(layer.transform.anchor, [0, 0, 0], "ix", 1)
|
||
|
|
ks["s"] = property_to_json(layer.transform.scale, [100, 100, 100], "ix", 6)
|
||
|
|
|
||
|
|
else:
|
||
|
|
# For 2D layers - ALWAYS add ix and l parameters (regardless of asset or main layer)
|
||
|
|
ks["o"] = property_to_json(layer.transform.opacity, 100, "ix", 11)
|
||
|
|
ks["r"] = property_to_json(layer.transform.rotation, 0, "ix", 10)
|
||
|
|
|
||
|
|
# Position and anchor should have 'l' parameter for 2D but keep 3 dimensions
|
||
|
|
ks["p"] = property_to_json(layer.transform.position, [0, 0, 0], "ix", 2, "l", 2)
|
||
|
|
ks["a"] = property_to_json(layer.transform.anchor, [0, 0, 0], "ix", 1, "l", 2)
|
||
|
|
|
||
|
|
# Scale ALSO needs 'l' parameter for 2D layers and keeps 3 dimensions
|
||
|
|
ks["s"] = property_to_json(layer.transform.scale, [100, 100, 100], "ix", 6, "l", 2)
|
||
|
|
|
||
|
|
# Add skew and skew_axis for 2D layers
|
||
|
|
if hasattr(layer.transform, 'skew'):
|
||
|
|
ks["sk"] = property_to_json(layer.transform.skew, 0, "ix", 2)
|
||
|
|
else:
|
||
|
|
ks["sk"] = {"a": 0, "k": 0, "ix": 2}
|
||
|
|
|
||
|
|
if hasattr(layer.transform, 'skew_axis'):
|
||
|
|
ks["sa"] = property_to_json(layer.transform.skew_axis, 0, "ix", 2)
|
||
|
|
else:
|
||
|
|
ks["sa"] = {"a": 0, "k": 0, "ix": 2}
|
||
|
|
|
||
|
|
#ks["ty"] = "tr"
|
||
|
|
json_data["ks"] = ks
|
||
|
|
|
||
|
|
|
||
|
|
# FIX: Always add ef if it exists, even if empty array
|
||
|
|
if hasattr(layer, 'ef'):
|
||
|
|
json_data["ef"] = layer.ef
|
||
|
|
|
||
|
|
# matte
|
||
|
|
if hasattr(layer, 'tt') and layer.tt is not None and layer.tt != 0:
|
||
|
|
json_data["tt"] = layer.tt
|
||
|
|
if hasattr(layer, 'tp') and layer.tp is not None and layer.tp != 0:
|
||
|
|
json_data["tp"] = layer.tp
|
||
|
|
if hasattr(layer, 'td') and layer.td is not None and layer.td != 0:
|
||
|
|
json_data["td"] = layer.td
|
||
|
|
|
||
|
|
# FIX: Handle parent index 0 properly
|
||
|
|
if hasattr(layer, 'parent_index') and layer.parent_index is not None:
|
||
|
|
json_data["parent"] = layer.parent_index
|
||
|
|
elif hasattr(layer, 'parent') and layer.parent is not None:
|
||
|
|
if isinstance(layer.parent, (int, float)):
|
||
|
|
json_data["parent"] = layer.parent
|
||
|
|
elif hasattr(layer.parent, 'index'):
|
||
|
|
json_data["parent"] = layer.parent.index
|
||
|
|
|
||
|
|
for shape in layer.shapes:
|
||
|
|
json_shape = shape_to_json(shape)
|
||
|
|
if json_shape:
|
||
|
|
json_data["shapes"].append(json_shape)
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
def font_to_json(font):
|
||
|
|
"""FontJSON - Lottie"""
|
||
|
|
font_json = {
|
||
|
|
"fName": font.name,
|
||
|
|
"fFamily": font.font_family,
|
||
|
|
"fStyle": font.font_style
|
||
|
|
}
|
||
|
|
if hasattr(font, 'ascent') and font.ascent is not None:
|
||
|
|
font_json["ascent"] = font.ascent
|
||
|
|
return font_json
|
||
|
|
|
||
|
|
|
||
|
|
def char_to_json(char):
|
||
|
|
"""CharsJSON"""
|
||
|
|
json_data = {
|
||
|
|
"ch": char.character,
|
||
|
|
"size": char.font_size,
|
||
|
|
"style": char.font_style,
|
||
|
|
"w": char.width,
|
||
|
|
"fFamily": char.font_family
|
||
|
|
}
|
||
|
|
|
||
|
|
# shapesshapesdata{}
|
||
|
|
if hasattr(char, 'data') and hasattr(char.data, 'shapes') and char.data.shapes:
|
||
|
|
# shapesshapes
|
||
|
|
json_data["data"] = {"shapes": []}
|
||
|
|
for shape in char.data.shapes:
|
||
|
|
shape_json = shape_to_json(shape)
|
||
|
|
if shape_json:
|
||
|
|
json_data["data"]["shapes"].append(shape_json)
|
||
|
|
else:
|
||
|
|
# shapesdata
|
||
|
|
json_data["data"] = {}
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
def extract_string_value(line):
|
||
|
|
""""""
|
||
|
|
start = line.find('"') + 1
|
||
|
|
end = line.rfind('"')
|
||
|
|
if start > 0 and end > start:
|
||
|
|
return line[start:end]
|
||
|
|
return ""
|
||
|
|
|
||
|
|
def null_layer_to_json(layer):
|
||
|
|
"""NullLayerJSON"""
|
||
|
|
json_data = {
|
||
|
|
"nm": layer.name,
|
||
|
|
"ddd": 1 if getattr(layer, 'threedimensional', False) else getattr(layer, 'ddd', 0),
|
||
|
|
"ty": layer.type,
|
||
|
|
"ind": layer.index,
|
||
|
|
"sr": layer.stretch,
|
||
|
|
"ip": layer.in_point,
|
||
|
|
"op": layer.out_point,
|
||
|
|
"st": layer.start_time,
|
||
|
|
#"ao": getattr(layer, 'ao', 0),
|
||
|
|
#"ao": layer.ao,
|
||
|
|
"bm": 0
|
||
|
|
}
|
||
|
|
# hd
|
||
|
|
if hasattr(layer, 'hd') and layer.hd is not None:
|
||
|
|
json_data["hd"] = layer.hd
|
||
|
|
|
||
|
|
# cl
|
||
|
|
if hasattr(layer, 'cl') and layer.cl is not None:
|
||
|
|
json_data["cl"] = layer.cl
|
||
|
|
# Add ct if it exists
|
||
|
|
if hasattr(layer, 'ct') and layer.ct is not None:
|
||
|
|
json_data["ct"] = layer.ct
|
||
|
|
if hasattr(layer, 'auto_orient') and layer.auto_orient is not None:
|
||
|
|
json_data["ao"] = layer.auto_orient
|
||
|
|
|
||
|
|
#if hasattr(layer, 'ln'):
|
||
|
|
# json_data["ln"] = layer.ln
|
||
|
|
|
||
|
|
# FIX: Always add hasMask (default to False if not present)
|
||
|
|
if hasattr(layer, 'hasMask'):
|
||
|
|
json_data["hasMask"] = layer.hasMask
|
||
|
|
if hasattr(layer, 'hd'):
|
||
|
|
json_data["hd"] = layer.hd
|
||
|
|
if hasattr(layer, 'cp'):
|
||
|
|
json_data["cp"] = layer.cp
|
||
|
|
|
||
|
|
# parentparent
|
||
|
|
if hasattr(layer, 'parent_index') and layer.parent_index is not None:
|
||
|
|
json_data["parent"] = layer.parent_index
|
||
|
|
elif hasattr(layer, 'parent') and layer.parent is not None:
|
||
|
|
if isinstance(layer.parent, (int, float)):
|
||
|
|
json_data["parent"] = layer.parent
|
||
|
|
elif hasattr(layer.parent, 'index'):
|
||
|
|
json_data["parent"] = layer.parent.index
|
||
|
|
|
||
|
|
|
||
|
|
# track matte
|
||
|
|
if hasattr(layer, 'td') and layer.td is not None:
|
||
|
|
json_data["td"] = layer.td
|
||
|
|
if hasattr(layer, 'tt') and layer.tt is not None:
|
||
|
|
json_data["tt"] = layer.tt
|
||
|
|
if hasattr(layer, 'tp') and layer.tp is not None:
|
||
|
|
json_data["tp"] = layer.tp
|
||
|
|
|
||
|
|
if hasattr(layer, 'ef'):
|
||
|
|
json_data["ef"] = layer.ef
|
||
|
|
|
||
|
|
# -
|
||
|
|
ks = {}
|
||
|
|
|
||
|
|
if layer.transform.anchor is not None:
|
||
|
|
ks["a"] = property_to_json(layer.transform.anchor, [0, 0, 0], "ix", 1, "l", 2)
|
||
|
|
|
||
|
|
if layer.transform.position is not None:
|
||
|
|
# position
|
||
|
|
pos_json = property_to_json(layer.transform.position, [0, 0, 0], "ix", 2, "l", 2)
|
||
|
|
# positiona
|
||
|
|
if hasattr(layer.transform.position, 'animated') and layer.transform.position.animated:
|
||
|
|
pos_json["a"] = 1
|
||
|
|
elif hasattr(layer.transform.position, 'keyframes') and layer.transform.position.keyframes:
|
||
|
|
pos_json["a"] = 1
|
||
|
|
ks["p"] = pos_json
|
||
|
|
|
||
|
|
# ... rest of transform properties remain the same ...
|
||
|
|
if hasattr(layer.transform, 'scale') and layer.transform.scale is not None:
|
||
|
|
if hasattr(layer.transform.scale, 'separated') and layer.transform.scale.separated:
|
||
|
|
scale_data = {"s": True}
|
||
|
|
if hasattr(layer.transform.scale, 'x') and layer.transform.scale.x is not None:
|
||
|
|
scale_data["x"] = property_to_json(layer.transform.scale.x, 100)
|
||
|
|
if hasattr(layer.transform.scale, 'x_ix'):
|
||
|
|
scale_data["x"]["ix"] = layer.transform.scale.x_ix
|
||
|
|
if hasattr(layer.transform.scale, 'y') and layer.transform.scale.y is not None:
|
||
|
|
scale_data["y"] = property_to_json(layer.transform.scale.y, 100)
|
||
|
|
if hasattr(layer.transform.scale, 'y_ix'):
|
||
|
|
scale_data["y"]["ix"] = layer.transform.scale.y_ix
|
||
|
|
if hasattr(layer.transform.scale, 'z') and layer.transform.scale.z is not None:
|
||
|
|
scale_data["z"] = property_to_json(layer.transform.scale.z, 100)
|
||
|
|
if hasattr(layer.transform.scale, 'z_ix'):
|
||
|
|
scale_data["z"]["ix"] = layer.transform.scale.z_ix
|
||
|
|
ks["s"] = scale_data
|
||
|
|
else:
|
||
|
|
ks["s"] = property_to_json(layer.transform.scale, [100, 100, 100], "ix", 6, "l", 2)
|
||
|
|
|
||
|
|
if layer.transform.rotation is not None:
|
||
|
|
ks["r"] = property_to_json(layer.transform.rotation, 0, "ix", 10)
|
||
|
|
|
||
|
|
if layer.transform.opacity is not None:
|
||
|
|
ks["o"] = property_to_json(layer.transform.opacity, 100, "ix", 11)
|
||
|
|
|
||
|
|
if hasattr(layer.transform, 'skew') and layer.transform.skew is not None:
|
||
|
|
ks["sk"] = property_to_json(layer.transform.skew, 0)
|
||
|
|
|
||
|
|
if hasattr(layer.transform, 'skew_axis') and layer.transform.skew_axis is not None:
|
||
|
|
ks["sa"] = property_to_json(layer.transform.skew_axis, 0)
|
||
|
|
|
||
|
|
json_data["ks"] = ks
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
# PrecompLayer
|
||
|
|
|
||
|
|
def precomp_layer_to_json(layer):
|
||
|
|
"""Convert a PreCompLayer object to JSON format"""
|
||
|
|
json_data = {
|
||
|
|
"ddd": 0,
|
||
|
|
"ind": layer.index,
|
||
|
|
"ty": ElementType.PRECOMP_LAYER,
|
||
|
|
"nm": layer.name,
|
||
|
|
"sr": 1,
|
||
|
|
"ks":{},
|
||
|
|
"ao": 0,
|
||
|
|
"ip": layer.in_point,
|
||
|
|
"op": layer.out_point,
|
||
|
|
"st": layer.start_time,
|
||
|
|
"bm": 0,
|
||
|
|
"refId": layer.reference_id
|
||
|
|
}
|
||
|
|
|
||
|
|
# FIX: Add ln property -
|
||
|
|
#if hasattr(layer, 'ln'):
|
||
|
|
# json_data["ln"] = int(layer.ln)
|
||
|
|
|
||
|
|
# FIX: Always add hasMask (default to False if not present)
|
||
|
|
if hasattr(layer, 'hasMask'):
|
||
|
|
json_data["hasMask"] = layer.hasMask
|
||
|
|
if hasattr(layer, 'hd'):
|
||
|
|
json_data["hd"] = layer.hd
|
||
|
|
|
||
|
|
if hasattr(layer, 'cp'):
|
||
|
|
json_data["cp"] = layer.cp
|
||
|
|
|
||
|
|
# FIX: Add masksProperties if present
|
||
|
|
if hasattr(layer, 'masksProperties') and layer.masksProperties:
|
||
|
|
json_data["masksProperties"] = layer.masksProperties
|
||
|
|
|
||
|
|
# FIX: Add tm (time remapping) property - None
|
||
|
|
if hasattr(layer, 'tm'):
|
||
|
|
json_data["tm"] = layer.tm
|
||
|
|
|
||
|
|
# FIX: Add w and h if present
|
||
|
|
if hasattr(layer, 'w'):
|
||
|
|
json_data["w"] = layer.w
|
||
|
|
if hasattr(layer, 'h'):
|
||
|
|
json_data["h"] = layer.h
|
||
|
|
|
||
|
|
# ct
|
||
|
|
if hasattr(layer, 'ct') and layer.ct is not None:
|
||
|
|
json_data["ct"] = layer.ct
|
||
|
|
# effects
|
||
|
|
if hasattr(layer, 'ef') and layer.ef is not None:
|
||
|
|
json_data["ef"] = layer.ef
|
||
|
|
|
||
|
|
# ks
|
||
|
|
ks = {}
|
||
|
|
|
||
|
|
# opacity
|
||
|
|
ks["o"] = property_to_json(layer.transform.opacity, 100, "ix", 11)
|
||
|
|
|
||
|
|
# rotation
|
||
|
|
ks["r"] = property_to_json(layer.transform.rotation, 0, "ix", 10)
|
||
|
|
|
||
|
|
# position - 3
|
||
|
|
ks["p"] = property_to_json(layer.transform.position, [0, 0, 0], "ix", 2, "l", 2)
|
||
|
|
|
||
|
|
# anchor - 3
|
||
|
|
ks["a"] = property_to_json(layer.transform.anchor, [0, 0, 0], "ix", 1, "l", 2)
|
||
|
|
|
||
|
|
# scale - 3
|
||
|
|
ks["s"] = property_to_json(layer.transform.scale, [100, 100, 100], "ix", 6, "l", 2)
|
||
|
|
|
||
|
|
json_data["ks"] = ks
|
||
|
|
|
||
|
|
# Add track matte related attributes
|
||
|
|
if hasattr(layer, 'tt') and layer.tt is not None:
|
||
|
|
json_data["tt"] = layer.tt
|
||
|
|
if hasattr(layer, 'tp') and layer.tp is not None:
|
||
|
|
json_data["tp"] = layer.tp
|
||
|
|
if hasattr(layer, 'td') and layer.td is not None:
|
||
|
|
json_data["td"] = layer.td
|
||
|
|
|
||
|
|
# Add parent info
|
||
|
|
if hasattr(layer, 'parent_index') and layer.parent_index is not None:
|
||
|
|
json_data["parent"] = layer.parent_index
|
||
|
|
elif hasattr(layer, 'parent') and layer.parent is not None:
|
||
|
|
if isinstance(layer.parent, (int, float)):
|
||
|
|
json_data["parent"] = layer.parent
|
||
|
|
elif hasattr(layer.parent, 'index'):
|
||
|
|
json_data["parent"] = layer.parent.index
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
def zig_zag_to_json(shape):
|
||
|
|
"""JSON"""
|
||
|
|
json_data = {
|
||
|
|
"ty": "zz",
|
||
|
|
"nm": shape.name if hasattr(shape, 'name') else "",
|
||
|
|
"mn": "ADBE Vector Filter - Zigzag",
|
||
|
|
"hd": getattr(shape, 'hd', False),
|
||
|
|
"ix": shape.property_index if hasattr(shape, 'property_index') else 1
|
||
|
|
}
|
||
|
|
|
||
|
|
# ZigZag
|
||
|
|
if hasattr(shape, 'frequency') and shape.frequency is not None:
|
||
|
|
json_data["r"] = property_to_json(shape.frequency, 5)
|
||
|
|
|
||
|
|
if hasattr(shape, 'amplitude') and shape.amplitude is not None:
|
||
|
|
json_data["s"] = property_to_json(shape.amplitude, 10)
|
||
|
|
|
||
|
|
if hasattr(shape, 'point_type') and shape.point_type is not None:
|
||
|
|
json_data["pt"] = property_to_json(shape.point_type, 1)
|
||
|
|
|
||
|
|
#
|
||
|
|
if hasattr(shape, 'bm') and shape.bm is not None:
|
||
|
|
json_data["bm"] = shape.bm
|
||
|
|
|
||
|
|
if hasattr(shape, 'cl') and shape.cl is not None:
|
||
|
|
json_data["cl"] = shape.cl
|
||
|
|
|
||
|
|
#if hasattr(shape, 'ln') and shape.ln is not None:
|
||
|
|
# json_data["ln"] = shape.ln
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
def shape_to_json(shape):
|
||
|
|
"""JSON"""
|
||
|
|
if isinstance(shape, Group):
|
||
|
|
return group_to_json(shape)
|
||
|
|
elif isinstance(shape, Path):
|
||
|
|
return path_to_json(shape)
|
||
|
|
elif isinstance(shape, Stroke):
|
||
|
|
return stroke_to_json(shape)
|
||
|
|
elif isinstance(shape, Fill):
|
||
|
|
return fill_to_json(shape)
|
||
|
|
elif isinstance(shape, Trim):
|
||
|
|
return trim_to_json(shape)
|
||
|
|
elif isinstance(shape, Rect):
|
||
|
|
return rect_to_json(shape)
|
||
|
|
elif isinstance(shape, Ellipse):
|
||
|
|
return ellipse_to_json(shape)
|
||
|
|
elif isinstance(shape, Star):
|
||
|
|
return star_to_json(shape)
|
||
|
|
elif isinstance(shape, Repeater):
|
||
|
|
return repeater_to_json(shape)
|
||
|
|
elif isinstance(shape, TransformShape):
|
||
|
|
return transform_shape_to_json(shape) # Use the correct function for TransformShape
|
||
|
|
elif isinstance(shape, GradientFill):
|
||
|
|
return gradient_fill_to_json(shape)
|
||
|
|
elif isinstance(shape, GradientStroke):
|
||
|
|
return gradient_stroke_to_json(shape)
|
||
|
|
elif isinstance(shape, Merge):
|
||
|
|
return merge_to_json(shape)
|
||
|
|
elif isinstance(shape, RoundedCorners):
|
||
|
|
return rounded_corners_to_json(shape)
|
||
|
|
elif isinstance(shape, Twist):
|
||
|
|
return twist_to_json(shape)
|
||
|
|
elif isinstance(shape, ZigZag):
|
||
|
|
return zig_zag_to_json(shape)
|
||
|
|
else:
|
||
|
|
print(f"Warning: : {type(shape)}")
|
||
|
|
return None
|
||
|
|
|
||
|
|
def group_to_json(group):
|
||
|
|
"""JSON - np"""
|
||
|
|
#
|
||
|
|
all_items = []
|
||
|
|
|
||
|
|
for item in group.shapes:
|
||
|
|
json_item = shape_to_json(item)
|
||
|
|
if json_item:
|
||
|
|
all_items.append(json_item)
|
||
|
|
|
||
|
|
# group.nameNone
|
||
|
|
group_name = group.name if group.name is not None else ""
|
||
|
|
|
||
|
|
# npit
|
||
|
|
if hasattr(group, 'number_of_properties') and group.number_of_properties is not None:
|
||
|
|
np_value = group.number_of_properties
|
||
|
|
else:
|
||
|
|
# npit
|
||
|
|
np_value = len(all_items)
|
||
|
|
|
||
|
|
# JSON
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.GROUP,
|
||
|
|
"it": all_items,
|
||
|
|
"nm": group_name,
|
||
|
|
"np": int(np_value), #
|
||
|
|
"cix": getattr(group, 'cix', 2),
|
||
|
|
"bm": 0,
|
||
|
|
"ix": getattr(group, 'property_index', 1),
|
||
|
|
"mn": getattr(group, 'mn', "ADBE Vector Group"),
|
||
|
|
"hd": getattr(group, 'hd', False)
|
||
|
|
}
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
def rect_to_json(rect):
|
||
|
|
"""JSON"""
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.RECT,
|
||
|
|
"nm": rect.name,
|
||
|
|
"ix": rect.property_index,
|
||
|
|
"d": getattr(rect, 'direction', 1), #
|
||
|
|
"mn": "ADBE Vector Shape - Rect",
|
||
|
|
"hd": getattr(rect, 'hd', False) # hd
|
||
|
|
}
|
||
|
|
|
||
|
|
# - ix
|
||
|
|
if hasattr(rect, "position") and rect.position:
|
||
|
|
json_data["p"] = property_to_json(rect.position, [0, 0], "ix", 3)
|
||
|
|
|
||
|
|
# - ix
|
||
|
|
if hasattr(rect, "size") and rect.size:
|
||
|
|
json_data["s"] = property_to_json(rect.size, [100, 100], "ix", 2)
|
||
|
|
|
||
|
|
# - ix
|
||
|
|
if hasattr(rect, "rounded") and rect.rounded:
|
||
|
|
rounded_ix = getattr(rect, 'rounded_ix', 4) # 41
|
||
|
|
json_data["r"] = property_to_json(rect.rounded, 0, "ix", rounded_ix)
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
def path_to_json(path):
|
||
|
|
"""JSON - ind, mn, hd"""
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.PATH,
|
||
|
|
"nm": path.name,
|
||
|
|
}
|
||
|
|
|
||
|
|
# pathd
|
||
|
|
if hasattr(path, 'd') and path.d is not None:
|
||
|
|
json_data["d"] = path.d
|
||
|
|
|
||
|
|
# ✅ ind
|
||
|
|
if hasattr(path, 'ind') and path.ind is not None:
|
||
|
|
json_data["ind"] = path.ind
|
||
|
|
|
||
|
|
# ixNone
|
||
|
|
if hasattr(path, 'property_index') and path.property_index is not None:
|
||
|
|
json_data["ix"] = path.property_index
|
||
|
|
|
||
|
|
# ✅ mn -
|
||
|
|
if hasattr(path, 'mn') and path.mn is not None:
|
||
|
|
json_data["mn"] = path.mn
|
||
|
|
|
||
|
|
# ✅ hd -
|
||
|
|
if hasattr(path, 'hd') and path.hd is not None:
|
||
|
|
json_data["hd"] = path.hd
|
||
|
|
|
||
|
|
# ks
|
||
|
|
ks = {
|
||
|
|
"a": 0 #
|
||
|
|
}
|
||
|
|
|
||
|
|
#
|
||
|
|
if hasattr(path.shape, 'keyframes') and path.shape.keyframes:
|
||
|
|
#
|
||
|
|
ks["a"] = 1
|
||
|
|
keyframes = []
|
||
|
|
|
||
|
|
for kf in path.shape.keyframes:
|
||
|
|
bezier = kf.value
|
||
|
|
|
||
|
|
keyframe = {
|
||
|
|
"t": kf.time,
|
||
|
|
"s": [{
|
||
|
|
"i": [[t.x, t.y] for t in bezier.in_tangents],
|
||
|
|
"o": [[t.x, t.y] for t in bezier.out_tangents],
|
||
|
|
"v": [[v.x, v.y] for v in bezier.vertices],
|
||
|
|
"c": bezier.closed
|
||
|
|
}]
|
||
|
|
}
|
||
|
|
|
||
|
|
# h
|
||
|
|
if hasattr(kf, 'h') and kf.h is not None:
|
||
|
|
keyframe["h"] = kf.h
|
||
|
|
|
||
|
|
#
|
||
|
|
if hasattr(kf, 'in_tan') and kf.in_tan:
|
||
|
|
# -
|
||
|
|
x_val = kf.in_tan['x']
|
||
|
|
y_val = kf.in_tan['y']
|
||
|
|
|
||
|
|
#
|
||
|
|
if isinstance(x_val, list) and len(x_val) == 1:
|
||
|
|
x_val = x_val[0]
|
||
|
|
if isinstance(y_val, list) and len(y_val) == 1:
|
||
|
|
y_val = y_val[0]
|
||
|
|
|
||
|
|
#
|
||
|
|
if isinstance(x_val, list) or isinstance(y_val, list):
|
||
|
|
keyframe["i"] = {"x": x_val if isinstance(x_val, list) else [x_val],
|
||
|
|
"y": y_val if isinstance(y_val, list) else [y_val]}
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
keyframe["i"] = {"x": x_val, "y": y_val}
|
||
|
|
|
||
|
|
if hasattr(kf, 'out_tan') and kf.out_tan:
|
||
|
|
# out_tan
|
||
|
|
x_val = kf.out_tan['x']
|
||
|
|
y_val = kf.out_tan['y']
|
||
|
|
|
||
|
|
#
|
||
|
|
if isinstance(x_val, list) and len(x_val) == 1:
|
||
|
|
x_val = x_val[0]
|
||
|
|
if isinstance(y_val, list) and len(y_val) == 1:
|
||
|
|
y_val = y_val[0]
|
||
|
|
|
||
|
|
#
|
||
|
|
if isinstance(x_val, list) or isinstance(y_val, list):
|
||
|
|
keyframe["o"] = {"x": x_val if isinstance(x_val, list) else [x_val],
|
||
|
|
"y": y_val if isinstance(y_val, list) else [y_val]}
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
keyframe["o"] = {"x": x_val, "y": y_val}
|
||
|
|
|
||
|
|
keyframes.append(keyframe)
|
||
|
|
|
||
|
|
ks["k"] = keyframes
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
bezier = path.shape.value
|
||
|
|
|
||
|
|
ks["k"] = {
|
||
|
|
"i": [[t.x, t.y] for t in bezier.in_tangents],
|
||
|
|
"o": [[t.x, t.y] for t in bezier.out_tangents],
|
||
|
|
"v": [[v.x, v.y] for v in bezier.vertices],
|
||
|
|
"c": bezier.closed
|
||
|
|
}
|
||
|
|
|
||
|
|
json_data["ks"] = ks
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
def ellipse_to_json(ellipse):
|
||
|
|
"""JSON"""
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.ELLIPSE,
|
||
|
|
"nm": ellipse.name,
|
||
|
|
"ix": ellipse.property_index,
|
||
|
|
"d": 1,
|
||
|
|
"mn": "ADBE Vector Shape - Ellipse",
|
||
|
|
"hd": False
|
||
|
|
}
|
||
|
|
|
||
|
|
#
|
||
|
|
if hasattr(ellipse, "size") and ellipse.size is not None:
|
||
|
|
#
|
||
|
|
is_animated = hasattr(ellipse.size, 'animated') and ellipse.size.animated
|
||
|
|
|
||
|
|
if is_animated and hasattr(ellipse.size, "value") and hasattr(ellipse.size.value, "components"):
|
||
|
|
# keyframes
|
||
|
|
components = ellipse.size.value.components
|
||
|
|
if isinstance(components, list) and len(components) > 0 and isinstance(components[0], dict):
|
||
|
|
json_data["s"] = {
|
||
|
|
"a": 1,
|
||
|
|
"k": components, # componentskeyframe dict
|
||
|
|
"ix": 2
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
size_value = [components[0], components[1]] if len(components) >= 2 else [723.001, 723.001]
|
||
|
|
json_data["s"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": size_value,
|
||
|
|
"ix": 2
|
||
|
|
}
|
||
|
|
elif hasattr(ellipse.size, "value"):
|
||
|
|
if hasattr(ellipse.size.value, "components"):
|
||
|
|
components = ellipse.size.value.components
|
||
|
|
size_value = [components[0], components[1]] if len(components) >= 2 else [723.001, 723.001]
|
||
|
|
elif hasattr(ellipse.size.value, "x") and hasattr(ellipse.size.value, "y"):
|
||
|
|
size_value = [ellipse.size.value.x, ellipse.size.value.y]
|
||
|
|
else:
|
||
|
|
size_value = [723.001, 723.001]
|
||
|
|
json_data["s"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": size_value,
|
||
|
|
"ix": 2
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
size_value = [723.001, 723.001] #
|
||
|
|
json_data["s"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": size_value,
|
||
|
|
"ix": 2
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
json_data["s"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": [723.001, 723.001],
|
||
|
|
"ix": 2
|
||
|
|
}
|
||
|
|
|
||
|
|
#
|
||
|
|
if hasattr(ellipse, "position") and ellipse.position is not None:
|
||
|
|
#
|
||
|
|
is_animated = hasattr(ellipse.position, 'animated') and ellipse.position.animated
|
||
|
|
|
||
|
|
if is_animated and hasattr(ellipse.position, "value") and hasattr(ellipse.position.value, "components"):
|
||
|
|
# keyframes
|
||
|
|
components = ellipse.position.value.components
|
||
|
|
if isinstance(components, list) and len(components) > 0 and isinstance(components[0], dict):
|
||
|
|
json_data["p"] = {
|
||
|
|
"a": 1,
|
||
|
|
"k": components,
|
||
|
|
"ix": 3
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
pos_value = [components[0], components[1]] if len(components) >= 2 else [0, 0]
|
||
|
|
json_data["p"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": pos_value,
|
||
|
|
"ix": 3
|
||
|
|
}
|
||
|
|
elif hasattr(ellipse.position, "value"):
|
||
|
|
if hasattr(ellipse.position.value, "components"):
|
||
|
|
components = ellipse.position.value.components
|
||
|
|
pos_value = [components[0], components[1]] if len(components) >= 2 else [0, 0]
|
||
|
|
elif hasattr(ellipse.position.value, "x") and hasattr(ellipse.position.value, "y"):
|
||
|
|
pos_value = [ellipse.position.value.x, ellipse.position.value.y]
|
||
|
|
else:
|
||
|
|
pos_value = [0, 0]
|
||
|
|
json_data["p"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": pos_value,
|
||
|
|
"ix": 3
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
pos_value = [0, 0] #
|
||
|
|
json_data["p"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": pos_value,
|
||
|
|
"ix": 3
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
json_data["p"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": [0, 0],
|
||
|
|
"ix": 3
|
||
|
|
}
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
def star_to_json(star):
|
||
|
|
"""JSON"""
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.STAR,
|
||
|
|
"nm": star.name,
|
||
|
|
"ix": star.property_index,
|
||
|
|
"mn": "ADBE Vector Shape - Star",
|
||
|
|
"hd": False
|
||
|
|
}
|
||
|
|
|
||
|
|
# direction
|
||
|
|
if hasattr(star, 'direction'):
|
||
|
|
json_data["d"] = star.direction
|
||
|
|
else:
|
||
|
|
json_data["d"] = 1
|
||
|
|
|
||
|
|
# star_type
|
||
|
|
if hasattr(star, 'star_type'):
|
||
|
|
json_data["sy"] = star.star_type.value if hasattr(star.star_type, 'value') else star.star_type
|
||
|
|
else:
|
||
|
|
json_data["sy"] = 1 # Star
|
||
|
|
|
||
|
|
# ix
|
||
|
|
if hasattr(star, "position") and star.position:
|
||
|
|
p_json = property_to_json(star.position, [0, 0])
|
||
|
|
if hasattr(star, "position_ix"):
|
||
|
|
p_json["ix"] = star.position_ix
|
||
|
|
else:
|
||
|
|
p_json["ix"] = 3
|
||
|
|
json_data["p"] = p_json
|
||
|
|
|
||
|
|
# ix
|
||
|
|
if hasattr(star, "inner_radius") and star.inner_radius:
|
||
|
|
ir_json = property_to_json(star.inner_radius, 0)
|
||
|
|
if hasattr(star, "inner_radius_ix"):
|
||
|
|
ir_json["ix"] = star.inner_radius_ix
|
||
|
|
else:
|
||
|
|
ir_json["ix"] = 6
|
||
|
|
json_data["ir"] = ir_json
|
||
|
|
|
||
|
|
# ix
|
||
|
|
if hasattr(star, "outer_radius") and star.outer_radius:
|
||
|
|
or_json = property_to_json(star.outer_radius, 0)
|
||
|
|
if hasattr(star, "outer_radius_ix"):
|
||
|
|
or_json["ix"] = star.outer_radius_ix
|
||
|
|
else:
|
||
|
|
or_json["ix"] = 7
|
||
|
|
json_data["or"] = or_json
|
||
|
|
|
||
|
|
# ix
|
||
|
|
if hasattr(star, "inner_roundness") and star.inner_roundness:
|
||
|
|
is_json = property_to_json(star.inner_roundness, 0)
|
||
|
|
if hasattr(star, "inner_roundness_ix"):
|
||
|
|
is_json["ix"] = star.inner_roundness_ix
|
||
|
|
else:
|
||
|
|
is_json["ix"] = 10
|
||
|
|
json_data["is"] = is_json
|
||
|
|
|
||
|
|
# ix
|
||
|
|
if hasattr(star, "outer_roundness") and star.outer_roundness:
|
||
|
|
os_json = property_to_json(star.outer_roundness, 0)
|
||
|
|
if hasattr(star, "outer_roundness_ix"):
|
||
|
|
os_json["ix"] = star.outer_roundness_ix
|
||
|
|
else:
|
||
|
|
os_json["ix"] = 11
|
||
|
|
json_data["os"] = os_json
|
||
|
|
|
||
|
|
# ix
|
||
|
|
if hasattr(star, "points") and star.points:
|
||
|
|
pt_json = property_to_json(star.points, 5)
|
||
|
|
if hasattr(star, "points_ix"):
|
||
|
|
pt_json["ix"] = star.points_ix
|
||
|
|
else:
|
||
|
|
pt_json["ix"] = 1
|
||
|
|
json_data["pt"] = pt_json
|
||
|
|
|
||
|
|
# ix
|
||
|
|
if hasattr(star, "rotation") and star.rotation:
|
||
|
|
r_json = property_to_json(star.rotation, 0)
|
||
|
|
if hasattr(star, "rotation_ix"):
|
||
|
|
r_json["ix"] = star.rotation_ix
|
||
|
|
else:
|
||
|
|
r_json["ix"] = 4
|
||
|
|
json_data["r"] = r_json
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
def repeater_to_json(repeater):
|
||
|
|
"""JSON - ix"""
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.REPEATER,
|
||
|
|
"nm": repeater.name,
|
||
|
|
"ix": repeater.property_index,
|
||
|
|
"mn": "ADBE Vector Filter - Repeater",
|
||
|
|
"hd": False
|
||
|
|
}
|
||
|
|
|
||
|
|
# - ix
|
||
|
|
if hasattr(repeater, "copies") and repeater.copies:
|
||
|
|
copies_json = property_to_json(repeater.copies, 1)
|
||
|
|
if hasattr(repeater, "copies_ix"):
|
||
|
|
copies_json["ix"] = repeater.copies_ix
|
||
|
|
json_data["c"] = copies_json
|
||
|
|
|
||
|
|
# - ix
|
||
|
|
if hasattr(repeater, "offset") and repeater.offset:
|
||
|
|
offset_json = property_to_json(repeater.offset, 0)
|
||
|
|
if hasattr(repeater, "offset_ix"):
|
||
|
|
offset_json["ix"] = repeater.offset_ix
|
||
|
|
json_data["o"] = offset_json
|
||
|
|
|
||
|
|
# composite mode
|
||
|
|
if hasattr(repeater, "composite"):
|
||
|
|
json_data["m"] = repeater.composite
|
||
|
|
|
||
|
|
# - ix
|
||
|
|
if hasattr(repeater, "transform") and repeater.transform:
|
||
|
|
tr_json = {}
|
||
|
|
|
||
|
|
# ty
|
||
|
|
if hasattr(repeater.transform, "type"):
|
||
|
|
tr_json["ty"] = repeater.transform.type
|
||
|
|
else:
|
||
|
|
tr_json["ty"] = "tr"
|
||
|
|
|
||
|
|
# positionix
|
||
|
|
if hasattr(repeater.transform, "position"):
|
||
|
|
p_json = property_to_json(repeater.transform.position, [0, 0])
|
||
|
|
if hasattr(repeater.transform, "position_ix"):
|
||
|
|
p_json["ix"] = repeater.transform.position_ix
|
||
|
|
tr_json["p"] = p_json
|
||
|
|
|
||
|
|
# anchorix
|
||
|
|
if hasattr(repeater.transform, "anchor_point"):
|
||
|
|
a_json = property_to_json(repeater.transform.anchor, [0, 0])
|
||
|
|
if hasattr(repeater.transform, "anchor_ix"):
|
||
|
|
a_json["ix"] = repeater.transform.anchor_ix
|
||
|
|
tr_json["a"] = a_json
|
||
|
|
|
||
|
|
# scaleix
|
||
|
|
if hasattr(repeater.transform, "scale"):
|
||
|
|
s_json = property_to_json(repeater.transform.scale, [100, 100])
|
||
|
|
if hasattr(repeater.transform, "scale_ix"):
|
||
|
|
s_json["ix"] = repeater.transform.scale_ix
|
||
|
|
tr_json["s"] = s_json
|
||
|
|
|
||
|
|
# rotationix
|
||
|
|
if hasattr(repeater.transform, "rotation"):
|
||
|
|
r_json = property_to_json(repeater.transform.rotation, 0)
|
||
|
|
if hasattr(repeater.transform, "rotation_ix"):
|
||
|
|
r_json["ix"] = repeater.transform.rotation_ix
|
||
|
|
tr_json["r"] = r_json
|
||
|
|
|
||
|
|
# start_opacityix
|
||
|
|
if hasattr(repeater.transform, "start_opacity"):
|
||
|
|
so_json = property_to_json(repeater.transform.start_opacity, 100)
|
||
|
|
if hasattr(repeater.transform, "start_opacity_ix"):
|
||
|
|
so_json["ix"] = repeater.transform.start_opacity_ix
|
||
|
|
tr_json["so"] = so_json
|
||
|
|
|
||
|
|
# end_opacityix
|
||
|
|
if hasattr(repeater.transform, "end_opacity"):
|
||
|
|
eo_json = property_to_json(repeater.transform.end_opacity, 100)
|
||
|
|
if hasattr(repeater.transform, "end_opacity_ix"):
|
||
|
|
eo_json["ix"] = repeater.transform.end_opacity_ix
|
||
|
|
tr_json["eo"] = eo_json
|
||
|
|
|
||
|
|
# name
|
||
|
|
if hasattr(repeater.transform, "name"):
|
||
|
|
tr_json["nm"] = repeater.transform.name
|
||
|
|
|
||
|
|
json_data["tr"] = tr_json
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
def property_to_json(prop, default_value, *args):
|
||
|
|
"""Fixed: Handle separated properties, expressions, spatial interpolation, and e field"""
|
||
|
|
extra_params = {}
|
||
|
|
for i in range(0, len(args), 2):
|
||
|
|
if i + 1 < len(args):
|
||
|
|
extra_params[args[i]] = args[i+1]
|
||
|
|
|
||
|
|
# Handle separated properties
|
||
|
|
if hasattr(prop, "separated") and prop.separated:
|
||
|
|
json_data = {"s": True}
|
||
|
|
|
||
|
|
# Check for expression on the separated property itself
|
||
|
|
if hasattr(prop, "expression") and prop.expression:
|
||
|
|
json_data["x"] = prop.expression
|
||
|
|
|
||
|
|
# Calculate correct default values for separated components
|
||
|
|
if isinstance(default_value, list):
|
||
|
|
x_default = default_value[0] if len(default_value) > 0 else 0
|
||
|
|
y_default = default_value[1] if len(default_value) > 1 else 0
|
||
|
|
z_default = default_value[2] if len(default_value) > 2 else 0
|
||
|
|
else:
|
||
|
|
x_default = y_default = z_default = default_value
|
||
|
|
|
||
|
|
# Process separated x component (only if it exists as a dict property)
|
||
|
|
if hasattr(prop, "x") and prop.x is not None and not isinstance(prop.x, str):
|
||
|
|
x_json = property_to_json(prop.x, x_default)
|
||
|
|
if hasattr(prop, 'x_ix'):
|
||
|
|
x_json["ix"] = prop.x_ix
|
||
|
|
else:
|
||
|
|
x_json["ix"] = 3
|
||
|
|
json_data["x"] = x_json
|
||
|
|
|
||
|
|
if hasattr(prop, "y") and prop.y is not None:
|
||
|
|
y_json = property_to_json(prop.y, y_default)
|
||
|
|
if hasattr(prop, 'y_ix'):
|
||
|
|
y_json["ix"] = prop.y_ix
|
||
|
|
else:
|
||
|
|
y_json["ix"] = 4
|
||
|
|
json_data["y"] = y_json
|
||
|
|
|
||
|
|
if hasattr(prop, "z") and prop.z is not None:
|
||
|
|
z_json = property_to_json(prop.z, z_default)
|
||
|
|
if hasattr(prop, 'z_ix'):
|
||
|
|
z_json["ix"] = prop.z_ix
|
||
|
|
else:
|
||
|
|
z_json["ix"] = 5
|
||
|
|
json_data["z"] = z_json
|
||
|
|
|
||
|
|
# Add animated flag if present
|
||
|
|
if hasattr(prop, "animated") and prop.animated:
|
||
|
|
json_data["a"] = 1
|
||
|
|
|
||
|
|
# Preserve the main ix value
|
||
|
|
if hasattr(prop, "ix") and prop.ix is not None:
|
||
|
|
json_data["ix"] = prop.ix
|
||
|
|
elif "ix" in extra_params:
|
||
|
|
json_data["ix"] = extra_params["ix"]
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
# Handle keyframe animation (non-separated)
|
||
|
|
if hasattr(prop, "keyframes") and prop.keyframes:
|
||
|
|
keyframes = []
|
||
|
|
for kf in prop.keyframes:
|
||
|
|
keyframe = {"t": kf.time}
|
||
|
|
|
||
|
|
# Format value
|
||
|
|
if isinstance(kf.value, list):
|
||
|
|
keyframe["s"] = kf.value
|
||
|
|
elif hasattr(kf.value, "components"):
|
||
|
|
keyframe["s"] = list(kf.value.components)
|
||
|
|
else:
|
||
|
|
keyframe["s"] = [kf.value]
|
||
|
|
|
||
|
|
# Add e field (end value) if it exists
|
||
|
|
if hasattr(kf, "e") and kf.e is not None:
|
||
|
|
if isinstance(kf.e, list):
|
||
|
|
keyframe["e"] = kf.e
|
||
|
|
elif hasattr(kf.e, "components"):
|
||
|
|
keyframe["e"] = list(kf.e.components)
|
||
|
|
else:
|
||
|
|
keyframe["e"] = [kf.e]
|
||
|
|
|
||
|
|
# Add h attribute only if it exists and is not None
|
||
|
|
if hasattr(kf, "h") and kf.h is not None:
|
||
|
|
keyframe["h"] = kf.h
|
||
|
|
|
||
|
|
# Add spatial interpolation (to/ti)
|
||
|
|
if hasattr(kf, "to") and kf.to:
|
||
|
|
keyframe["to"] = kf.to
|
||
|
|
if hasattr(kf, "ti") and kf.ti:
|
||
|
|
keyframe["ti"] = kf.ti
|
||
|
|
|
||
|
|
# Add n attribute
|
||
|
|
if hasattr(kf, "n") and kf.n is not None:
|
||
|
|
keyframe["n"] = kf.n
|
||
|
|
|
||
|
|
# Add easing - DO NOT wrap in list unless already a list
|
||
|
|
if hasattr(kf, "in_tan") and kf.in_tan:
|
||
|
|
x_val = kf.in_tan["x"]
|
||
|
|
y_val = kf.in_tan["y"]
|
||
|
|
# Only wrap in list if the value isn't already a list
|
||
|
|
if not isinstance(x_val, list):
|
||
|
|
x_val = x_val
|
||
|
|
if not isinstance(y_val, list):
|
||
|
|
y_val = y_val
|
||
|
|
keyframe["i"] = {"x": x_val, "y": y_val}
|
||
|
|
|
||
|
|
if hasattr(kf, "out_tan") and kf.out_tan:
|
||
|
|
x_val = kf.out_tan["x"]
|
||
|
|
y_val = kf.out_tan["y"]
|
||
|
|
# Only wrap in list if the value isn't already a list
|
||
|
|
if not isinstance(x_val, list):
|
||
|
|
x_val = x_val
|
||
|
|
if not isinstance(y_val, list):
|
||
|
|
y_val = y_val
|
||
|
|
keyframe["o"] = {"x": x_val, "y": y_val}
|
||
|
|
|
||
|
|
|
||
|
|
keyframes.append(keyframe)
|
||
|
|
|
||
|
|
json_data = {"a": 1, "k": keyframes}
|
||
|
|
|
||
|
|
else:
|
||
|
|
# Static property
|
||
|
|
if hasattr(prop, "value"):
|
||
|
|
value = prop.value
|
||
|
|
|
||
|
|
if hasattr(value, "components"):
|
||
|
|
k_value = list(value.components)
|
||
|
|
elif hasattr(value, "r") and hasattr(value, "g") and hasattr(value, "b"):
|
||
|
|
k_value = [value.r, value.g, value.b]
|
||
|
|
if hasattr(value, "a"):
|
||
|
|
k_value.append(value.a)
|
||
|
|
elif isinstance(value, list):
|
||
|
|
k_value = value
|
||
|
|
else:
|
||
|
|
k_value = value
|
||
|
|
|
||
|
|
# Check if animated flag is set
|
||
|
|
is_animated = 0
|
||
|
|
if hasattr(prop, 'animated') and prop.animated:
|
||
|
|
is_animated = 1
|
||
|
|
elif hasattr(prop, 'expression') and prop.expression:
|
||
|
|
is_animated = 1
|
||
|
|
|
||
|
|
json_data = {"a": is_animated, "k": k_value}
|
||
|
|
else:
|
||
|
|
json_data = {"a": 0, "k": default_value}
|
||
|
|
|
||
|
|
# Add expression if exists (for non-separated properties)
|
||
|
|
if hasattr(prop, "expression") and prop.expression and not hasattr(prop, "separated"):
|
||
|
|
json_data["x"] = prop.expression
|
||
|
|
|
||
|
|
# Add 'l' parameter for 2D layer transforms
|
||
|
|
if "l" in extra_params:
|
||
|
|
json_data["l"] = extra_params["l"]
|
||
|
|
|
||
|
|
# Add 'ix' only if explicitly requested
|
||
|
|
if "ix" in extra_params:
|
||
|
|
json_data["ix"] = extra_params["ix"]
|
||
|
|
|
||
|
|
# Add 'a' parameter if specified
|
||
|
|
if "a" in extra_params:
|
||
|
|
json_data["a"] = extra_params["a"]
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
def stroke_to_json(stroke):
|
||
|
|
"""JSON"""
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.STROKE,
|
||
|
|
"bm": 0,
|
||
|
|
"nm": stroke.name,
|
||
|
|
"mn": "ADBE Vector Graphic - Stroke",
|
||
|
|
"hd": False,
|
||
|
|
}
|
||
|
|
|
||
|
|
# Color -
|
||
|
|
|
||
|
|
# FIX: Color - properly handle animated colors with keyframes
|
||
|
|
if hasattr(stroke.color, 'keyframes') and stroke.color.keyframes:
|
||
|
|
# Animated color
|
||
|
|
keyframes = []
|
||
|
|
for kf in stroke.color.keyframes:
|
||
|
|
# Respect original dimensions
|
||
|
|
color_dim = getattr(stroke, 'color_dimensions', 4)
|
||
|
|
if color_dim == 2:
|
||
|
|
s_data = [kf.value.r, kf.value.g]
|
||
|
|
elif color_dim == 3:
|
||
|
|
s_data = [kf.value.r, kf.value.g, kf.value.b]
|
||
|
|
else:
|
||
|
|
s_data = [kf.value.r, kf.value.g, kf.value.b, 1]
|
||
|
|
|
||
|
|
kf_data = {
|
||
|
|
"s": s_data,
|
||
|
|
"t": kf.time
|
||
|
|
}
|
||
|
|
|
||
|
|
# Add easing if present
|
||
|
|
if hasattr(kf, 'in_tan') and kf.in_tan:
|
||
|
|
kf_data["i"] = kf.in_tan
|
||
|
|
if hasattr(kf, 'out_tan') and kf.out_tan:
|
||
|
|
kf_data["o"] = kf.out_tan
|
||
|
|
|
||
|
|
keyframes.append(kf_data)
|
||
|
|
|
||
|
|
# Output with "k" containing keyframes array directly (no "a" field needed when animated)
|
||
|
|
json_data["c"] = {"k": keyframes}
|
||
|
|
|
||
|
|
# Add ix if originally present
|
||
|
|
if hasattr(stroke, 'has_c_ix') and stroke.has_c_ix:
|
||
|
|
json_data["c"]["ix"] = getattr(stroke, 'c_ix', 3)
|
||
|
|
else:
|
||
|
|
# Static color
|
||
|
|
color_dim = getattr(stroke, 'color_dimensions', 3)
|
||
|
|
has_c_a = getattr(stroke, 'has_c_a', False)
|
||
|
|
has_c_ix = getattr(stroke, 'has_c_ix', False)
|
||
|
|
|
||
|
|
color_array = []
|
||
|
|
if color_dim == 1:
|
||
|
|
color_array = [stroke.color.value.r]
|
||
|
|
elif color_dim == 2:
|
||
|
|
color_array = [stroke.color.value.r, stroke.color.value.g]
|
||
|
|
elif color_dim == 3:
|
||
|
|
color_array = [stroke.color.value.r, stroke.color.value.g, stroke.color.value.b]
|
||
|
|
else: # 4 or more
|
||
|
|
color_array = [stroke.color.value.r, stroke.color.value.g, stroke.color.value.b, 1]
|
||
|
|
|
||
|
|
json_data["c"] = {"k": color_array}
|
||
|
|
if has_c_a:
|
||
|
|
json_data["c"]["a"] = 0
|
||
|
|
if has_c_ix:
|
||
|
|
json_data["c"]["ix"] = getattr(stroke, 'c_ix', 3)
|
||
|
|
|
||
|
|
|
||
|
|
# Opacity - FIX: Handle animated opacity
|
||
|
|
if hasattr(stroke.opacity, 'keyframes') and stroke.opacity.keyframes:
|
||
|
|
# Animated opacity
|
||
|
|
keyframes = []
|
||
|
|
for kf in stroke.opacity.keyframes:
|
||
|
|
kf_data = {
|
||
|
|
"t": kf.time,
|
||
|
|
"s": [kf.value] # Opacity value as single-element array
|
||
|
|
}
|
||
|
|
|
||
|
|
# Add easing parameters if present
|
||
|
|
if hasattr(kf, 'in_tan') and kf.in_tan:
|
||
|
|
if isinstance(kf.in_tan, dict):
|
||
|
|
kf_data["i"] = {
|
||
|
|
"x": [kf.in_tan.get('x', 0.833)],
|
||
|
|
"y": [kf.in_tan.get('y', 0.833)]
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
kf_data["i"] = kf.in_tan
|
||
|
|
|
||
|
|
if hasattr(kf, 'out_tan') and kf.out_tan:
|
||
|
|
if isinstance(kf.out_tan, dict):
|
||
|
|
kf_data["o"] = {
|
||
|
|
"x": [kf.out_tan.get('x', 0.167)],
|
||
|
|
"y": [kf.out_tan.get('y', 0.167)]
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
kf_data["o"] = kf.out_tan
|
||
|
|
|
||
|
|
keyframes.append(kf_data)
|
||
|
|
|
||
|
|
json_data["o"] = {
|
||
|
|
"a": 1,
|
||
|
|
"k": keyframes,
|
||
|
|
"ix": 4
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
# Static opacity
|
||
|
|
json_data["o"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": stroke.opacity.value,
|
||
|
|
"ix": 4
|
||
|
|
}
|
||
|
|
|
||
|
|
# Width - FIX: Handle animated width
|
||
|
|
if hasattr(stroke.width, 'keyframes') and stroke.width.keyframes:
|
||
|
|
# Animated width
|
||
|
|
keyframes = []
|
||
|
|
for kf in stroke.width.keyframes:
|
||
|
|
kf_data = {
|
||
|
|
"t": kf.time,
|
||
|
|
"s": [kf.value] # Width value as single-element array
|
||
|
|
}
|
||
|
|
|
||
|
|
# Add easing parameters if present
|
||
|
|
if hasattr(kf, 'in_tan') and kf.in_tan:
|
||
|
|
if isinstance(kf.in_tan, dict):
|
||
|
|
kf_data["i"] = {
|
||
|
|
"x": [kf.in_tan.get('x', 0.833)],
|
||
|
|
"y": [kf.in_tan.get('y', 0.833)]
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
kf_data["i"] = kf.in_tan
|
||
|
|
|
||
|
|
if hasattr(kf, 'out_tan') and kf.out_tan:
|
||
|
|
if isinstance(kf.out_tan, dict):
|
||
|
|
kf_data["o"] = {
|
||
|
|
"x": [kf.out_tan.get('x', 0.167)],
|
||
|
|
"y": [kf.out_tan.get('y', 0.167)]
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
kf_data["o"] = kf.out_tan
|
||
|
|
|
||
|
|
keyframes.append(kf_data)
|
||
|
|
|
||
|
|
json_data["w"] = {
|
||
|
|
"a": 1,
|
||
|
|
"k": keyframes,
|
||
|
|
"ix": 5
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
# Static width
|
||
|
|
json_data["w"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": stroke.width.value,
|
||
|
|
"ix": 5
|
||
|
|
}
|
||
|
|
|
||
|
|
# Line cap and join
|
||
|
|
json_data["lc"] = stroke.line_cap.value if hasattr(stroke, "line_cap") and stroke.line_cap else 2
|
||
|
|
json_data["lj"] = stroke.line_join.value if hasattr(stroke, "line_join") and stroke.line_join else 2
|
||
|
|
|
||
|
|
# Miter limit - FIX: Output ml whenever it exists, regardless of line_join value
|
||
|
|
if hasattr(stroke, "miter_limit") and stroke.miter_limit is not None:
|
||
|
|
json_data["ml"] = stroke.miter_limit
|
||
|
|
|
||
|
|
# ml2
|
||
|
|
if hasattr(stroke, "ml2") and stroke.ml2 is not None:
|
||
|
|
if isinstance(stroke.ml2, Value):
|
||
|
|
if hasattr(stroke.ml2, 'keyframes') and stroke.ml2.keyframes:
|
||
|
|
# Animated ml2
|
||
|
|
keyframes = []
|
||
|
|
for kf in stroke.ml2.keyframes:
|
||
|
|
kf_data = {
|
||
|
|
"t": kf.time,
|
||
|
|
"s": [kf.value] if not isinstance(kf.value, list) else kf.value
|
||
|
|
}
|
||
|
|
|
||
|
|
if hasattr(kf, 'in_tan') and kf.in_tan:
|
||
|
|
kf_data["i"] = kf.in_tan
|
||
|
|
if hasattr(kf, 'out_tan') and kf.out_tan:
|
||
|
|
kf_data["o"] = kf.out_tan
|
||
|
|
|
||
|
|
keyframes.append(kf_data)
|
||
|
|
|
||
|
|
ml2_json = {"a": 1, "k": keyframes}
|
||
|
|
else:
|
||
|
|
ml2_json = {"a": 0, "k": stroke.ml2.value}
|
||
|
|
|
||
|
|
if hasattr(stroke, 'ml2_ix') and stroke.ml2_ix is not None:
|
||
|
|
ml2_json["ix"] = stroke.ml2_ix
|
||
|
|
|
||
|
|
json_data["ml2"] = ml2_json
|
||
|
|
else:
|
||
|
|
json_data["ml2"] = stroke.ml2
|
||
|
|
|
||
|
|
# Dashes - FIX: Handle animated dash lengths
|
||
|
|
if hasattr(stroke, "dashes") and stroke.dashes:
|
||
|
|
dash_array = []
|
||
|
|
for i, dash in enumerate(stroke.dashes):
|
||
|
|
dash_item = {"n": dash.type.value if hasattr(dash.type, 'value') else dash.type}
|
||
|
|
|
||
|
|
# Check if dash length is animated
|
||
|
|
if hasattr(dash.length, 'keyframes') and dash.length.keyframes:
|
||
|
|
# Animated dash length
|
||
|
|
keyframes = []
|
||
|
|
for kf in dash.length.keyframes:
|
||
|
|
kf_data = {
|
||
|
|
"t": kf.time,
|
||
|
|
"s": [kf.value]
|
||
|
|
}
|
||
|
|
|
||
|
|
# ADD: Include hold property if present
|
||
|
|
if hasattr(kf, 'hold'):
|
||
|
|
kf_data["h"] = kf.hold
|
||
|
|
|
||
|
|
# Only add easing if not a hold keyframe
|
||
|
|
if not hasattr(kf, 'hold') or not kf.hold:
|
||
|
|
if hasattr(kf, 'in_tan') and kf.in_tan:
|
||
|
|
if isinstance(kf.in_tan, dict):
|
||
|
|
kf_data["i"] = {
|
||
|
|
"x": [kf.in_tan.get('x', 0.833)],
|
||
|
|
"y": [kf.in_tan.get('y', 0.833)]
|
||
|
|
}
|
||
|
|
|
||
|
|
if hasattr(kf, 'out_tan') and kf.out_tan:
|
||
|
|
if isinstance(kf.out_tan, dict):
|
||
|
|
kf_data["o"] = {
|
||
|
|
"x": [kf.out_tan.get('x', 0.167)],
|
||
|
|
"y": [kf.out_tan.get('y', 0.167)]
|
||
|
|
}
|
||
|
|
|
||
|
|
keyframes.append(kf_data)
|
||
|
|
|
||
|
|
dash_item["v"] = {
|
||
|
|
"a": 1,
|
||
|
|
"k": keyframes
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
# Static dash length
|
||
|
|
dash_item["v"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": dash.length.value if hasattr(dash.length, 'value') else dash.length
|
||
|
|
}
|
||
|
|
|
||
|
|
# Add name
|
||
|
|
if hasattr(dash, 'name') and dash.name:
|
||
|
|
dash_item["nm"] = dash.name
|
||
|
|
else:
|
||
|
|
if dash.type == StrokeDashType.Dash or dash.type == "d":
|
||
|
|
dash_item["nm"] = "dash"
|
||
|
|
elif dash.type == StrokeDashType.Gap or dash.type == "g":
|
||
|
|
dash_item["nm"] = "gap"
|
||
|
|
elif dash.type == StrokeDashType.Offset or dash.type == "o":
|
||
|
|
dash_item["nm"] = "offset"
|
||
|
|
|
||
|
|
if hasattr(dash, 'v_ix'):
|
||
|
|
dash_item["v"]["ix"] = dash.v_ix
|
||
|
|
else:
|
||
|
|
dash_item["v"]["ix"] = i + 1
|
||
|
|
|
||
|
|
dash_array.append(dash_item)
|
||
|
|
|
||
|
|
json_data["d"] = dash_array
|
||
|
|
|
||
|
|
# ix
|
||
|
|
if hasattr(stroke, 'property_index') and stroke.property_index is not None:
|
||
|
|
json_data["ix"] = stroke.property_index
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
def fill_to_json(fill):
|
||
|
|
"""Fixed: Support animated colors and variable color dimensions"""
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.FILL,
|
||
|
|
"bm": 0,
|
||
|
|
"nm": fill.name,
|
||
|
|
"mn": getattr(fill, 'mn', "ADBE Vector Graphic - Fill"),
|
||
|
|
"hd": getattr(fill, 'hd', False)
|
||
|
|
}
|
||
|
|
|
||
|
|
# Color -
|
||
|
|
if hasattr(fill.color, 'keyframes') and fill.color.keyframes:
|
||
|
|
#
|
||
|
|
keyframes = []
|
||
|
|
for kf in fill.color.keyframes:
|
||
|
|
#
|
||
|
|
color_dim = getattr(fill, 'color_dimensions', 4)
|
||
|
|
if color_dim == 2:
|
||
|
|
s_data = [kf.value.r, kf.value.g]
|
||
|
|
elif color_dim == 3:
|
||
|
|
s_data = [kf.value.r, kf.value.g, kf.value.b]
|
||
|
|
else:
|
||
|
|
s_data = [kf.value.r, kf.value.g, kf.value.b, 1]
|
||
|
|
|
||
|
|
kf_data = {
|
||
|
|
"t": kf.time,
|
||
|
|
"s": s_data
|
||
|
|
}
|
||
|
|
if hasattr(kf, 'in_tan') and kf.in_tan:
|
||
|
|
kf_data["i"] = kf.in_tan
|
||
|
|
if hasattr(kf, 'out_tan') and kf.out_tan:
|
||
|
|
kf_data["o"] = kf.out_tan
|
||
|
|
keyframes.append(kf_data)
|
||
|
|
|
||
|
|
json_data["c"] = {"a": 1, "k": keyframes}
|
||
|
|
|
||
|
|
# ix
|
||
|
|
if hasattr(fill, 'has_c_ix') and fill.has_c_ix:
|
||
|
|
json_data["c"]["ix"] = getattr(fill, 'c_ix', 4)
|
||
|
|
else:
|
||
|
|
# FIX: Respect actual color dimensions
|
||
|
|
color_dim = getattr(fill, 'color_dimensions', 3)
|
||
|
|
has_c_a = getattr(fill, 'has_c_a', False)
|
||
|
|
has_c_ix = getattr(fill, 'has_c_ix', False)
|
||
|
|
|
||
|
|
color_array = []
|
||
|
|
if color_dim == 1:
|
||
|
|
color_array = [fill.color.value.r]
|
||
|
|
elif color_dim == 2:
|
||
|
|
color_array = [fill.color.value.r, fill.color.value.g]
|
||
|
|
elif color_dim == 3:
|
||
|
|
color_array = [fill.color.value.r, fill.color.value.g, fill.color.value.b]
|
||
|
|
else: # 4 or more
|
||
|
|
color_array = [fill.color.value.r, fill.color.value.g, fill.color.value.b, 1]
|
||
|
|
|
||
|
|
json_data["c"] = {"k": color_array}
|
||
|
|
if has_c_a:
|
||
|
|
json_data["c"]["a"] = 0
|
||
|
|
if has_c_ix:
|
||
|
|
json_data["c"]["ix"] = getattr(fill, 'c_ix', 4)
|
||
|
|
|
||
|
|
# Opacity
|
||
|
|
json_data["o"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": fill.opacity.value,
|
||
|
|
"ix": 5
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check if opacity is animated
|
||
|
|
if hasattr(fill.opacity, 'keyframes') and fill.opacity.keyframes:
|
||
|
|
json_data["o"]["a"] = 1
|
||
|
|
keyframes = []
|
||
|
|
for kf in fill.opacity.keyframes:
|
||
|
|
keyframe = {"t": kf.time, "s": [kf.value]}
|
||
|
|
if hasattr(kf, 'in_tan') and kf.in_tan:
|
||
|
|
keyframe["i"] = {"x": [kf.in_tan['x']], "y": [kf.in_tan['y']]}
|
||
|
|
if hasattr(kf, 'out_tan') and kf.out_tan:
|
||
|
|
keyframe["o"] = {"x": [kf.out_tan['x']], "y": [kf.out_tan['y']]}
|
||
|
|
keyframes.append(keyframe)
|
||
|
|
json_data["o"]["k"] = keyframes
|
||
|
|
|
||
|
|
# Fill rule
|
||
|
|
json_data["r"] = fill.fill_rule.value if hasattr(fill, "fill_rule") and fill.fill_rule is not None else 1
|
||
|
|
|
||
|
|
# Add property index to fill element itself if it exists
|
||
|
|
if hasattr(fill, 'property_index') and fill.property_index is not None:
|
||
|
|
json_data["ix"] = fill.property_index
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
def gradient_fill_to_json(gradient_fill):
|
||
|
|
"""JSON - """
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.GRADIENT_FILL,
|
||
|
|
"nm": gradient_fill.name,
|
||
|
|
"o": {"a": 0, "k": gradient_fill.opacity.value if hasattr(gradient_fill, "opacity") else 100, "ix": 10},
|
||
|
|
"r": gradient_fill.fill_rule.value if hasattr(gradient_fill, "fill_rule") and gradient_fill.fill_rule is not None else 1,
|
||
|
|
"bm": 0,
|
||
|
|
"mn": "ADBE Vector Graphic - G-Fill",
|
||
|
|
"hd": False
|
||
|
|
}
|
||
|
|
|
||
|
|
# Add property index if exists
|
||
|
|
if hasattr(gradient_fill, 'property_index') and gradient_fill.property_index is not None:
|
||
|
|
json_data["ix"] = gradient_fill.property_index
|
||
|
|
|
||
|
|
# Start and end points
|
||
|
|
if hasattr(gradient_fill, "start_point"):
|
||
|
|
json_data["s"] = property_to_json(gradient_fill.start_point, [0, 0], "ix", 5)
|
||
|
|
else:
|
||
|
|
json_data["s"] = {"a": 0, "k": [0, 0], "ix": 5}
|
||
|
|
|
||
|
|
if hasattr(gradient_fill, "end_point"):
|
||
|
|
json_data["e"] = property_to_json(gradient_fill.end_point, [100, 100], "ix", 6)
|
||
|
|
else:
|
||
|
|
json_data["e"] = {"a": 0, "k": [100, 100], "ix": 6}
|
||
|
|
|
||
|
|
# Gradient type
|
||
|
|
gradient_type = 1 # Default linear
|
||
|
|
if hasattr(gradient_fill, "gradient_type"):
|
||
|
|
gradient_type = gradient_fill.gradient_type.value
|
||
|
|
json_data["t"] = gradient_type
|
||
|
|
else:
|
||
|
|
json_data["t"] = 1
|
||
|
|
|
||
|
|
# FIX: Always add h and a attributes if they exist
|
||
|
|
if hasattr(gradient_fill, "highlight_length"):
|
||
|
|
json_data["h"] = property_to_json(gradient_fill.highlight_length, 0, "ix", 7)
|
||
|
|
|
||
|
|
if hasattr(gradient_fill, "highlight_angle"):
|
||
|
|
json_data["a"] = property_to_json(gradient_fill.highlight_angle, 0, "ix", 8)
|
||
|
|
|
||
|
|
# -
|
||
|
|
if hasattr(gradient_fill, "_original_color_array") and gradient_fill._original_color_array:
|
||
|
|
#
|
||
|
|
json_data["g"] = {
|
||
|
|
"p": gradient_fill._color_points,
|
||
|
|
"k": {
|
||
|
|
"a": 0,
|
||
|
|
"k": gradient_fill._original_color_array,
|
||
|
|
"ix": 9
|
||
|
|
}
|
||
|
|
}
|
||
|
|
elif hasattr(gradient_fill, "colors") and gradient_fill.colors is not None:
|
||
|
|
#
|
||
|
|
if hasattr(gradient_fill.colors, "colors"):
|
||
|
|
colors_list = gradient_fill.colors.colors
|
||
|
|
|
||
|
|
if isinstance(colors_list, list) and colors_list:
|
||
|
|
flat_colors = []
|
||
|
|
num_colors = len(colors_list)
|
||
|
|
|
||
|
|
# 4rgb
|
||
|
|
for pos, color in colors_list:
|
||
|
|
flat_colors.extend([pos, color.r, color.g, color.b])
|
||
|
|
|
||
|
|
json_data["g"] = {
|
||
|
|
"p": num_colors,
|
||
|
|
"k": {
|
||
|
|
"a": 0,
|
||
|
|
"k": flat_colors,
|
||
|
|
"ix": 9
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
json_data["g"] = {
|
||
|
|
"p": 3,
|
||
|
|
"k": {
|
||
|
|
"a": 0,
|
||
|
|
"k": [0.0, 0.85, 0.36, 0.33, 0.5, 0.84, 1.0, 0.0, 1.0, 0.85, 0.36, 0.33],
|
||
|
|
"ix": 9
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
json_data["g"] = {
|
||
|
|
"p": 3,
|
||
|
|
"k": {
|
||
|
|
"a": 0,
|
||
|
|
"k": [0.0, 0.85, 0.36, 0.33, 0.5, 0.84, 1.0, 0.0, 1.0, 0.85, 0.36, 0.33],
|
||
|
|
"ix": 9
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
def gradient_stroke_to_json(shape):
|
||
|
|
"""JSON - """
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.GRADIENT_STROKE,
|
||
|
|
"nm": shape.name,
|
||
|
|
"bm": 0,
|
||
|
|
"mn": "ADBE Vector Graphic - G-Stroke",
|
||
|
|
"hd": False
|
||
|
|
}
|
||
|
|
|
||
|
|
# property_index
|
||
|
|
if hasattr(shape, 'property_index') and shape.property_index is not None:
|
||
|
|
json_data["ix"] = shape.property_index
|
||
|
|
|
||
|
|
# - ix
|
||
|
|
json_data["o"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": shape.opacity.value if hasattr(shape, "opacity") else 100,
|
||
|
|
"ix": 9 # ix
|
||
|
|
}
|
||
|
|
|
||
|
|
# - ix
|
||
|
|
json_data["w"] = {
|
||
|
|
"a": 0,
|
||
|
|
"k": shape.width.value if hasattr(shape, "width") else 14, #
|
||
|
|
"ix": 10 # ix
|
||
|
|
}
|
||
|
|
|
||
|
|
# -
|
||
|
|
json_data["lc"] = shape.line_cap.value if hasattr(shape, "line_cap") and shape.line_cap is not None else 1
|
||
|
|
json_data["lj"] = shape.line_join.value if hasattr(shape, "line_join") and shape.line_join is not None else 1
|
||
|
|
|
||
|
|
#
|
||
|
|
if hasattr(shape, "miter_limit") and shape.miter_limit is not None:
|
||
|
|
json_data["ml"] = shape.miter_limit
|
||
|
|
|
||
|
|
# ml2
|
||
|
|
if hasattr(shape, "ml2") and shape.ml2 is not None:
|
||
|
|
if isinstance(shape.ml2, Value):
|
||
|
|
ml2_json = {"a": 0, "k": shape.ml2.value}
|
||
|
|
if hasattr(shape, 'ml2_ix'):
|
||
|
|
ml2_json["ix"] = shape.ml2_ix
|
||
|
|
json_data["ml2"] = ml2_json
|
||
|
|
else:
|
||
|
|
json_data["ml2"] = shape.ml2
|
||
|
|
|
||
|
|
# - ix
|
||
|
|
if hasattr(shape, "start_point"):
|
||
|
|
json_data["s"] = property_to_json(shape.start_point, [0, 0], "ix", 4)
|
||
|
|
else:
|
||
|
|
json_data["s"] = {"a": 0, "k": [0, 0], "ix": 4}
|
||
|
|
|
||
|
|
# - ix
|
||
|
|
if hasattr(shape, "end_point"):
|
||
|
|
json_data["e"] = property_to_json(shape.end_point, [100, 0], "ix", 5) #
|
||
|
|
else:
|
||
|
|
json_data["e"] = {"a": 0, "k": [100, 0], "ix": 5} #
|
||
|
|
|
||
|
|
#
|
||
|
|
json_data["t"] = shape.gradient_type.value if hasattr(shape, "gradient_type") else 1
|
||
|
|
|
||
|
|
#
|
||
|
|
if hasattr(shape, "highlight_length"):
|
||
|
|
json_data["h"] = property_to_json(shape.highlight_length, 0, "ix", 7)
|
||
|
|
else:
|
||
|
|
json_data["h"] = {"a": 0, "k": 0, "ix": 7}
|
||
|
|
|
||
|
|
if hasattr(shape, "highlight_angle"):
|
||
|
|
json_data["a"] = property_to_json(shape.highlight_angle, 0, "ix", 8)
|
||
|
|
else:
|
||
|
|
json_data["a"] = {"a": 0, "k": 0, "ix": 8}
|
||
|
|
|
||
|
|
# -
|
||
|
|
if hasattr(shape, "_original_g_data") and shape._original_g_data:
|
||
|
|
#
|
||
|
|
json_data["g"] = shape._original_g_data
|
||
|
|
elif hasattr(shape, "_original_color_array") and shape._original_color_array:
|
||
|
|
#
|
||
|
|
json_data["g"] = {
|
||
|
|
"p": shape._color_points,
|
||
|
|
"k": {
|
||
|
|
"a": 0,
|
||
|
|
"k": shape._original_color_array,
|
||
|
|
"ix": 8 # 89
|
||
|
|
}
|
||
|
|
}
|
||
|
|
elif hasattr(shape, "colors") and shape.colors:
|
||
|
|
# ... ...
|
||
|
|
json_data["g"] = {
|
||
|
|
"p": len(colors_list) if 'colors_list' in locals() else 3,
|
||
|
|
"k": {
|
||
|
|
"a": 0,
|
||
|
|
"k": flat_colors if 'flat_colors' in locals() else [0, 0, 0, 0, 0.5, 1, 1, 1, 1, 0, 0, 0],
|
||
|
|
"ix": 8 # 89
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
# - 312
|
||
|
|
json_data["g"] = {
|
||
|
|
"p": 3,
|
||
|
|
"k": {
|
||
|
|
"a": 0,
|
||
|
|
"k": [0, 0, 0, 0, 0.5, 1, 1, 1, 1, 0, 0, 0],
|
||
|
|
"ix": 8 # 89
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#
|
||
|
|
if hasattr(shape, "dashes") and shape.dashes:
|
||
|
|
dash_array = []
|
||
|
|
for dash in shape.dashes:
|
||
|
|
dash_item = {
|
||
|
|
"n": dash.type.value,
|
||
|
|
"v": {"a": 0, "k": dash.length.value}
|
||
|
|
}
|
||
|
|
dash_array.append(dash_item)
|
||
|
|
json_data["d"] = dash_array
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
# - JSON
|
||
|
|
def merge_to_json(merge):
|
||
|
|
"""JSON"""
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.MERGE,
|
||
|
|
"nm": merge.name,
|
||
|
|
"mm": merge.merge_mode,
|
||
|
|
"mn": "ADBE Vector Filter - Merge",
|
||
|
|
"hd": False
|
||
|
|
}
|
||
|
|
|
||
|
|
if hasattr(merge, "property_index") and merge.property_index is not None:
|
||
|
|
json_data["ix"] = merge.property_index
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
# - JSON
|
||
|
|
def rounded_corners_to_json(rounded_corners):
|
||
|
|
"""JSON"""
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.ROUNDED_CORNERS,
|
||
|
|
"nm": rounded_corners.name,
|
||
|
|
"mn": "ADBE Vector Filter - RC",
|
||
|
|
"hd": False
|
||
|
|
}
|
||
|
|
|
||
|
|
if hasattr(rounded_corners, "property_index") and rounded_corners.property_index is not None:
|
||
|
|
json_data["ix"] = rounded_corners.property_index
|
||
|
|
|
||
|
|
#
|
||
|
|
if hasattr(rounded_corners, "radius"):
|
||
|
|
json_data["r"] = property_to_json(rounded_corners.radius, 0, "ix", 1)
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
# - JSON
|
||
|
|
def twist_to_json(twist):
|
||
|
|
"""JSON"""
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.TWIST,
|
||
|
|
"nm": twist.name,
|
||
|
|
"mn": "ADBE Vector Filter - Twist",
|
||
|
|
"hd": False
|
||
|
|
}
|
||
|
|
|
||
|
|
if hasattr(twist, "property_index") and twist.property_index is not None:
|
||
|
|
json_data["ix"] = twist.property_index
|
||
|
|
|
||
|
|
#
|
||
|
|
if hasattr(twist, "angle"):
|
||
|
|
json_data["a"] = property_to_json(twist.angle, 0, "ix", 1)
|
||
|
|
|
||
|
|
#
|
||
|
|
if hasattr(twist, "center"):
|
||
|
|
json_data["c"] = property_to_json(twist.center, [0, 0], "ix", 2)
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
def trim_to_json(trim):
|
||
|
|
"""Convert a Trim object to JSON, properly handling animations"""
|
||
|
|
json_data = {
|
||
|
|
"ty": ElementType.TRIM,
|
||
|
|
"nm": trim.name,
|
||
|
|
"ix": trim.property_index,
|
||
|
|
"mn": "ADBE Vector Filter - Trim",
|
||
|
|
"hd": False
|
||
|
|
}
|
||
|
|
|
||
|
|
#
|
||
|
|
if hasattr(trim, "multiple"):
|
||
|
|
json_data["m"] = trim.multiple.value
|
||
|
|
else:
|
||
|
|
json_data["m"] = 1 #
|
||
|
|
|
||
|
|
# Start value
|
||
|
|
if hasattr(trim, "start") and trim.start:
|
||
|
|
json_data["s"] = property_to_json(trim.start, 0, "ix", 1)
|
||
|
|
|
||
|
|
# End value
|
||
|
|
if hasattr(trim, "end") and trim.end:
|
||
|
|
json_data["e"] = property_to_json(trim.end, 100, "ix", 2)
|
||
|
|
|
||
|
|
# Offset value
|
||
|
|
if hasattr(trim, "offset") and trim.offset:
|
||
|
|
json_data["o"] = property_to_json(trim.offset, 0, "ix", 3)
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
def effects_to_json(effects):
|
||
|
|
"""JSON"""
|
||
|
|
if not isinstance(effects, list):
|
||
|
|
effects = [effects] #
|
||
|
|
|
||
|
|
json_effects = []
|
||
|
|
|
||
|
|
for effect in effects:
|
||
|
|
# effect
|
||
|
|
if isinstance(effect, dict) and "ty" in effect:
|
||
|
|
json_effect = {
|
||
|
|
"ty": effect.get("ty", 5),
|
||
|
|
"nm": effect.get("nm", ""),
|
||
|
|
"mn": effect.get("mn", ""),
|
||
|
|
"ix": effect.get("ix", 1),
|
||
|
|
"en": effect.get("en", 1)
|
||
|
|
}
|
||
|
|
|
||
|
|
# np0
|
||
|
|
if "np" in effect and effect["np"] > 0:
|
||
|
|
json_effect["np"] = effect["np"]
|
||
|
|
|
||
|
|
#
|
||
|
|
if "ef" in effect:
|
||
|
|
json_effect["ef"] = effect["ef"]
|
||
|
|
|
||
|
|
json_effects.append(json_effect)
|
||
|
|
|
||
|
|
return json_effects
|
||
|
|
|
||
|
|
|
||
|
|
# JSON
|
||
|
|
def transform_shape_to_json(transform):
|
||
|
|
"""Convert TransformShape to JSON with full separated properties support"""
|
||
|
|
json_data = {
|
||
|
|
"ty": "tr",
|
||
|
|
"nm": transform.name if hasattr(transform, 'name') else "Transform Shape"
|
||
|
|
}
|
||
|
|
|
||
|
|
if hasattr(transform, 'property_index') and transform.property_index is not None:
|
||
|
|
json_data["ix"] = transform.property_index
|
||
|
|
|
||
|
|
if hasattr(transform, 'hd') and transform.hd is not None:
|
||
|
|
json_data["hd"] = transform.hd
|
||
|
|
# Process anchor
|
||
|
|
if hasattr(transform, 'anchor') and transform.anchor is not None:
|
||
|
|
json_data["a"] = property_to_json(transform.anchor, [0, 0])
|
||
|
|
if not hasattr(transform, '_is_shape_transform'):
|
||
|
|
json_data["a"]["ix"] = 1
|
||
|
|
|
||
|
|
# Process position
|
||
|
|
if hasattr(transform, 'position') and transform.position is not None:
|
||
|
|
json_data["p"] = property_to_json(transform.position, [0, 0])
|
||
|
|
if not hasattr(transform, '_is_shape_transform'):
|
||
|
|
json_data["p"]["ix"] = 2
|
||
|
|
|
||
|
|
# Process scale - FIXED: Properly output separated properties
|
||
|
|
if hasattr(transform, 'scale') and transform.scale is not None:
|
||
|
|
if hasattr(transform.scale, 'separated') and transform.scale.separated:
|
||
|
|
# Output separated scale
|
||
|
|
scale_json = {"s": True}
|
||
|
|
|
||
|
|
if hasattr(transform.scale, 'x') and transform.scale.x is not None:
|
||
|
|
scale_json["x"] = property_to_json(transform.scale.x, 100)
|
||
|
|
if hasattr(transform.scale, 'x_ix'):
|
||
|
|
scale_json["x"]["ix"] = transform.scale.x_ix
|
||
|
|
else:
|
||
|
|
scale_json["x"]["ix"] = 3
|
||
|
|
|
||
|
|
if hasattr(transform.scale, 'y') and transform.scale.y is not None:
|
||
|
|
scale_json["y"] = property_to_json(transform.scale.y, 100)
|
||
|
|
if hasattr(transform.scale, 'y_ix'):
|
||
|
|
scale_json["y"]["ix"] = transform.scale.y_ix
|
||
|
|
else:
|
||
|
|
scale_json["y"]["ix"] = 4
|
||
|
|
|
||
|
|
if hasattr(transform.scale, 'z') and transform.scale.z is not None:
|
||
|
|
scale_json["z"] = property_to_json(transform.scale.z, 100)
|
||
|
|
if hasattr(transform.scale, 'z_ix'):
|
||
|
|
scale_json["z"]["ix"] = transform.scale.z_ix
|
||
|
|
else:
|
||
|
|
scale_json["z"]["ix"] = 5
|
||
|
|
|
||
|
|
if hasattr(transform.scale, 'ix'):
|
||
|
|
scale_json["ix"] = transform.scale.ix
|
||
|
|
elif not hasattr(transform, '_is_shape_transform'):
|
||
|
|
scale_json["ix"] = 3
|
||
|
|
|
||
|
|
json_data["s"] = scale_json
|
||
|
|
else:
|
||
|
|
scale_json = property_to_json(transform.scale, [100, 100, 100])
|
||
|
|
if not hasattr(transform, '_is_shape_transform'):
|
||
|
|
scale_json["ix"] = 3
|
||
|
|
json_data["s"] = scale_json
|
||
|
|
|
||
|
|
# Process rotation - FIXED: Handle separated rotation
|
||
|
|
if hasattr(transform, 'rotation') and transform.rotation is not None:
|
||
|
|
if hasattr(transform.rotation, 'separated') and transform.rotation.separated:
|
||
|
|
# Output separated rotation
|
||
|
|
rot_json = {"s": True}
|
||
|
|
|
||
|
|
if hasattr(transform.rotation, 'x') and transform.rotation.x is not None:
|
||
|
|
rot_json["x"] = property_to_json(transform.rotation.x, 0)
|
||
|
|
if hasattr(transform.rotation, 'x_ix'):
|
||
|
|
rot_json["x"]["ix"] = transform.rotation.x_ix
|
||
|
|
|
||
|
|
if hasattr(transform.rotation, 'y') and transform.rotation.y is not None:
|
||
|
|
rot_json["y"] = property_to_json(transform.rotation.y, 0)
|
||
|
|
if hasattr(transform.rotation, 'y_ix'):
|
||
|
|
rot_json["y"]["ix"] = transform.rotation.y_ix
|
||
|
|
|
||
|
|
if hasattr(transform.rotation, 'z') and transform.rotation.z is not None:
|
||
|
|
rot_json["z"] = property_to_json(transform.rotation.z, 0)
|
||
|
|
if hasattr(transform.rotation, 'z_ix'):
|
||
|
|
rot_json["z"]["ix"] = transform.rotation.z_ix
|
||
|
|
|
||
|
|
if hasattr(transform.rotation, 'ix'):
|
||
|
|
rot_json["ix"] = transform.rotation.ix
|
||
|
|
elif not hasattr(transform, '_is_shape_transform'):
|
||
|
|
rot_json["ix"] = 6
|
||
|
|
|
||
|
|
json_data["r"] = rot_json
|
||
|
|
else:
|
||
|
|
json_data["r"] = property_to_json(transform.rotation, 0)
|
||
|
|
if not hasattr(transform, '_is_shape_transform'):
|
||
|
|
json_data["r"]["ix"] = 6
|
||
|
|
|
||
|
|
# Process opacity
|
||
|
|
if hasattr(transform, 'opacity') and transform.opacity is not None:
|
||
|
|
json_data["o"] = property_to_json(transform.opacity, 100)
|
||
|
|
if not hasattr(transform, '_is_shape_transform'):
|
||
|
|
json_data["o"]["ix"] = 7
|
||
|
|
|
||
|
|
# Process skew
|
||
|
|
if hasattr(transform, 'skew') and transform.skew is not None:
|
||
|
|
json_data["sk"] = property_to_json(transform.skew, 0)
|
||
|
|
if not hasattr(transform, '_is_shape_transform'):
|
||
|
|
json_data["sk"]["ix"] = 4
|
||
|
|
|
||
|
|
# Process skew axis
|
||
|
|
if hasattr(transform, 'skew_axis') and transform.skew_axis is not None:
|
||
|
|
json_data["sa"] = property_to_json(transform.skew_axis, 0)
|
||
|
|
if not hasattr(transform, '_is_shape_transform'):
|
||
|
|
json_data["sa"]["ix"] = 5
|
||
|
|
|
||
|
|
return json_data
|
||
|
|
|
||
|
|
|
||
|
|
# To use the function:
|
||
|
|
def from_sequence(sequence_text):
|
||
|
|
"""
|
||
|
|
|
||
|
|
Args:
|
||
|
|
sequence_text
|
||
|
|
Returns:
|
||
|
|
"""
|
||
|
|
lines = sequence_text.strip().split('\n')
|
||
|
|
|
||
|
|
idx = 0
|
||
|
|
animation, idx = parse_animation_tag(lines, idx)
|
||
|
|
return animation
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
def parse_asset_tag(lines, idx):
|
||
|
|
"""asset"""
|
||
|
|
if not lines[idx].startswith('(asset'):
|
||
|
|
raise ValueError(f"Expected asset tag, got: {lines[idx]}")
|
||
|
|
|
||
|
|
asset_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
asset = {
|
||
|
|
"id": asset_attrs.get("id", ""),
|
||
|
|
"layers": []
|
||
|
|
}
|
||
|
|
|
||
|
|
#
|
||
|
|
if "nm" in asset_attrs:
|
||
|
|
asset["nm"] = asset_attrs["nm"]
|
||
|
|
if "fr" in asset_attrs:
|
||
|
|
asset["fr"] = float(asset_attrs["fr"])
|
||
|
|
if "w" in asset_attrs:
|
||
|
|
asset["w"] = float(asset_attrs["w"])
|
||
|
|
if "h" in asset_attrs:
|
||
|
|
asset["h"] = float(asset_attrs["h"])
|
||
|
|
if "u" in asset_attrs:
|
||
|
|
asset["u"] = asset_attrs["u"]
|
||
|
|
if "p" in asset_attrs:
|
||
|
|
asset["p"] = asset_attrs["p"]
|
||
|
|
if "e" in asset_attrs:
|
||
|
|
asset["e"] = int(asset_attrs["e"])
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
if lines[idx].startswith('(layer') or lines[idx].startswith('(shape_layer'):
|
||
|
|
layer, new_idx = parse_layer_tag(lines, idx)
|
||
|
|
# assetlayer
|
||
|
|
if hasattr(layer, '__dict__'):
|
||
|
|
layer._is_asset_layer = True
|
||
|
|
asset["layers"].append(layer)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(null_layer'):
|
||
|
|
layer, new_idx = parse_layer_tag(lines, idx)
|
||
|
|
if hasattr(layer, '__dict__'):
|
||
|
|
layer._is_asset_layer = True
|
||
|
|
asset["layers"].append(layer)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(precomp_layer'):
|
||
|
|
layer, new_idx = parse_precomp_layer_tag(lines, idx)
|
||
|
|
if hasattr(layer, '__dict__'):
|
||
|
|
layer._is_asset_layer = True
|
||
|
|
asset["layers"].append(layer)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(text_layer'): # ADD THIS CASE FOR TEXT LAYERS
|
||
|
|
layer, new_idx = parse_text_layer_tag(lines, idx)
|
||
|
|
if hasattr(layer, '__dict__'):
|
||
|
|
layer._is_asset_layer = True
|
||
|
|
asset["layers"].append(layer)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(solid_layer'):
|
||
|
|
layer, new_idx = parse_solid_layer_tag(lines, idx)
|
||
|
|
if hasattr(layer, '__dict__'):
|
||
|
|
layer._is_asset_layer = True
|
||
|
|
asset["layers"].append(layer)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].strip() == '(/asset)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return asset, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_animation_tag(lines, idx):
|
||
|
|
"""Parse animation tag and its contents"""
|
||
|
|
if not lines[idx].startswith('(animation'):
|
||
|
|
raise ValueError(f"Expected animation tag, got: {lines[idx]}")
|
||
|
|
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
animation = {
|
||
|
|
"v": attrs.get("v", "5.0.0"),
|
||
|
|
"fr": float(attrs.get("fr", 30)),
|
||
|
|
"ip": float(attrs.get("ip", 0)),
|
||
|
|
"op": float(attrs.get("op", 30)),
|
||
|
|
"w": float(attrs.get("w", 360)),
|
||
|
|
"h": float(attrs.get("h", 360)),
|
||
|
|
"nm": attrs.get("nm", "Animation"),
|
||
|
|
"ddd": int(attrs.get("ddd", 0)),
|
||
|
|
"assets": [],
|
||
|
|
"layers": [],
|
||
|
|
"markers": [], # Initialize as empty list
|
||
|
|
"props": {}, # Initialize as empty dict
|
||
|
|
"fonts": None, # fonts
|
||
|
|
"chars": [] # chars
|
||
|
|
}
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
if lines[idx].startswith('(markers'):
|
||
|
|
# Parse markers properly
|
||
|
|
markers_line = lines[idx]
|
||
|
|
start = markers_line.find(' ') + 1
|
||
|
|
end = markers_line.rfind(')')
|
||
|
|
if start > 0 and end > start:
|
||
|
|
markers_content = markers_line[start:end].strip()
|
||
|
|
if markers_content and markers_content != "[]":
|
||
|
|
try:
|
||
|
|
animation["markers"] = json.loads(markers_content)
|
||
|
|
except:
|
||
|
|
animation["markers"] = []
|
||
|
|
else:
|
||
|
|
animation["markers"] = []
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(props'):
|
||
|
|
# Parse props properly
|
||
|
|
props_line = lines[idx]
|
||
|
|
start = props_line.find(' ') + 1
|
||
|
|
end = props_line.rfind(')')
|
||
|
|
if start > 0 and end > start:
|
||
|
|
props_content = props_line[start:end].strip()
|
||
|
|
if props_content and props_content != "{}":
|
||
|
|
try:
|
||
|
|
animation["props"] = json.loads(props_content)
|
||
|
|
except:
|
||
|
|
animation["props"] = {}
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(fonts'):
|
||
|
|
# fonts
|
||
|
|
fonts_list = []
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/fonts'):
|
||
|
|
if lines[idx].startswith('(font'):
|
||
|
|
font_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
font = Font(
|
||
|
|
font_family=font_attrs.get("family", ""),
|
||
|
|
font_style=font_attrs.get("style", "Regular"),
|
||
|
|
name=font_attrs.get("name", "")
|
||
|
|
)
|
||
|
|
if "ascent" in font_attrs:
|
||
|
|
font.ascent = float(font_attrs["ascent"])
|
||
|
|
if "path" in font_attrs:
|
||
|
|
font.path = font_attrs["path"]
|
||
|
|
if "weight" in font_attrs:
|
||
|
|
font.weight = font_attrs["weight"]
|
||
|
|
if "origin" in font_attrs:
|
||
|
|
font.origin = int(font_attrs["origin"])
|
||
|
|
fonts_list.append(font)
|
||
|
|
idx += 1
|
||
|
|
animation["fonts"] = {"list": fonts_list}
|
||
|
|
if lines[idx].startswith('(/fonts'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(chars'):
|
||
|
|
# chars
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/chars'):
|
||
|
|
if lines[idx].startswith('(char '):
|
||
|
|
char_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
char = Chars()
|
||
|
|
char.character = char_attrs.get("ch", "")
|
||
|
|
char.font_family = char_attrs.get("family", "")
|
||
|
|
char.font_size = float(char_attrs.get("size", 0))
|
||
|
|
char.font_style = char_attrs.get("style", "")
|
||
|
|
char.width = float(char_attrs.get("w", 0))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
# character shapes
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(char_shapes'):
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/char_shapes'):
|
||
|
|
# Parse shape elements for chars
|
||
|
|
if lines[idx].startswith('(group'):
|
||
|
|
shape, new_idx = parse_group_tag(lines, idx)
|
||
|
|
char.data.shapes.append(shape)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(path'):
|
||
|
|
shape, new_idx = parse_path_tag(lines, idx)
|
||
|
|
char.data.shapes.append(shape)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(fill'):
|
||
|
|
shape, new_idx = parse_fill_tag(lines, idx)
|
||
|
|
char.data.shapes.append(shape)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(stroke'):
|
||
|
|
shape, new_idx = parse_stroke_tag(lines, idx)
|
||
|
|
char.data.shapes.append(shape)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(rect'):
|
||
|
|
shape, new_idx = parse_rect_tag(lines, idx)
|
||
|
|
char.data.shapes.append(shape)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(ellipse'):
|
||
|
|
shape, new_idx = parse_ellipse_tag(lines, idx)
|
||
|
|
char.data.shapes.append(shape)
|
||
|
|
idx = new_idx
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
if lines[idx].startswith('(/char_shapes'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
animation["chars"].append(char)
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/char'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(/chars'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(asset'):
|
||
|
|
asset, new_idx = parse_asset_tag(lines, idx)
|
||
|
|
animation["assets"].append(asset)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(layer') or lines[idx].startswith('(shape_layer'):
|
||
|
|
layer, new_idx = parse_layer_tag(lines, idx)
|
||
|
|
animation["layers"].append(layer)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(null_layer'):
|
||
|
|
layer, new_idx = parse_layer_tag(lines, idx)
|
||
|
|
animation["layers"].append(layer)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(precomp_layer'):
|
||
|
|
layer, new_idx = parse_precomp_layer_tag(lines, idx)
|
||
|
|
animation["layers"].append(layer)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(text_layer'):
|
||
|
|
layer, new_idx = parse_text_layer_tag(lines, idx)
|
||
|
|
animation["layers"].append(layer)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(solid_layer'): # Add this
|
||
|
|
layer, new_idx = parse_solid_layer_tag(lines, idx)
|
||
|
|
animation["layers"].append(layer)
|
||
|
|
idx = new_idx
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
asset_map = {asset.get('id'): asset for asset in animation["assets"] if 'id' in asset}
|
||
|
|
for layer in animation["layers"]:
|
||
|
|
if isinstance(layer, PreCompLayer) and hasattr(layer, 'reference_id'):
|
||
|
|
ref_id = layer.reference_id
|
||
|
|
if ref_id in asset_map:
|
||
|
|
layer.referenced_asset = asset_map[ref_id]
|
||
|
|
|
||
|
|
# Build parent-child relationships
|
||
|
|
layer_map = {layer.index: layer for layer in animation["layers"] if hasattr(layer, 'index')}
|
||
|
|
for layer in animation["layers"]:
|
||
|
|
if hasattr(layer, 'parent_index') and layer.parent_index is not None:
|
||
|
|
parent_index = layer.parent_index
|
||
|
|
if parent_index in layer_map:
|
||
|
|
layer.parent = layer_map[parent_index]
|
||
|
|
|
||
|
|
for asset in animation["assets"]:
|
||
|
|
if "layers" in asset:
|
||
|
|
layer_map = {layer.index: layer for layer in asset["layers"] if hasattr(layer, 'index')}
|
||
|
|
for layer in asset["layers"]:
|
||
|
|
if hasattr(layer, 'parent_index') and layer.parent_index is not None:
|
||
|
|
parent_index = layer.parent_index
|
||
|
|
if parent_index in layer_map:
|
||
|
|
layer.parent = layer_map[parent_index]
|
||
|
|
|
||
|
|
return animation, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_layer_tag(lines, idx):
|
||
|
|
""""""
|
||
|
|
if not (lines[idx].startswith('(layer') or lines[idx].startswith('(null_layer')):
|
||
|
|
raise ValueError(f"Expected layer tag, got: {lines[idx]}")
|
||
|
|
|
||
|
|
layer_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
#
|
||
|
|
if 'null_layer' in lines[idx]:
|
||
|
|
layer = NullLayer()
|
||
|
|
layer.type = ElementType.NULL_LAYER
|
||
|
|
else:
|
||
|
|
layer = ShapeLayer()
|
||
|
|
|
||
|
|
#
|
||
|
|
layer.index = int(float(layer_attrs.get("index", 0)))
|
||
|
|
layer.name = layer_attrs.get("name", "Layer")
|
||
|
|
layer.in_point = float(layer_attrs.get("in_point", 0))
|
||
|
|
layer.out_point = float(layer_attrs.get("out_point", 60))
|
||
|
|
layer.start_time = float(layer_attrs.get("start_time", 0))
|
||
|
|
|
||
|
|
# Parse optional ShapeLayer attributes - only set if present
|
||
|
|
if "ddd" in layer_attrs:
|
||
|
|
layer.ddd = int(layer_attrs["ddd"])
|
||
|
|
|
||
|
|
if "hd" in layer_attrs:
|
||
|
|
layer.hd = layer_attrs["hd"].lower() == 'true'
|
||
|
|
|
||
|
|
if "cp" in layer_attrs:
|
||
|
|
layer.cp = layer_attrs["cp"].lower() == 'true'
|
||
|
|
|
||
|
|
if "cl" in layer_attrs:
|
||
|
|
cl_value = layer_attrs["cl"]
|
||
|
|
if cl_value and cl_value != '""':
|
||
|
|
layer.cl = cl_value.strip('"')
|
||
|
|
|
||
|
|
if "ao" in layer_attrs:
|
||
|
|
layer.ao = int(layer_attrs["ao"])
|
||
|
|
|
||
|
|
# Parse track matte attributes - only set if present
|
||
|
|
if "tt" in layer_attrs:
|
||
|
|
layer.tt = int(layer_attrs["tt"])
|
||
|
|
|
||
|
|
if "tp" in layer_attrs:
|
||
|
|
tp_value = layer_attrs["tp"]
|
||
|
|
if tp_value and tp_value.isdigit():
|
||
|
|
layer.tp = int(tp_value)
|
||
|
|
else:
|
||
|
|
layer.tp = tp_value
|
||
|
|
|
||
|
|
if "td" in layer_attrs:
|
||
|
|
layer.td = int(layer_attrs["td"])
|
||
|
|
|
||
|
|
if "ct" in layer_attrs:
|
||
|
|
layer.ct = int(layer_attrs["ct"])
|
||
|
|
|
||
|
|
if "hasMask" in layer_attrs:
|
||
|
|
layer.hasMask = layer_attrs["hasMask"].lower() == 'true'
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
# is_asset
|
||
|
|
if layer_attrs.get("is_asset", "").lower() == "true":
|
||
|
|
layer._is_asset_layer = True
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
if line.startswith('(parent'):
|
||
|
|
# parent
|
||
|
|
parent_index = extract_number(line)
|
||
|
|
layer.parent_index = int(parent_index)
|
||
|
|
idx += 1
|
||
|
|
elif line.strip() == '(transform)':
|
||
|
|
transform, new_idx = parse_transform_tag(lines, idx)
|
||
|
|
layer.transform = transform
|
||
|
|
idx = new_idx
|
||
|
|
#
|
||
|
|
elif line.startswith('(group'):
|
||
|
|
group, new_idx = parse_group_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(group)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(group)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(path'):
|
||
|
|
path, new_idx = parse_path_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(path)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(path)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(fill'):
|
||
|
|
fill, new_idx = parse_fill_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(fill)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(fill)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(stroke'):
|
||
|
|
stroke, new_idx = parse_stroke_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(stroke)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(stroke)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(rect'):
|
||
|
|
rect, new_idx = parse_rect_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(rect)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(rect)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(ellipse'):
|
||
|
|
ellipse, new_idx = parse_ellipse_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(ellipse)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(ellipse)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(star'):
|
||
|
|
star, new_idx = parse_star_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(star)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(star)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(trim'):
|
||
|
|
trim, new_idx = parse_trim_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(trim)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(trim)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(repeater'):
|
||
|
|
repeater, new_idx = parse_repeater_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(repeater)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(repeater)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(gradient_fill'):
|
||
|
|
gradient_fill, new_idx = parse_gradient_fill_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(gradient_fill)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(gradient_fill)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(gradient_stroke'):
|
||
|
|
gradient_stroke, new_idx = parse_gradient_stroke_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(gradient_stroke)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(gradient_stroke)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(merge'):
|
||
|
|
merge, new_idx = parse_merge_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(merge)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(merge)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(rounded_corners'):
|
||
|
|
rounded_corners, new_idx = parse_rounded_corners_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(rounded_corners)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(rounded_corners)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(twist'):
|
||
|
|
twist, new_idx = parse_twist_tag(lines, idx)
|
||
|
|
if hasattr(layer, 'add_shape'):
|
||
|
|
layer.add_shape(twist)
|
||
|
|
elif hasattr(layer, 'shapes'):
|
||
|
|
layer.shapes.append(twist)
|
||
|
|
idx = new_idx
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(zig_zag'):
|
||
|
|
zig_zag, new_idx = parse_zig_zag_tag(lines, idx)
|
||
|
|
layer.shapes.append(zig_zag)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(tm '):
|
||
|
|
# tm -
|
||
|
|
tm_attrs = parse_tag_attrs(line)
|
||
|
|
#print("tm_attrs", tm_attrs)
|
||
|
|
tm_data = {}
|
||
|
|
if 'a' in tm_attrs:
|
||
|
|
tm_data['a'] = int(tm_attrs['a'])
|
||
|
|
if 'ix' in tm_attrs:
|
||
|
|
tm_data['ix'] = int(tm_attrs['ix'])
|
||
|
|
|
||
|
|
# keyframes
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
if line.startswith('(keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(line)
|
||
|
|
kf = {}
|
||
|
|
if 't' in kf_attrs:
|
||
|
|
kf['t'] = float(kf_attrs['t'])
|
||
|
|
if 's' in kf_attrs:
|
||
|
|
kf['s'] = [float(kf_attrs['s'])]
|
||
|
|
if 'h' in kf_attrs:
|
||
|
|
kf['h'] = int(kf_attrs['h'])
|
||
|
|
|
||
|
|
#
|
||
|
|
if 'i_x' in kf_attrs or 'i_y' in kf_attrs:
|
||
|
|
kf['i'] = {}
|
||
|
|
if 'i_x' in kf_attrs:
|
||
|
|
kf['i']['x'] = [float(kf_attrs['i_x'])]
|
||
|
|
if 'i_y' in kf_attrs:
|
||
|
|
kf['i']['y'] = [float(kf_attrs['i_y'])]
|
||
|
|
|
||
|
|
if 'o_x' in kf_attrs or 'o_y' in kf_attrs:
|
||
|
|
kf['o'] = {}
|
||
|
|
if 'o_x' in kf_attrs:
|
||
|
|
kf['o']['x'] = [float(kf_attrs['o_x'])]
|
||
|
|
if 'o_y' in kf_attrs:
|
||
|
|
kf['o']['y'] = [float(kf_attrs['o_y'])]
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(value'):
|
||
|
|
#
|
||
|
|
val = extract_number(line)
|
||
|
|
tm_data['k'] = val
|
||
|
|
idx += 1
|
||
|
|
elif line.strip() == '(/tm)':
|
||
|
|
if keyframes:
|
||
|
|
tm_data['k'] = keyframes
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
layer.tm = tm_data
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(effects'):
|
||
|
|
# effects
|
||
|
|
effects, new_idx = parse_effects_tag(lines, idx)
|
||
|
|
layer.ef = effects
|
||
|
|
idx = new_idx
|
||
|
|
|
||
|
|
|
||
|
|
elif line.startswith('(tt'):
|
||
|
|
tt_value = extract_number(line)
|
||
|
|
layer.tt = int(tt_value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(tp'):
|
||
|
|
tp_value = extract_number(line)
|
||
|
|
layer.tp = int(tp_value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(td'):
|
||
|
|
td_value = extract_number(line)
|
||
|
|
layer.td = int(td_value)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(masksProperties'):
|
||
|
|
# masksProperties
|
||
|
|
layer.masksProperties = []
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/masksProperties)'):
|
||
|
|
if lines[idx].startswith('(mask '):
|
||
|
|
# mask
|
||
|
|
mask_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask = {}
|
||
|
|
|
||
|
|
#
|
||
|
|
if "inv" in mask_attrs:
|
||
|
|
mask["inv"] = mask_attrs["inv"].lower() == "true"
|
||
|
|
if "mode" in mask_attrs:
|
||
|
|
mask["mode"] = mask_attrs["mode"]
|
||
|
|
if "nm" in mask_attrs:
|
||
|
|
mask["nm"] = mask_attrs["nm"]
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# mask
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask)'):
|
||
|
|
if lines[idx].startswith('(mask_pt '):
|
||
|
|
# pt ()
|
||
|
|
pt_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask["pt"] = {}
|
||
|
|
if "a" in pt_attrs:
|
||
|
|
mask["pt"]["a"] = int(pt_attrs["a"])
|
||
|
|
if "ix" in pt_attrs:
|
||
|
|
mask["pt"]["ix"] = int(pt_attrs["ix"])
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# pt.k ()
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(mask_pt_k'):
|
||
|
|
if lines[idx].startswith('(mask_pt_k_array'):
|
||
|
|
# k
|
||
|
|
mask["pt"]["k"] = []
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
#
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_k_array)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe = {}
|
||
|
|
|
||
|
|
if "t" in kf_attrs:
|
||
|
|
keyframe["t"] = float(kf_attrs["t"])
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
#
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_keyframe)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_kf_i'):
|
||
|
|
i_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe["i"] = {
|
||
|
|
"x": float(i_attrs.get("x", 0)),
|
||
|
|
"y": float(i_attrs.get("y", 0))
|
||
|
|
}
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_o'):
|
||
|
|
o_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe["o"] = {
|
||
|
|
"x": float(o_attrs.get("x", 0)),
|
||
|
|
"y": float(o_attrs.get("y", 0))
|
||
|
|
}
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_s'):
|
||
|
|
keyframe["s"] = []
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_kf_s)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_kf_shape'):
|
||
|
|
shape_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
shape = {}
|
||
|
|
if "c" in shape_attrs:
|
||
|
|
shape["c"] = shape_attrs["c"].lower() == "true"
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_kf_shape)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_kf_shape_i'):
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
shape["i"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
shape["i"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_shape_o'):
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
shape["o"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
shape["o"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_shape_v'):
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
shape["v"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
shape["v"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
if lines[idx].startswith('(/mask_pt_kf_shape)'):
|
||
|
|
idx += 1
|
||
|
|
keyframe["s"].append(shape)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
if lines[idx].startswith('(/mask_pt_kf_s)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask_pt_keyframe)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
mask["pt"]["k"].append(keyframe)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask_pt_k_array)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
else:
|
||
|
|
# kshape
|
||
|
|
mask["pt"]["k"] = {}
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# shape
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_k)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_k_c'):
|
||
|
|
# closed
|
||
|
|
parts = lines[idx].split()
|
||
|
|
if len(parts) > 1:
|
||
|
|
mask["pt"]["k"]["c"] = parts[1].rstrip(')').lower() == "true"
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_pt_k_i'):
|
||
|
|
# i
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
mask["pt"]["k"]["i"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
mask["pt"]["k"]["i"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_pt_k_o'):
|
||
|
|
# o
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
mask["pt"]["k"]["o"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
mask["pt"]["k"]["o"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_pt_k_v'):
|
||
|
|
# v
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
mask["pt"]["k"]["v"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
mask["pt"]["k"]["v"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask_pt_k)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/mask_pt)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_o '):
|
||
|
|
# opacity
|
||
|
|
o_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask["o"] = {}
|
||
|
|
if "a" in o_attrs:
|
||
|
|
mask["o"]["a"] = int(o_attrs["a"])
|
||
|
|
if "ix" in o_attrs:
|
||
|
|
mask["o"]["ix"] = int(o_attrs["ix"])
|
||
|
|
|
||
|
|
#
|
||
|
|
if "a" in o_attrs and int(o_attrs["a"]) == 1:
|
||
|
|
#
|
||
|
|
mask["o"]["k"] = []
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and lines[idx].startswith('(keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe = {}
|
||
|
|
if "t" in kf_attrs:
|
||
|
|
keyframe["t"] = float(kf_attrs["t"])
|
||
|
|
if "s" in kf_attrs:
|
||
|
|
keyframe["s"] = [float(kf_attrs["s"])]
|
||
|
|
if "i_x" in kf_attrs and "i_y" in kf_attrs:
|
||
|
|
keyframe["i"] = {
|
||
|
|
"x": [float(kf_attrs["i_x"])],
|
||
|
|
"y": [float(kf_attrs["i_y"])]
|
||
|
|
}
|
||
|
|
if "o_x" in kf_attrs and "o_y" in kf_attrs:
|
||
|
|
keyframe["o"] = {
|
||
|
|
"x": [float(kf_attrs["o_x"])],
|
||
|
|
"y": [float(kf_attrs["o_y"])]
|
||
|
|
}
|
||
|
|
mask["o"]["k"].append(keyframe)
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
if "k" in o_attrs:
|
||
|
|
mask["o"]["k"] = float(o_attrs["k"])
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_x '):
|
||
|
|
# dilate
|
||
|
|
x_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask["x"] = {}
|
||
|
|
if "a" in x_attrs:
|
||
|
|
mask["x"]["a"] = int(x_attrs["a"])
|
||
|
|
if "ix" in x_attrs:
|
||
|
|
mask["x"]["ix"] = int(x_attrs["ix"])
|
||
|
|
|
||
|
|
#
|
||
|
|
if "a" in x_attrs and int(x_attrs["a"]) == 1:
|
||
|
|
#
|
||
|
|
mask["x"]["k"] = []
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and lines[idx].startswith('(keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe = {}
|
||
|
|
if "t" in kf_attrs:
|
||
|
|
keyframe["t"] = float(kf_attrs["t"])
|
||
|
|
if "s" in kf_attrs:
|
||
|
|
keyframe["s"] = [float(kf_attrs["s"])]
|
||
|
|
if "i_x" in kf_attrs and "i_y" in kf_attrs:
|
||
|
|
keyframe["i"] = {
|
||
|
|
"x": [float(kf_attrs["i_x"])],
|
||
|
|
"y": [float(kf_attrs["i_y"])]
|
||
|
|
}
|
||
|
|
if "o_x" in kf_attrs and "o_y" in kf_attrs:
|
||
|
|
keyframe["o"] = {
|
||
|
|
"x": [float(kf_attrs["o_x"])],
|
||
|
|
"y": [float(kf_attrs["o_y"])]
|
||
|
|
}
|
||
|
|
mask["x"]["k"].append(keyframe)
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
if "k" in x_attrs:
|
||
|
|
mask["x"]["k"] = float(x_attrs["k"])
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
layer.masksProperties.append(mask)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/masksProperties)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
elif lines[idx].strip() == '(/layer)' or lines[idx].strip() == '(/null_layer)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return layer, idx
|
||
|
|
|
||
|
|
def parse_transform_tag(lines, idx):
|
||
|
|
"""Parse transform tag with support for single-line static properties"""
|
||
|
|
transform = Transform()
|
||
|
|
|
||
|
|
# Initialize default values
|
||
|
|
transform.position = MultiDimensional(NVector(0, 0, 0))
|
||
|
|
transform.scale = MultiDimensional(NVector(100, 100, 100))
|
||
|
|
transform.rotation = Value(0)
|
||
|
|
transform.opacity = Value(100)
|
||
|
|
transform.anchor = MultiDimensional(NVector(0, 0, 0))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx].strip()
|
||
|
|
|
||
|
|
# Check if this line contains multiple static properties
|
||
|
|
if '(' in line and ')' in line and line.count('(') > 1:
|
||
|
|
# Parse multiple properties on the same line
|
||
|
|
import re
|
||
|
|
props = re.findall(r'\([^)]+\)', line)
|
||
|
|
for prop in props:
|
||
|
|
prop = prop.strip()
|
||
|
|
if prop.startswith('(position'):
|
||
|
|
components = extract_numbers(prop)
|
||
|
|
if components:
|
||
|
|
if len(components) == 1:
|
||
|
|
transform.position = MultiDimensional(NVector(components[0], 0, 0))
|
||
|
|
elif len(components) == 2:
|
||
|
|
transform.position = MultiDimensional(NVector(components[0], components[1], 0))
|
||
|
|
else:
|
||
|
|
transform.position = MultiDimensional(NVector(*components))
|
||
|
|
elif prop.startswith('(scale'):
|
||
|
|
components = extract_numbers(prop)
|
||
|
|
if len(components) >= 3:
|
||
|
|
transform.scale = MultiDimensional(NVector(*components))
|
||
|
|
elif len(components) == 2:
|
||
|
|
transform.scale = MultiDimensional(NVector(components[0], components[1], 100))
|
||
|
|
elif len(components) == 1:
|
||
|
|
transform.scale = MultiDimensional(NVector(components[0], components[0], 100))
|
||
|
|
elif prop.startswith('(rotation'):
|
||
|
|
value = extract_number(prop)
|
||
|
|
transform.rotation = Value(value)
|
||
|
|
elif prop.startswith('(opacity'):
|
||
|
|
value = extract_number(prop)
|
||
|
|
transform.opacity = Value(value)
|
||
|
|
elif prop.startswith('(anchor'):
|
||
|
|
components = extract_numbers(prop)
|
||
|
|
if components:
|
||
|
|
if len(components) == 1:
|
||
|
|
transform.anchor = MultiDimensional(NVector(components[0], 0, 0))
|
||
|
|
else:
|
||
|
|
transform.anchor = MultiDimensional(NVector(*components))
|
||
|
|
idx += 1
|
||
|
|
continue
|
||
|
|
|
||
|
|
# Add expression parsing
|
||
|
|
|
||
|
|
if line.startswith('(position'):
|
||
|
|
if 'separated=true' in line:
|
||
|
|
# Handle separated position with animated components
|
||
|
|
transform.position = Value(NVector(0, 0, 0))
|
||
|
|
transform.position.separated = True
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/position)'):
|
||
|
|
if lines[idx].startswith('(position_x'):
|
||
|
|
if 'animated=true' in lines[idx]:
|
||
|
|
# Parse animated x component
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/position_x)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
value = float(attrs.get('s', 0))
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
# Handle bracketed format for easing parameters
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
if i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
|
||
|
|
if i_y_str.startswith('[') and i_y_str.endswith(']'):
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
# Handle bracketed format for easing parameters
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
if o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
|
||
|
|
if o_y_str.startswith('[') and o_y_str.endswith(']'):
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
if 'h' in attrs:
|
||
|
|
kf.h = int(attrs['h'])
|
||
|
|
# Handle n and e attributes
|
||
|
|
if 'n' in attrs:
|
||
|
|
kf.n = attrs['n']
|
||
|
|
if 'e' in attrs:
|
||
|
|
try:
|
||
|
|
kf.e = json.loads(attrs['e']) if attrs['e'].startswith('[') else float(attrs['e'])
|
||
|
|
except:
|
||
|
|
kf.e = attrs['e']
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
x_value = Value(keyframes[0].value if keyframes else 0)
|
||
|
|
x_value.keyframes = keyframes
|
||
|
|
transform.position.x = x_value
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/position_x)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.position.x = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(position_y'):
|
||
|
|
if 'animated=true' in lines[idx]:
|
||
|
|
# Parse animated y component
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/position_y)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
value = float(attrs.get('s', 0))
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
# Handle bracketed format for easing parameters
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
if i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
|
||
|
|
if i_y_str.startswith('[') and i_y_str.endswith(']'):
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
# Handle bracketed format for easing parameters
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
if o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
|
||
|
|
if o_y_str.startswith('[') and o_y_str.endswith(']'):
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
if 'h' in attrs:
|
||
|
|
kf.h = int(attrs['h'])
|
||
|
|
# Handle n and e attributes
|
||
|
|
if 'n' in attrs:
|
||
|
|
kf.n = attrs['n']
|
||
|
|
if 'e' in attrs:
|
||
|
|
try:
|
||
|
|
kf.e = json.loads(attrs['e']) if attrs['e'].startswith('[') else float(attrs['e'])
|
||
|
|
except:
|
||
|
|
kf.e = attrs['e']
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
y_value = Value(keyframes[0].value if keyframes else 0)
|
||
|
|
y_value.keyframes = keyframes
|
||
|
|
transform.position.y = y_value
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/position_y)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.position.y = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(position_z'):
|
||
|
|
if 'animated=true' in lines[idx]:
|
||
|
|
# Parse animated z component
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/position_z)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
value = float(attrs.get('s', 0))
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
# Handle bracketed format for easing parameters
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
if i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
|
||
|
|
if i_y_str.startswith('[') and i_y_str.endswith(']'):
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
# Handle bracketed format for easing parameters
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
if o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
|
||
|
|
if o_y_str.startswith('[') and o_y_str.endswith(']'):
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
if 'h' in attrs:
|
||
|
|
kf.h = int(attrs['h'])
|
||
|
|
# Handle n and e attributes
|
||
|
|
if 'n' in attrs:
|
||
|
|
kf.n = attrs['n']
|
||
|
|
if 'e' in attrs:
|
||
|
|
try:
|
||
|
|
kf.e = json.loads(attrs['e']) if attrs['e'].startswith('[') else float(attrs['e'])
|
||
|
|
except:
|
||
|
|
kf.e = attrs['e']
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
z_value = Value(keyframes[0].value if keyframes else 0)
|
||
|
|
z_value.keyframes = keyframes
|
||
|
|
transform.position.z = z_value
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/position_z)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.position.z = Value(value)
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
if lines[idx].startswith('(/position)'):
|
||
|
|
idx += 1
|
||
|
|
elif 'animated=true' in line:
|
||
|
|
# Regular animated position
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/position)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
|
||
|
|
s_str = attrs.get('s', '')
|
||
|
|
if s_str:
|
||
|
|
values = [float(x) for x in s_str.split()]
|
||
|
|
if len(values) >= 3:
|
||
|
|
value = NVector(values[0], values[1], values[2])
|
||
|
|
elif len(values) == 2:
|
||
|
|
value = NVector(values[0], values[1], 0)
|
||
|
|
else:
|
||
|
|
value = NVector(0, 0, 0)
|
||
|
|
else:
|
||
|
|
value = NVector(0, 0, 0)
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
# Handle space-separated list format
|
||
|
|
if ' ' in i_x_str:
|
||
|
|
i_x = [float(v) for v in i_x_str.split()]
|
||
|
|
i_y = [float(v) for v in i_y_str.split()]
|
||
|
|
# Handle bracketed format
|
||
|
|
elif i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
# Extract value from bracketed format [0.833] -> 0.833
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
# Handle space-separated list format
|
||
|
|
if ' ' in o_x_str:
|
||
|
|
o_x = [float(v) for v in o_x_str.split()]
|
||
|
|
o_y = [float(v) for v in o_y_str.split()]
|
||
|
|
# Handle bracketed format
|
||
|
|
elif o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
# Extract value from bracketed format [0.833] -> 0.833
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
if 'to' in attrs:
|
||
|
|
try:
|
||
|
|
kf.to = json.loads(attrs['to'])
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
if 'ti' in attrs:
|
||
|
|
try:
|
||
|
|
kf.ti = json.loads(attrs['ti'])
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
if 'h' in attrs:
|
||
|
|
kf.h = int(attrs['h'])
|
||
|
|
# Handle n and e attributes
|
||
|
|
if 'n' in attrs:
|
||
|
|
kf.n = attrs['n']
|
||
|
|
if 'e' in attrs:
|
||
|
|
try:
|
||
|
|
kf.e = json.loads(attrs['e']) if attrs['e'].startswith('[') else [float(x) for x in attrs['e'].split()]
|
||
|
|
except:
|
||
|
|
try:
|
||
|
|
kf.e = float(attrs['e'])
|
||
|
|
except:
|
||
|
|
kf.e = attrs['e']
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/position)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
pos_value = MultiDimensional(keyframes[0].value)
|
||
|
|
pos_value.keyframes = keyframes
|
||
|
|
transform.position = pos_value
|
||
|
|
else:
|
||
|
|
components = extract_numbers(line)
|
||
|
|
if components:
|
||
|
|
if len(components) == 1:
|
||
|
|
transform.position = MultiDimensional(NVector(components[0], 0, 0))
|
||
|
|
elif len(components) == 2:
|
||
|
|
transform.position = MultiDimensional(NVector(components[0], components[1], 0))
|
||
|
|
else:
|
||
|
|
transform.position = MultiDimensional(NVector(*components))
|
||
|
|
else:
|
||
|
|
transform.position = MultiDimensional(NVector(250, 250, 0))
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(scale'):
|
||
|
|
# Scale parsing code with bracketed format handling
|
||
|
|
if 'separated=true' in line:
|
||
|
|
# Handle separated scale
|
||
|
|
transform.scale = Value(NVector(100, 100, 100))
|
||
|
|
transform.scale.separated = True
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/scale)'):
|
||
|
|
if lines[idx].startswith('(scale_x'):
|
||
|
|
if 'animated=true' in lines[idx]:
|
||
|
|
# Parse animated x component
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/scale_x)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
value = float(attrs.get('s', 100))
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
if i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
|
||
|
|
if i_y_str.startswith('[') and i_y_str.endswith(']'):
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
if o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
|
||
|
|
if o_y_str.startswith('[') and o_y_str.endswith(']'):
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
x_value = Value(keyframes[0].value if keyframes else 100)
|
||
|
|
x_value.keyframes = keyframes
|
||
|
|
transform.scale.x = x_value
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/scale_x)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.scale.x = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(scale_y'):
|
||
|
|
if 'animated=true' in lines[idx]:
|
||
|
|
# Parse animated y component
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/scale_y)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
value = float(attrs.get('s', 100))
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
if i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
|
||
|
|
if i_y_str.startswith('[') and i_y_str.endswith(']'):
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
if o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
|
||
|
|
if o_y_str.startswith('[') and o_y_str.endswith(']'):
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
y_value = Value(keyframes[0].value if keyframes else 100)
|
||
|
|
y_value.keyframes = keyframes
|
||
|
|
transform.scale.y = y_value
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/scale_y)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.scale.y = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(scale_z'):
|
||
|
|
if 'animated=true' in lines[idx]:
|
||
|
|
# Parse animated z component
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/scale_z)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
value = float(attrs.get('s', 100))
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
if i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
|
||
|
|
if i_y_str.startswith('[') and i_y_str.endswith(']'):
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
if o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
|
||
|
|
if o_y_str.startswith('[') and o_y_str.endswith(']'):
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
z_value = Value(keyframes[0].value if keyframes else 100)
|
||
|
|
z_value.keyframes = keyframes
|
||
|
|
transform.scale.z = z_value
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/scale_z)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.scale.z = Value(value)
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
if lines[idx].startswith('(/scale)'):
|
||
|
|
idx += 1
|
||
|
|
elif 'animated=true' in line:
|
||
|
|
# Regular animated scale
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/scale)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
|
||
|
|
s_str = attrs.get('s', '')
|
||
|
|
if s_str:
|
||
|
|
values = [float(x) for x in s_str.split()]
|
||
|
|
if len(values) >= 3:
|
||
|
|
value = NVector(values[0], values[1], values[2])
|
||
|
|
elif len(values) == 2:
|
||
|
|
value = NVector(values[0], values[1], 100)
|
||
|
|
else:
|
||
|
|
value = NVector(values[0] if values else 100, values[0] if values else 100, 100)
|
||
|
|
else:
|
||
|
|
value = NVector(100, 100, 100)
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
# Handle easing parameters
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
# Handle space-separated list format
|
||
|
|
if ' ' in i_x_str:
|
||
|
|
i_x = [float(v) for v in i_x_str.split()]
|
||
|
|
i_y = [float(v) for v in i_y_str.split()]
|
||
|
|
# Handle bracketed format
|
||
|
|
elif i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
# Handle space-separated list format
|
||
|
|
if ' ' in o_x_str:
|
||
|
|
o_x = [float(v) for v in o_x_str.split()]
|
||
|
|
o_y = [float(v) for v in o_y_str.split()]
|
||
|
|
# Handle bracketed format
|
||
|
|
elif o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
# FIX: Add h attribute restoration
|
||
|
|
if 'h' in attrs:
|
||
|
|
kf.h = int(attrs['h'])
|
||
|
|
|
||
|
|
# Add n and e attributes
|
||
|
|
if 'n' in attrs:
|
||
|
|
kf.n = attrs['n']
|
||
|
|
if 'e' in attrs:
|
||
|
|
try:
|
||
|
|
kf.e = json.loads(attrs['e']) if attrs['e'].startswith('[') else float(attrs['e'])
|
||
|
|
except:
|
||
|
|
kf.e = attrs['e']
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
scale_value = MultiDimensional(keyframes[0].value)
|
||
|
|
scale_value.keyframes = keyframes
|
||
|
|
transform.scale = scale_value
|
||
|
|
else:
|
||
|
|
transform.scale = MultiDimensional(NVector(100, 100, 100))
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/scale)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
else:
|
||
|
|
components = extract_numbers(line)
|
||
|
|
if len(components) >= 3:
|
||
|
|
transform.scale = MultiDimensional(NVector(*components))
|
||
|
|
elif len(components) == 2:
|
||
|
|
transform.scale = MultiDimensional(NVector(components[0], components[1], 100))
|
||
|
|
elif len(components) == 1:
|
||
|
|
transform.scale = MultiDimensional(NVector(components[0], components[0], 100))
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(rotation'):
|
||
|
|
# Handle separated rotation
|
||
|
|
if 'separated=true' in line:
|
||
|
|
# Handle separated rotation
|
||
|
|
transform.rotation = Value(0)
|
||
|
|
transform.rotation.separated = True
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/rotation)'):
|
||
|
|
if lines[idx].startswith('(rotation_x'):
|
||
|
|
if 'animated=true' in lines[idx]:
|
||
|
|
# Parse animated x component
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/rotation_x)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
value = float(attrs.get('s', 0))
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
if i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
|
||
|
|
if i_y_str.startswith('[') and i_y_str.endswith(']'):
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
if o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
|
||
|
|
if o_y_str.startswith('[') and o_y_str.endswith(']'):
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
x_value = Value(keyframes[0].value if keyframes else 0)
|
||
|
|
x_value.keyframes = keyframes
|
||
|
|
transform.rotation.x = x_value
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/rotation_x)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.rotation.x = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(rotation_y'):
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.rotation.y = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(rotation_z'):
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.rotation.z = Value(value)
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
if lines[idx].startswith('(/rotation)'):
|
||
|
|
idx += 1
|
||
|
|
elif 'animated=true' in line:
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/rotation)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
s_str = attrs.get('s', '')
|
||
|
|
value = float(s_str) if s_str else 0
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
# Handle space-separated list format
|
||
|
|
if ' ' in i_x_str:
|
||
|
|
i_x = [float(v) for v in i_x_str.split()]
|
||
|
|
i_y = [float(v) for v in i_y_str.split()]
|
||
|
|
# Handle bracketed format
|
||
|
|
elif i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
# Handle space-separated list format
|
||
|
|
if ' ' in o_x_str:
|
||
|
|
o_x = [float(v) for v in o_x_str.split()]
|
||
|
|
o_y = [float(v) for v in o_y_str.split()]
|
||
|
|
# Handle bracketed format
|
||
|
|
elif o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
if 'h' in attrs:
|
||
|
|
kf.h = int(attrs['h'])
|
||
|
|
# Handle n and e attributes
|
||
|
|
if 'n' in attrs:
|
||
|
|
kf.n = attrs['n']
|
||
|
|
if 'e' in attrs:
|
||
|
|
try:
|
||
|
|
kf.e = json.loads(attrs['e']) if attrs['e'].startswith('[') else float(attrs['e'])
|
||
|
|
except:
|
||
|
|
kf.e = attrs['e']
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/rotation)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
rot_value = Value(keyframes[0].value)
|
||
|
|
rot_value.keyframes = keyframes
|
||
|
|
transform.rotation = rot_value
|
||
|
|
else:
|
||
|
|
value = extract_number(line)
|
||
|
|
transform.rotation = Value(value)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(opacity'):
|
||
|
|
if 'animated=true' in line:
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/opacity)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
s_str = attrs.get('s', '')
|
||
|
|
value = float(s_str) if s_str else 100
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
# Handle space-separated list format
|
||
|
|
if ' ' in i_x_str:
|
||
|
|
i_x = [float(v) for v in i_x_str.split()]
|
||
|
|
i_y = [float(v) for v in i_y_str.split()]
|
||
|
|
# Handle bracketed format
|
||
|
|
elif i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
# Handle space-separated list format
|
||
|
|
if ' ' in o_x_str:
|
||
|
|
o_x = [float(v) for v in o_x_str.split()]
|
||
|
|
o_y = [float(v) for v in o_y_str.split()]
|
||
|
|
# Handle bracketed format
|
||
|
|
elif o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
# ADD THIS: Parse h attribute for hold keyframe
|
||
|
|
if 'h' in attrs:
|
||
|
|
kf.h = int(attrs['h'])
|
||
|
|
|
||
|
|
# Handle n and e attributes
|
||
|
|
if 'n' in attrs:
|
||
|
|
kf.n = attrs['n']
|
||
|
|
if 'e' in attrs:
|
||
|
|
try:
|
||
|
|
kf.e = json.loads(attrs['e']) if attrs['e'].startswith('[') else float(attrs['e'])
|
||
|
|
except:
|
||
|
|
kf.e = attrs['e']
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/opacity)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
op_value = Value(keyframes[0].value)
|
||
|
|
op_value.keyframes = keyframes
|
||
|
|
transform.opacity = op_value
|
||
|
|
else:
|
||
|
|
value = extract_number(line)
|
||
|
|
transform.opacity = Value(value)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
|
||
|
|
elif line.startswith('(anchor'):
|
||
|
|
if 'animated=true' in line:
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/anchor)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
|
||
|
|
s_str = attrs.get('s', '')
|
||
|
|
if s_str:
|
||
|
|
values = [float(x) for x in s_str.split()]
|
||
|
|
if len(values) >= 2:
|
||
|
|
value = NVector(values[0], values[1], 0)
|
||
|
|
else:
|
||
|
|
value = NVector(0, 0, 0)
|
||
|
|
else:
|
||
|
|
value = NVector(0, 0, 0)
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
# Handle space-separated list format
|
||
|
|
if ' ' in i_x_str:
|
||
|
|
i_x = [float(v) for v in i_x_str.split()]
|
||
|
|
i_y = [float(v) for v in i_y_str.split()]
|
||
|
|
# Handle bracketed format
|
||
|
|
elif i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
# Handle space-separated list format
|
||
|
|
if ' ' in o_x_str:
|
||
|
|
o_x = [float(v) for v in o_x_str.split()]
|
||
|
|
o_y = [float(v) for v in o_y_str.split()]
|
||
|
|
# Handle bracketed format
|
||
|
|
elif o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
# Add to/ti attributes parsing
|
||
|
|
if 'to' in attrs:
|
||
|
|
try:
|
||
|
|
kf.to = json.loads(attrs['to'])
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
if 'ti' in attrs:
|
||
|
|
try:
|
||
|
|
kf.ti = json.loads(attrs['ti'])
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
if 'h' in attrs:
|
||
|
|
kf.h = int(attrs['h'])
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/anchor)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
anchor_value = MultiDimensional(keyframes[0].value)
|
||
|
|
anchor_value.keyframes = keyframes
|
||
|
|
transform.anchor = anchor_value
|
||
|
|
else:
|
||
|
|
components = extract_numbers(line)
|
||
|
|
if components:
|
||
|
|
if len(components) == 1:
|
||
|
|
transform.anchor = MultiDimensional(NVector(components[0], 0, 0))
|
||
|
|
else:
|
||
|
|
transform.anchor = MultiDimensional(NVector(*components))
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(skew'):
|
||
|
|
value = extract_number(line)
|
||
|
|
transform.skew = Value(value)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(skew_axis'):
|
||
|
|
value = extract_number(line)
|
||
|
|
transform.skew_axis = Value(value)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.strip() == '(/transform)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return transform, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_solid_layer_tag(lines, idx):
|
||
|
|
""""""
|
||
|
|
from .layers import SolidColorLayer
|
||
|
|
|
||
|
|
solid_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
solid_layer = SolidColorLayer()
|
||
|
|
solid_layer.type = ElementType.SOLID_LAYER
|
||
|
|
solid_layer.index = int(float(solid_attrs.get("index", 0)))
|
||
|
|
solid_layer.name = solid_attrs.get("name", "Solid Layer")
|
||
|
|
solid_layer.in_point = float(solid_attrs.get("in_point", 0))
|
||
|
|
solid_layer.out_point = float(solid_attrs.get("out_point", 60))
|
||
|
|
solid_layer.start_time = float(solid_attrs.get("start_time", 0))
|
||
|
|
solid_layer.color = solid_attrs.get("color", "#000000")
|
||
|
|
solid_layer.width = float(solid_attrs.get("width", 512))
|
||
|
|
solid_layer.height = float(solid_attrs.get("height", 512))
|
||
|
|
|
||
|
|
# hasMask
|
||
|
|
if "hasMask" in solid_attrs:
|
||
|
|
solid_layer.hasMask = solid_attrs["hasMask"].lower() == "true"
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if line.startswith('(parent'):
|
||
|
|
parent_index = int(extract_number(line))
|
||
|
|
solid_layer.parent_index = parent_index
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(transform'):
|
||
|
|
transform, new_idx = parse_transform_tag(lines, idx)
|
||
|
|
solid_layer.transform = transform
|
||
|
|
idx = new_idx
|
||
|
|
|
||
|
|
elif line.startswith('(masksProperties'):
|
||
|
|
# masksProperties
|
||
|
|
solid_layer.masksProperties = []
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/masksProperties)'):
|
||
|
|
if lines[idx].startswith('(mask '):
|
||
|
|
# mask
|
||
|
|
mask_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask = {}
|
||
|
|
|
||
|
|
#
|
||
|
|
if "inv" in mask_attrs:
|
||
|
|
mask["inv"] = mask_attrs["inv"].lower() == "true"
|
||
|
|
if "mode" in mask_attrs:
|
||
|
|
mask["mode"] = mask_attrs["mode"]
|
||
|
|
if "nm" in mask_attrs:
|
||
|
|
mask["nm"] = mask_attrs["nm"]
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# mask
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask)'):
|
||
|
|
if lines[idx].startswith('(mask_pt '):
|
||
|
|
# pt
|
||
|
|
pt_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask["pt"] = {}
|
||
|
|
if "a" in pt_attrs:
|
||
|
|
mask["pt"]["a"] = int(pt_attrs["a"])
|
||
|
|
if "ix" in pt_attrs:
|
||
|
|
mask["pt"]["ix"] = int(pt_attrs["ix"])
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# pt.k
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(mask_pt_k'):
|
||
|
|
if lines[idx].startswith('(mask_pt_k_array'):
|
||
|
|
# k
|
||
|
|
mask["pt"]["k"] = []
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
#
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_k_array)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe = {}
|
||
|
|
|
||
|
|
if "t" in kf_attrs:
|
||
|
|
keyframe["t"] = float(kf_attrs["t"])
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# ...
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_keyframe)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_kf_i'):
|
||
|
|
i_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe["i"] = {
|
||
|
|
"x": float(i_attrs.get("x", 0)),
|
||
|
|
"y": float(i_attrs.get("y", 0))
|
||
|
|
}
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_o'):
|
||
|
|
o_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe["o"] = {
|
||
|
|
"x": float(o_attrs.get("x", 0)),
|
||
|
|
"y": float(o_attrs.get("y", 0))
|
||
|
|
}
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_s'):
|
||
|
|
keyframe["s"] = []
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_kf_s)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_kf_shape'):
|
||
|
|
shape_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
shape = {}
|
||
|
|
if "c" in shape_attrs:
|
||
|
|
shape["c"] = shape_attrs["c"].lower() == "true"
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_kf_shape)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_kf_shape_i'):
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
shape["i"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
shape["i"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_shape_o'):
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
shape["o"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
shape["o"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_shape_v'):
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
shape["v"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
shape["v"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
if lines[idx].startswith('(/mask_pt_kf_shape)'):
|
||
|
|
idx += 1
|
||
|
|
keyframe["s"].append(shape)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
if lines[idx].startswith('(/mask_pt_kf_s)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask_pt_keyframe)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
mask["pt"]["k"].append(keyframe)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask_pt_k_array)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
else:
|
||
|
|
# kshape
|
||
|
|
mask["pt"]["k"] = {}
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# shape
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_k)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_k_c'):
|
||
|
|
# closed
|
||
|
|
parts = lines[idx].split()
|
||
|
|
if len(parts) > 1:
|
||
|
|
mask["pt"]["k"]["c"] = parts[1].rstrip(')').lower() == "true"
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_pt_k_i'):
|
||
|
|
# i
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
mask["pt"]["k"]["i"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
mask["pt"]["k"]["i"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_pt_k_o'):
|
||
|
|
# o
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
mask["pt"]["k"]["o"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
mask["pt"]["k"]["o"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_pt_k_v'):
|
||
|
|
# v
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
mask["pt"]["k"]["v"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
mask["pt"]["k"]["v"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask_pt_k)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/mask_pt)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_o '):
|
||
|
|
# opacity
|
||
|
|
o_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask["o"] = {}
|
||
|
|
if "a" in o_attrs:
|
||
|
|
mask["o"]["a"] = int(o_attrs["a"])
|
||
|
|
if "k" in o_attrs:
|
||
|
|
mask["o"]["k"] = float(o_attrs["k"])
|
||
|
|
if "ix" in o_attrs:
|
||
|
|
mask["o"]["ix"] = int(o_attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_x '):
|
||
|
|
# dilate
|
||
|
|
x_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask["x"] = {}
|
||
|
|
if "a" in x_attrs:
|
||
|
|
mask["x"]["a"] = int(x_attrs["a"])
|
||
|
|
if "k" in x_attrs:
|
||
|
|
mask["x"]["k"] = float(x_attrs["k"])
|
||
|
|
if "ix" in x_attrs:
|
||
|
|
mask["x"]["ix"] = int(x_attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
solid_layer.masksProperties.append(mask)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/masksProperties)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(effects'):
|
||
|
|
# effects
|
||
|
|
effects, new_idx = parse_effects_tag(lines, idx)
|
||
|
|
solid_layer.ef = effects
|
||
|
|
idx = new_idx
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
elif line.startswith('(tm '):
|
||
|
|
# tm -
|
||
|
|
tm_attrs = parse_tag_attrs(line)
|
||
|
|
tm_data = {}
|
||
|
|
if 'a' in tm_attrs:
|
||
|
|
tm_data['a'] = int(tm_attrs['a'])
|
||
|
|
if 'ix' in tm_attrs:
|
||
|
|
tm_data['ix'] = int(tm_attrs['ix'])
|
||
|
|
|
||
|
|
# keyframes
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
if line.startswith('(keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(line)
|
||
|
|
kf = {}
|
||
|
|
if 't' in kf_attrs:
|
||
|
|
kf['t'] = float(kf_attrs['t'])
|
||
|
|
if 's' in kf_attrs:
|
||
|
|
kf['s'] = [float(kf_attrs['s'])]
|
||
|
|
if 'h' in kf_attrs:
|
||
|
|
kf['h'] = int(kf_attrs['h'])
|
||
|
|
|
||
|
|
#
|
||
|
|
if 'i_x' in kf_attrs or 'i_y' in kf_attrs:
|
||
|
|
kf['i'] = {}
|
||
|
|
if 'i_x' in kf_attrs:
|
||
|
|
kf['i']['x'] = [float(kf_attrs['i_x'])]
|
||
|
|
if 'i_y' in kf_attrs:
|
||
|
|
kf['i']['y'] = [float(kf_attrs['i_y'])]
|
||
|
|
|
||
|
|
if 'o_x' in kf_attrs or 'o_y' in kf_attrs:
|
||
|
|
kf['o'] = {}
|
||
|
|
if 'o_x' in kf_attrs:
|
||
|
|
kf['o']['x'] = [float(kf_attrs['o_x'])]
|
||
|
|
if 'o_y' in kf_attrs:
|
||
|
|
kf['o']['y'] = [float(kf_attrs['o_y'])]
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(value'):
|
||
|
|
#
|
||
|
|
val = extract_number(line)
|
||
|
|
tm_data['k'] = val
|
||
|
|
idx += 1
|
||
|
|
elif line.strip() == '(/tm)':
|
||
|
|
if keyframes:
|
||
|
|
tm_data['k'] = keyframes
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
solid_layer.tm = tm_data
|
||
|
|
|
||
|
|
|
||
|
|
elif line.startswith('(tt'):
|
||
|
|
tt_value = extract_number(line)
|
||
|
|
solid_layer.tt = int(tt_value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(tp'):
|
||
|
|
tp_value = extract_number(line)
|
||
|
|
solid_layer.tp = int(tp_value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(td'):
|
||
|
|
td_value = extract_number(line)
|
||
|
|
solid_layer.td = int(td_value)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.strip() == '(/solid_layer)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return solid_layer, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_text_layer_tag(lines, idx):
|
||
|
|
""" - """
|
||
|
|
text_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
text_layer = TextLayer()
|
||
|
|
text_layer.type = ElementType.TEXT_LAYER
|
||
|
|
text_layer.index = int(float(text_attrs.get("index", 0)))
|
||
|
|
text_layer.name = text_attrs.get("name", "Text Layer")
|
||
|
|
text_layer.in_point = float(text_attrs.get("in_point", 0))
|
||
|
|
text_layer.out_point = float(text_attrs.get("out_point", 60))
|
||
|
|
text_layer.start_time = float(text_attrs.get("start_time", 0))
|
||
|
|
#text_layer.ct = int(text_attrs.get("ct", 0))
|
||
|
|
|
||
|
|
# ln
|
||
|
|
#text_layer.ln =(text_attrs.get("ln", ""))
|
||
|
|
if "ct" in text_attrs:
|
||
|
|
text_layer.ct = int(text_attrs.get("ct"))
|
||
|
|
# hasMask
|
||
|
|
if "hasMask" in text_attrs:
|
||
|
|
text_layer.hasMask = text_attrs["hasMask"].lower() == "true"
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
|
||
|
|
if line.startswith('(tt'):
|
||
|
|
tt_value = extract_number(line)
|
||
|
|
text_layer.tt = int(tt_value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(tp'):
|
||
|
|
tp_value = extract_number(line)
|
||
|
|
text_layer.tp = int(tp_value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(td'):
|
||
|
|
td_value = extract_number(line)
|
||
|
|
text_layer.td = int(td_value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(parent'):
|
||
|
|
parent_index = int(extract_number(line))
|
||
|
|
text_layer.parent_index = parent_index
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(effects'):
|
||
|
|
# effects
|
||
|
|
effects, new_idx = parse_effects_tag(lines, idx)
|
||
|
|
text_layer.ef = effects
|
||
|
|
idx = new_idx
|
||
|
|
|
||
|
|
|
||
|
|
elif line.startswith('(transform'):
|
||
|
|
transform, new_idx = parse_transform_tag(lines, idx)
|
||
|
|
text_layer.transform = transform
|
||
|
|
idx = new_idx
|
||
|
|
|
||
|
|
|
||
|
|
elif line.startswith('(text_data'):
|
||
|
|
#
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
path_option = {}
|
||
|
|
more_options = {}
|
||
|
|
animators = []
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/text_data)'):
|
||
|
|
if lines[idx].startswith('(text_keyframes'):
|
||
|
|
#
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/text_keyframes)'):
|
||
|
|
if lines[idx].startswith('(text_keyframe'):
|
||
|
|
# text_keyframe
|
||
|
|
line_content = lines[idx]
|
||
|
|
|
||
|
|
#
|
||
|
|
doc_data = {}
|
||
|
|
time = 0
|
||
|
|
|
||
|
|
# t -
|
||
|
|
t_match = re.search(r't=([\d\.\-]+)', line_content)
|
||
|
|
if t_match:
|
||
|
|
time = float(t_match.group(1))
|
||
|
|
|
||
|
|
# font_size -
|
||
|
|
fs_match = re.search(r'font_size=([\d\.\-]+)', line_content)
|
||
|
|
if fs_match:
|
||
|
|
doc_data['s'] = float(fs_match.group(1))
|
||
|
|
|
||
|
|
# font_family
|
||
|
|
ff_match = re.search(r'font_family="([^"]*)"', line_content)
|
||
|
|
if ff_match:
|
||
|
|
doc_data['f'] = ff_match.group(1)
|
||
|
|
|
||
|
|
# text -
|
||
|
|
text_match = re.search(r'text="([^"]*(?:\\.[^"]*)*)"', line_content)
|
||
|
|
if text_match:
|
||
|
|
text_val = text_match.group(1)
|
||
|
|
doc_data['t'] = text_val.replace('\\"', '"')
|
||
|
|
|
||
|
|
# ca -
|
||
|
|
ca_match = re.search(r'ca=([\d]+)', line_content)
|
||
|
|
if ca_match:
|
||
|
|
doc_data['ca'] = int(ca_match.group(1))
|
||
|
|
|
||
|
|
# justify -
|
||
|
|
j_match = re.search(r'justify=([\d]+)', line_content)
|
||
|
|
if j_match:
|
||
|
|
doc_data['j'] = int(j_match.group(1))
|
||
|
|
|
||
|
|
# tracking -
|
||
|
|
tr_match = re.search(r'tracking=([\d\.\-]+)', line_content)
|
||
|
|
if tr_match:
|
||
|
|
doc_data['tr'] = float(tr_match.group(1))
|
||
|
|
|
||
|
|
# line_height -
|
||
|
|
lh_match = re.search(r'line_height=([\d\.\-]+)', line_content)
|
||
|
|
if lh_match:
|
||
|
|
doc_data['lh'] = float(lh_match.group(1))
|
||
|
|
|
||
|
|
# letter_spacing -
|
||
|
|
ls_match = re.search(r'letter_spacing=([\d\.\-]+)', line_content)
|
||
|
|
if ls_match:
|
||
|
|
doc_data['ls'] = float(ls_match.group(1))
|
||
|
|
|
||
|
|
# fill_color
|
||
|
|
fc_match = re.search(r'fill_color=\[([^\]]+)\]', line_content)
|
||
|
|
if fc_match:
|
||
|
|
fc_str = fc_match.group(1)
|
||
|
|
fc_values = [float(v.strip()) for v in fc_str.split(',')]
|
||
|
|
doc_data['fc'] = fc_values
|
||
|
|
|
||
|
|
# ADD PARSING FOR MISSING FIELDS
|
||
|
|
# stroke_color
|
||
|
|
sc_match = re.search(r'stroke_color=\[([^\]]+)\]', line_content)
|
||
|
|
if sc_match:
|
||
|
|
sc_str = sc_match.group(1)
|
||
|
|
sc_values = [float(v.strip()) for v in sc_str.split(',')]
|
||
|
|
doc_data['sc'] = sc_values
|
||
|
|
|
||
|
|
# stroke_width -
|
||
|
|
sw_match = re.search(r'stroke_width=([\d\.\-]+)', line_content)
|
||
|
|
if sw_match:
|
||
|
|
doc_data['sw'] = float(sw_match.group(1))
|
||
|
|
|
||
|
|
# offset
|
||
|
|
of_match = re.search(r'offset=(true|false)', line_content)
|
||
|
|
if of_match:
|
||
|
|
doc_data['of'] = of_match.group(1) == 'true'
|
||
|
|
|
||
|
|
# Extract wrap_size (sz)
|
||
|
|
sz_match = re.search(r'wrap_size=\[([^\]]+)\]', line_content)
|
||
|
|
if sz_match:
|
||
|
|
sz_str = sz_match.group(1)
|
||
|
|
sz_values = [float(v.strip()) for v in sz_str.split(',')]
|
||
|
|
doc_data['sz'] = sz_values
|
||
|
|
|
||
|
|
# Extract wrap_position (ps)
|
||
|
|
ps_match = re.search(r'wrap_position=\[([^\]]+)\]', line_content)
|
||
|
|
if ps_match:
|
||
|
|
ps_str = ps_match.group(1)
|
||
|
|
ps_values = [float(v.strip()) for v in ps_str.split(',')]
|
||
|
|
doc_data['ps'] = ps_values
|
||
|
|
|
||
|
|
keyframes.append({"s": doc_data, "t": time})
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/text_keyframes'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(document_full'):
|
||
|
|
#
|
||
|
|
doc_json = extract_string_value(lines[idx])
|
||
|
|
if doc_json:
|
||
|
|
try:
|
||
|
|
keyframes = json.loads(doc_json)
|
||
|
|
except json.JSONDecodeError:
|
||
|
|
keyframes = []
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(path_option'):
|
||
|
|
path_json = extract_string_value(lines[idx])
|
||
|
|
if path_json:
|
||
|
|
try:
|
||
|
|
path_option = json.loads(path_json)
|
||
|
|
except json.JSONDecodeError:
|
||
|
|
path_option = {}
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(more_options') and not lines[idx].endswith('")'):
|
||
|
|
# more_options
|
||
|
|
line_content = lines[idx]
|
||
|
|
|
||
|
|
# g
|
||
|
|
g_match = re.search(r'\(more_options g (\d+)', line_content)
|
||
|
|
g_value = int(g_match.group(1)) if g_match else 1
|
||
|
|
|
||
|
|
# alignment
|
||
|
|
alignment_data = {"a": 0, "k": [0, 0], "ix": 2}
|
||
|
|
|
||
|
|
# a
|
||
|
|
a_match = re.search(r'alignment a=(\d+)', line_content)
|
||
|
|
if a_match:
|
||
|
|
alignment_data['a'] = int(a_match.group(1))
|
||
|
|
|
||
|
|
# alignment_k
|
||
|
|
k_match = re.search(r'alignment_k ([\d\.\-\s]+)(?:alignment_ix|$|\))', line_content)
|
||
|
|
if k_match:
|
||
|
|
k_values = [float(v) for v in k_match.group(1).strip().split()]
|
||
|
|
alignment_data['k'] = k_values
|
||
|
|
|
||
|
|
# alignment_ix
|
||
|
|
ix_match = re.search(r'alignment_ix (\d+)', line_content)
|
||
|
|
if ix_match:
|
||
|
|
alignment_data['ix'] = int(ix_match.group(1))
|
||
|
|
|
||
|
|
more_options = {"g": g_value, "a": alignment_data}
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(more_options "'):
|
||
|
|
# JSON
|
||
|
|
more_json = extract_string_value(lines[idx])
|
||
|
|
if more_json:
|
||
|
|
try:
|
||
|
|
more_options = json.loads(more_json)
|
||
|
|
except json.JSONDecodeError:
|
||
|
|
more_options = {}
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(animators'):
|
||
|
|
animators = []
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/animators'):
|
||
|
|
if lines[idx].startswith('(animator '):
|
||
|
|
animator = {}
|
||
|
|
nm_match = re.search(r'nm="([^"]*)"', lines[idx])
|
||
|
|
if nm_match:
|
||
|
|
animator["nm"] = nm_match.group(1)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# Parse range selector
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(range_selector'):
|
||
|
|
range_selector = {}
|
||
|
|
params = lines[idx]
|
||
|
|
|
||
|
|
# Parse basic range selector properties
|
||
|
|
# Fix: Use [^)\s]+ to exclude closing parenthesis and whitespace
|
||
|
|
for prop in ["t", "r", "b", "sh", "rn"]:
|
||
|
|
match = re.search(rf'{prop}=([^)\s]+)', params)
|
||
|
|
if match:
|
||
|
|
range_selector[prop] = float(match.group(1)) if '.' in match.group(1) else int(match.group(1))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# Parse range properties
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/range_selector'):
|
||
|
|
prop_line = lines[idx]
|
||
|
|
|
||
|
|
# Map property names
|
||
|
|
prop_map = {
|
||
|
|
"range_start": "s",
|
||
|
|
"range_end": "e",
|
||
|
|
"range_offset": "o",
|
||
|
|
"amount": "a",
|
||
|
|
"max_ease": "xe",
|
||
|
|
"min_ease": "ne",
|
||
|
|
"s_m": "sm"
|
||
|
|
}
|
||
|
|
|
||
|
|
found_prop = False
|
||
|
|
for prop_name, prop_key in prop_map.items():
|
||
|
|
if prop_line.startswith(f'({prop_name} '):
|
||
|
|
found_prop = True
|
||
|
|
# Check if animated
|
||
|
|
a_match = re.search(r'a=(\d+)', prop_line)
|
||
|
|
|
||
|
|
if a_match and a_match.group(1) == "1":
|
||
|
|
# Animated property - parse keyframes
|
||
|
|
prop_data = {"a": 1, "k": []}
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
while idx < len(lines) and lines[idx].startswith(f'({prop_name}_keyframe'):
|
||
|
|
kf_line = lines[idx]
|
||
|
|
keyframe = {}
|
||
|
|
|
||
|
|
# Parse time
|
||
|
|
t_match = re.search(r't=([^)\s]+)', kf_line)
|
||
|
|
if t_match:
|
||
|
|
keyframe["t"] = float(t_match.group(1))
|
||
|
|
|
||
|
|
# Parse value
|
||
|
|
s_match = re.search(r's=([^)]+?)(?:\s+[io]_|$|\))', kf_line)
|
||
|
|
if s_match:
|
||
|
|
s_values = s_match.group(1).strip().split()
|
||
|
|
if len(s_values) > 1:
|
||
|
|
keyframe["s"] = [float(v) for v in s_values]
|
||
|
|
else:
|
||
|
|
keyframe["s"] = [float(s_values[0])]
|
||
|
|
|
||
|
|
# Parse interpolation
|
||
|
|
for interp in ["i", "o"]:
|
||
|
|
x_match = re.search(rf'{interp}_x=([^)]+?)(?:\s+[io]_|$|\))', kf_line)
|
||
|
|
y_match = re.search(rf'{interp}_y=([^)]+?)(?:\s+[io]_|$|\))', kf_line)
|
||
|
|
|
||
|
|
if x_match or y_match:
|
||
|
|
keyframe[interp] = {}
|
||
|
|
if x_match:
|
||
|
|
x_values = x_match.group(1).strip().split()
|
||
|
|
keyframe[interp]["x"] = [float(v) for v in x_values]
|
||
|
|
if y_match:
|
||
|
|
y_values = y_match.group(1).strip().split()
|
||
|
|
keyframe[interp]["y"] = [float(v) for v in y_values]
|
||
|
|
|
||
|
|
prop_data["k"].append(keyframe)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# Skip closing tag if present
|
||
|
|
if idx < len(lines) and lines[idx] == f'(/{prop_name})':
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
range_selector[prop_key] = prop_data
|
||
|
|
else:
|
||
|
|
# Static property - ALWAYS use the {a, k, ix} structure
|
||
|
|
k_match = re.search(r'k=([^)\s]+)', prop_line)
|
||
|
|
ix_match = re.search(r'ix=(\d+)', prop_line)
|
||
|
|
|
||
|
|
k_value = 0
|
||
|
|
if k_match:
|
||
|
|
k_str = k_match.group(1)
|
||
|
|
# Handle both single values and space-separated values
|
||
|
|
if ' ' in prop_line[prop_line.find('k='):]:
|
||
|
|
# Multiple values - extract until we hit ix= or )
|
||
|
|
k_full_match = re.search(r'k=([\d.\s-]+?)(?:\s+ix=|\))', prop_line)
|
||
|
|
if k_full_match:
|
||
|
|
k_values = k_full_match.group(1).strip().split()
|
||
|
|
k_value = [float(v) for v in k_values]
|
||
|
|
else:
|
||
|
|
k_value = float(k_str)
|
||
|
|
else:
|
||
|
|
k_value = float(k_str)
|
||
|
|
|
||
|
|
range_selector[prop_key] = {
|
||
|
|
"a": int(a_match.group(1)) if a_match else 0,
|
||
|
|
"k": k_value,
|
||
|
|
"ix": int(ix_match.group(1)) if ix_match else 0
|
||
|
|
}
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
|
||
|
|
if not found_prop:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
animator["s"] = range_selector
|
||
|
|
if idx < len(lines) and lines[idx] == '(/range_selector)':
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# Parse animator properties
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(animator_properties'):
|
||
|
|
idx += 1
|
||
|
|
a_props = {}
|
||
|
|
|
||
|
|
prop_map = {
|
||
|
|
"opacity_animators": "o",
|
||
|
|
"position_animators": "p",
|
||
|
|
"scale_animators": "s",
|
||
|
|
"rotation_animators": "r",
|
||
|
|
"anchor_animators": "a",
|
||
|
|
"skew_animators": "sk",
|
||
|
|
"skew_axis_animators": "sa",
|
||
|
|
"fill_colo_animatorsr": "fc",
|
||
|
|
"stroke_color_animators": "sc",
|
||
|
|
"stroke_width_animators": "sw",
|
||
|
|
"tracking_animators": "t"
|
||
|
|
}
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/animator_properties'):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
# Check each property type
|
||
|
|
for prop_name, prop_key in prop_map.items():
|
||
|
|
if line.startswith(f'({prop_name} '):
|
||
|
|
# Check if animated
|
||
|
|
a_match = re.search(r'a=(\d+)', line)
|
||
|
|
|
||
|
|
if a_match and a_match.group(1) == "1":
|
||
|
|
# Animated property - parse keyframes
|
||
|
|
prop_data = {"a": 1, "k": []}
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# Parse keyframes
|
||
|
|
while idx < len(lines) and lines[idx].startswith('(keyframe '):
|
||
|
|
kf_line = lines[idx]
|
||
|
|
keyframe = {}
|
||
|
|
|
||
|
|
# Parse time
|
||
|
|
t_match = re.search(r't=([^)\s]+)', kf_line)
|
||
|
|
if t_match:
|
||
|
|
keyframe["t"] = float(t_match.group(1))
|
||
|
|
|
||
|
|
# Parse value(s) - now properly handle quoted values
|
||
|
|
s_match = re.search(r's="([^"]*)"', kf_line)
|
||
|
|
if s_match:
|
||
|
|
s_values = s_match.group(1).strip().split()
|
||
|
|
if len(s_values) > 1:
|
||
|
|
keyframe["s"] = [float(v) for v in s_values]
|
||
|
|
else:
|
||
|
|
keyframe["s"] = [float(s_values[0])]
|
||
|
|
|
||
|
|
# Parse 'to' and 'ti'
|
||
|
|
to_match = re.search(r'to="([^"]*)"', kf_line)
|
||
|
|
if to_match:
|
||
|
|
to_values = to_match.group(1).strip().split()
|
||
|
|
if len(to_values) > 1:
|
||
|
|
keyframe["to"] = [float(v) for v in to_values]
|
||
|
|
else:
|
||
|
|
keyframe["to"] = [float(to_values[0])]
|
||
|
|
|
||
|
|
ti_match = re.search(r'ti="([^"]*)"', kf_line)
|
||
|
|
if ti_match:
|
||
|
|
ti_values = ti_match.group(1).strip().split()
|
||
|
|
if len(ti_values) > 1:
|
||
|
|
keyframe["ti"] = [float(v) for v in ti_values]
|
||
|
|
else:
|
||
|
|
keyframe["ti"] = [float(ti_values[0])]
|
||
|
|
|
||
|
|
|
||
|
|
# Parse interpolation - handle quoted values
|
||
|
|
for interp in ["i", "o"]:
|
||
|
|
x_match = re.search(rf'{interp}_x="([^"]*)"', kf_line)
|
||
|
|
y_match = re.search(rf'{interp}_y="([^"]*)"', kf_line)
|
||
|
|
|
||
|
|
if x_match or y_match:
|
||
|
|
keyframe[interp] = {}
|
||
|
|
if x_match:
|
||
|
|
x_values = x_match.group(1).strip().split()
|
||
|
|
if len(x_values) > 1:
|
||
|
|
keyframe[interp]["x"] = [float(v) for v in x_values]
|
||
|
|
else:
|
||
|
|
keyframe[interp]["x"] = float(x_values[0])
|
||
|
|
if y_match:
|
||
|
|
y_values = y_match.group(1).strip().split()
|
||
|
|
if len(y_values) > 1:
|
||
|
|
keyframe[interp]["y"] = [float(v) for v in y_values]
|
||
|
|
else:
|
||
|
|
keyframe[interp]["y"] = float(y_values[0])
|
||
|
|
|
||
|
|
prop_data["k"].append(keyframe)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# Check for closing tag
|
||
|
|
if idx < len(lines) and lines[idx] == f'(/{prop_name})':
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
a_props[prop_key] = prop_data
|
||
|
|
else:
|
||
|
|
# Static property
|
||
|
|
k_match = re.search(r'k=(\[[\d.,\s-]+\]|[^)\s]+)', line)
|
||
|
|
ix_match = re.search(r'ix=(\d+)', line)
|
||
|
|
|
||
|
|
k_value = 0
|
||
|
|
if k_match:
|
||
|
|
k_str = k_match.group(1)
|
||
|
|
if k_str.startswith('['):
|
||
|
|
# Parse array
|
||
|
|
k_str = k_str.strip('[]')
|
||
|
|
k_value = [float(v.strip()) for v in k_str.split(',')]
|
||
|
|
else:
|
||
|
|
k_value = float(k_str)
|
||
|
|
|
||
|
|
a_props[prop_key] = {
|
||
|
|
"a": int(a_match.group(1)) if a_match else 0,
|
||
|
|
"k": k_value,
|
||
|
|
"ix": int(ix_match.group(1)) if ix_match else 0
|
||
|
|
}
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
# Line doesn't match any property
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
animator["a"] = a_props
|
||
|
|
idx += 1 # Skip (/animator_properties)
|
||
|
|
|
||
|
|
|
||
|
|
idx += 1 # Skip (/animator)
|
||
|
|
animators.append(animator)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
text_layer.data.animators = animators
|
||
|
|
idx += 1 # Skip (/animators)
|
||
|
|
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
#
|
||
|
|
if keyframes:
|
||
|
|
text_layer.data.document = Value(keyframes)
|
||
|
|
text_layer.data.path_option = path_option
|
||
|
|
text_layer.data.more_options = more_options
|
||
|
|
text_layer.data.animators = animators
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/text_data)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
elif line.startswith('(masksProperties'):
|
||
|
|
# masksProperties
|
||
|
|
text_layer.masksProperties = []
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/masksProperties)'):
|
||
|
|
if lines[idx].startswith('(mask '):
|
||
|
|
# mask
|
||
|
|
mask_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask = {}
|
||
|
|
|
||
|
|
#
|
||
|
|
if "inv" in mask_attrs:
|
||
|
|
mask["inv"] = mask_attrs["inv"].lower() == "true"
|
||
|
|
if "mode" in mask_attrs:
|
||
|
|
mask["mode"] = mask_attrs["mode"]
|
||
|
|
if "nm" in mask_attrs:
|
||
|
|
mask["nm"] = mask_attrs["nm"]
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# mask
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask)'):
|
||
|
|
if lines[idx].startswith('(mask_pt '):
|
||
|
|
# pt
|
||
|
|
pt_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask["pt"] = {}
|
||
|
|
if "a" in pt_attrs:
|
||
|
|
mask["pt"]["a"] = int(pt_attrs["a"])
|
||
|
|
if "ix" in pt_attrs:
|
||
|
|
mask["pt"]["ix"] = int(pt_attrs["ix"])
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# pt.k
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(mask_pt_k'):
|
||
|
|
if lines[idx].startswith('(mask_pt_k_array'):
|
||
|
|
# k
|
||
|
|
mask["pt"]["k"] = []
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
#
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_k_array)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe = {}
|
||
|
|
|
||
|
|
if "t" in kf_attrs:
|
||
|
|
keyframe["t"] = float(kf_attrs["t"])
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# ...
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_keyframe)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_kf_i'):
|
||
|
|
i_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe["i"] = {
|
||
|
|
"x": float(i_attrs.get("x", 0)),
|
||
|
|
"y": float(i_attrs.get("y", 0))
|
||
|
|
}
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_o'):
|
||
|
|
o_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe["o"] = {
|
||
|
|
"x": float(o_attrs.get("x", 0)),
|
||
|
|
"y": float(o_attrs.get("y", 0))
|
||
|
|
}
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_s'):
|
||
|
|
keyframe["s"] = []
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_kf_s)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_kf_shape'):
|
||
|
|
shape_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
shape = {}
|
||
|
|
if "c" in shape_attrs:
|
||
|
|
shape["c"] = shape_attrs["c"].lower() == "true"
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_kf_shape)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_kf_shape_i'):
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
shape["i"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
shape["i"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_shape_o'):
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
shape["o"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
shape["o"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_shape_v'):
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
shape["v"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
shape["v"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
if lines[idx].startswith('(/mask_pt_kf_shape)'):
|
||
|
|
idx += 1
|
||
|
|
keyframe["s"].append(shape)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
if lines[idx].startswith('(/mask_pt_kf_s)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask_pt_keyframe)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
mask["pt"]["k"].append(keyframe)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask_pt_k_array)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
else:
|
||
|
|
# kshape
|
||
|
|
mask["pt"]["k"] = {}
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# shape
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_k)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_k_c'):
|
||
|
|
# closed
|
||
|
|
parts = lines[idx].split()
|
||
|
|
if len(parts) > 1:
|
||
|
|
mask["pt"]["k"]["c"] = parts[1].rstrip(')').lower() == "true"
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_pt_k_i'):
|
||
|
|
# i
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
mask["pt"]["k"]["i"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
mask["pt"]["k"]["i"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_pt_k_o'):
|
||
|
|
# o
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
mask["pt"]["k"]["o"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
mask["pt"]["k"]["o"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_pt_k_v'):
|
||
|
|
# v
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
mask["pt"]["k"]["v"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
mask["pt"]["k"]["v"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask_pt_k)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/mask_pt)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_o '):
|
||
|
|
# opacity
|
||
|
|
o_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask["o"] = {}
|
||
|
|
if "a" in o_attrs:
|
||
|
|
mask["o"]["a"] = int(o_attrs["a"])
|
||
|
|
if "k" in o_attrs:
|
||
|
|
mask["o"]["k"] = float(o_attrs["k"])
|
||
|
|
if "ix" in o_attrs:
|
||
|
|
mask["o"]["ix"] = int(o_attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_x '):
|
||
|
|
# dilate
|
||
|
|
x_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask["x"] = {}
|
||
|
|
if "a" in x_attrs:
|
||
|
|
mask["x"]["a"] = int(x_attrs["a"])
|
||
|
|
if "k" in x_attrs:
|
||
|
|
mask["x"]["k"] = float(x_attrs["k"])
|
||
|
|
if "ix" in x_attrs:
|
||
|
|
mask["x"]["ix"] = int(x_attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
text_layer.masksProperties.append(mask)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/masksProperties)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
|
||
|
|
elif line.strip() == '(/text_layer)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return text_layer, idx
|
||
|
|
|
||
|
|
def parse_group_tag(lines, idx):
|
||
|
|
"""Parse a group tag and its contents"""
|
||
|
|
group_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
group = Group()
|
||
|
|
group.shapes = []
|
||
|
|
|
||
|
|
group.name = group_attrs.get("name", "")
|
||
|
|
|
||
|
|
# property_index
|
||
|
|
if "ix" in group_attrs:
|
||
|
|
group.property_index = int(group_attrs.get("ix", 1))
|
||
|
|
if "cix" in group_attrs:
|
||
|
|
group.cix = int(group_attrs.get("cix", 2))
|
||
|
|
if "bm" in group_attrs:
|
||
|
|
group.bm = 0
|
||
|
|
if "hd" in group_attrs:
|
||
|
|
group.hd = group_attrs.get("hd", "false").lower() == "true"
|
||
|
|
if "mn" in group_attrs:
|
||
|
|
group.mn = group_attrs.get("mn", "")
|
||
|
|
if "np" in group_attrs:
|
||
|
|
group.number_of_properties = int(float(group_attrs.get("np")))
|
||
|
|
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
if lines[idx].startswith('("TransformShape"'):
|
||
|
|
transform_shape, new_idx = parse_transform_shape_tag(lines, idx)
|
||
|
|
group.shapes.append(transform_shape)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(group'):
|
||
|
|
nested_group, new_idx = parse_group_tag(lines, idx)
|
||
|
|
group.shapes.append(nested_group)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(path'):
|
||
|
|
path, new_idx = parse_path_tag(lines, idx)
|
||
|
|
group.shapes.append(path)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(fill'):
|
||
|
|
fill, new_idx = parse_fill_tag(lines, idx)
|
||
|
|
group.shapes.append(fill)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(stroke'):
|
||
|
|
stroke, new_idx = parse_stroke_tag(lines, idx)
|
||
|
|
group.shapes.append(stroke)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(rect'):
|
||
|
|
rect, new_idx = parse_rect_tag(lines, idx)
|
||
|
|
group.shapes.append(rect)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(ellipse'):
|
||
|
|
ellipse, new_idx = parse_ellipse_tag(lines, idx)
|
||
|
|
group.shapes.append(ellipse)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(star'):
|
||
|
|
star, new_idx = parse_star_tag(lines, idx)
|
||
|
|
group.shapes.append(star)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(trim'):
|
||
|
|
trim, new_idx = parse_trim_tag(lines, idx)
|
||
|
|
group.shapes.append(trim)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(repeater'):
|
||
|
|
repeater, new_idx = parse_repeater_tag(lines, idx)
|
||
|
|
group.shapes.append(repeater)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(gradient_fill'):
|
||
|
|
gradient_fill, new_idx = parse_gradient_fill_tag(lines, idx)
|
||
|
|
group.shapes.append(gradient_fill)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(gradient_stroke'):
|
||
|
|
gradient_stroke, new_idx = parse_gradient_stroke_tag(lines, idx)
|
||
|
|
group.shapes.append(gradient_stroke)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(merge'):
|
||
|
|
merge, new_idx = parse_merge_tag(lines, idx)
|
||
|
|
group.shapes.append(merge)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(rounded_corners'):
|
||
|
|
rounded_corners, new_idx = parse_rounded_corners_tag(lines, idx)
|
||
|
|
group.shapes.append(rounded_corners)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(twist'):
|
||
|
|
twist, new_idx = parse_twist_tag(lines, idx)
|
||
|
|
group.shapes.append(twist)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].startswith('(zig_zag'):
|
||
|
|
zig_zag, new_idx = parse_zig_zag_tag(lines, idx)
|
||
|
|
group.shapes.append(zig_zag)
|
||
|
|
idx = new_idx
|
||
|
|
elif lines[idx].strip() == '(/group)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return group, idx
|
||
|
|
|
||
|
|
def parse_zig_zag_tag(lines, idx):
|
||
|
|
""""""
|
||
|
|
zig_zag = ZigZag()
|
||
|
|
|
||
|
|
#
|
||
|
|
if lines[idx].startswith('(zig_zag'):
|
||
|
|
attrs = lines[idx][8:-1].strip() # Remove '(zig_zag' and ')'
|
||
|
|
|
||
|
|
#
|
||
|
|
if 'name=' in attrs:
|
||
|
|
import re
|
||
|
|
name_match = re.search(r'name="([^"]*)"', attrs)
|
||
|
|
if name_match:
|
||
|
|
zig_zag.name = name_match.group(1)
|
||
|
|
|
||
|
|
if 'ix=' in attrs:
|
||
|
|
import re
|
||
|
|
ix_match = re.search(r'ix=(\d+)', attrs)
|
||
|
|
if ix_match:
|
||
|
|
zig_zag.property_index = int(ix_match.group(1))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
#
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/zig_zag)'):
|
||
|
|
line = lines[idx].strip()
|
||
|
|
|
||
|
|
if line.startswith('(frequency'):
|
||
|
|
# frequency
|
||
|
|
parts = line.split()
|
||
|
|
if len(parts) > 1:
|
||
|
|
value_str = parts[1].rstrip(')')
|
||
|
|
zig_zag.frequency = Value(float(value_str))
|
||
|
|
|
||
|
|
elif line.startswith('(amplitude'):
|
||
|
|
# amplitude
|
||
|
|
parts = line.split()
|
||
|
|
if len(parts) > 1:
|
||
|
|
value_str = parts[1].rstrip(')')
|
||
|
|
zig_zag.amplitude = Value(float(value_str))
|
||
|
|
|
||
|
|
elif line.startswith('(point_type'):
|
||
|
|
# point_type
|
||
|
|
parts = line.split()
|
||
|
|
if len(parts) > 1:
|
||
|
|
value_str = parts[1].rstrip(')')
|
||
|
|
zig_zag.point_type = Value(float(value_str))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
#
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/zig_zag)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return zig_zag, idx
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
def parse_trim_tag(lines, idx):
|
||
|
|
"""Parse trim tag and its contents - handle keyframes properly with e field"""
|
||
|
|
trim_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
trim = Trim()
|
||
|
|
trim.name = trim_attrs.get("name", "")
|
||
|
|
|
||
|
|
if "ix" in trim_attrs:
|
||
|
|
trim.property_index = int(trim_attrs.get("ix", 1))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if line.startswith('(start'):
|
||
|
|
if 'animated=true' in line:
|
||
|
|
# Process animated start with keyframes
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/start)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
s_str = attrs.get('s', '0')
|
||
|
|
value = float(s_str) if s_str else 0
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
# Handle e field (end value)
|
||
|
|
if 'e' in attrs:
|
||
|
|
e_str = attrs.get('e', '0')
|
||
|
|
kf.e = float(e_str) if e_str else 0
|
||
|
|
|
||
|
|
# Handle easing parameters
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
if ' ' in i_x_str:
|
||
|
|
i_x = [float(v) for v in i_x_str.split()]
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
|
||
|
|
if ' ' in i_y_str:
|
||
|
|
i_y = [float(v) for v in i_y_str.split()]
|
||
|
|
else:
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
if ' ' in o_x_str:
|
||
|
|
o_x = [float(v) for v in o_x_str.split()]
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
|
||
|
|
if ' ' in o_y_str:
|
||
|
|
o_y = [float(v) for v in o_y_str.split()]
|
||
|
|
else:
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
trim.start = Value(keyframes[0].value)
|
||
|
|
trim.start.keyframes = keyframes
|
||
|
|
trim.start.animated = True
|
||
|
|
else:
|
||
|
|
trim.start = Value(0)
|
||
|
|
trim.start.animated = True
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/start)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
value = extract_number(line)
|
||
|
|
trim.start = Value(value)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(end'):
|
||
|
|
if 'animated=true' in line:
|
||
|
|
# Process animated end with keyframes
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/end)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
s_str = attrs.get('s', '100')
|
||
|
|
value = float(s_str) if s_str else 100
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
# Handle e field (end value)
|
||
|
|
if 'e' in attrs:
|
||
|
|
e_str = attrs.get('e', '100')
|
||
|
|
kf.e = float(e_str) if e_str else 100
|
||
|
|
|
||
|
|
# Handle easing parameters
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
if ' ' in i_x_str:
|
||
|
|
i_y = [float(v) for v in i_x_str.split()]
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
|
||
|
|
if ' ' in i_y_str:
|
||
|
|
i_y = [float(v) for v in i_y_str.split()]
|
||
|
|
else:
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
if ' ' in o_x_str:
|
||
|
|
o_x = [float(v) for v in o_x_str.split()]
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
|
||
|
|
if ' ' in o_y_str:
|
||
|
|
o_y = [float(v) for v in o_y_str.split()]
|
||
|
|
else:
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
trim.end = Value(keyframes[0].value)
|
||
|
|
trim.end.keyframes = keyframes
|
||
|
|
trim.end.animated = True
|
||
|
|
else:
|
||
|
|
trim.end = Value(100)
|
||
|
|
trim.end.animated = True
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/end)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
value = extract_number(line)
|
||
|
|
trim.end = Value(value)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(offset'):
|
||
|
|
if 'animated=true' in line:
|
||
|
|
# Process animated offset with keyframes
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/offset)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
s_str = attrs.get('s', '0')
|
||
|
|
value = float(s_str) if s_str else 0
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
# Handle e field (end value)
|
||
|
|
if 'e' in attrs:
|
||
|
|
e_str = attrs.get('e', '0')
|
||
|
|
kf.e = float(e_str) if e_str else 0
|
||
|
|
|
||
|
|
# Handle easing parameters
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
if ' ' in i_x_str:
|
||
|
|
i_x = [float(v) for v in i_x_str.split()]
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
|
||
|
|
if ' ' in i_y_str:
|
||
|
|
i_y = [float(v) for v in i_y_str.split()]
|
||
|
|
else:
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
if ' ' in o_x_str:
|
||
|
|
o_x = [float(v) for v in o_x_str.split()]
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
|
||
|
|
if ' ' in o_y_str:
|
||
|
|
o_y = [float(v) for v in o_y_str.split()]
|
||
|
|
else:
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
trim.offset = Value(keyframes[0].value)
|
||
|
|
trim.offset.keyframes = keyframes
|
||
|
|
trim.offset.animated = True
|
||
|
|
else:
|
||
|
|
trim.offset = Value(0)
|
||
|
|
trim.offset.animated = True
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/offset)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
value = extract_number(line)
|
||
|
|
trim.offset = Value(value)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(multiple'):
|
||
|
|
value = int(extract_number(line))
|
||
|
|
trim.multiple = TrimMultipleShapes(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.strip() == '(/trim)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return trim, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_star_tag(lines, idx):
|
||
|
|
"""Parse star tag and its contents"""
|
||
|
|
star_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
star = Star()
|
||
|
|
star.name = star_attrs.get("name", "")
|
||
|
|
|
||
|
|
if "ix" in star_attrs:
|
||
|
|
star.property_index = int(star_attrs.get("ix", 1))
|
||
|
|
|
||
|
|
# directionstar_type
|
||
|
|
if "d" in star_attrs:
|
||
|
|
star.direction = float(star_attrs.get("d", 1))
|
||
|
|
else:
|
||
|
|
star.direction = 1
|
||
|
|
|
||
|
|
if "sy" in star_attrs:
|
||
|
|
try:
|
||
|
|
sy_value = int(star_attrs.get("sy", 1))
|
||
|
|
# StarType: 1(Star), 2(Polygon)
|
||
|
|
if sy_value in [1, 2]:
|
||
|
|
star.star_type = StarType(sy_value)
|
||
|
|
else:
|
||
|
|
# Star(1)
|
||
|
|
star.star_type = StarType.Star
|
||
|
|
except (ValueError, KeyError):
|
||
|
|
star.star_type = StarType.Star
|
||
|
|
else:
|
||
|
|
star.star_type = StarType.Star
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if line.startswith('(position'):
|
||
|
|
attrs = parse_tag_attrs(line)
|
||
|
|
components = extract_numbers(line)
|
||
|
|
if components:
|
||
|
|
star.position = MultiDimensional(NVector(*components))
|
||
|
|
if "ix" in attrs:
|
||
|
|
star.position_ix = int(attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(inner_radius'):
|
||
|
|
attrs = parse_tag_attrs(line)
|
||
|
|
value = extract_number(line)
|
||
|
|
star.inner_radius = Value(value)
|
||
|
|
if "ix" in attrs:
|
||
|
|
star.inner_radius_ix = int(attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(outer_radius'):
|
||
|
|
attrs = parse_tag_attrs(line)
|
||
|
|
value = extract_number(line)
|
||
|
|
star.outer_radius = Value(value)
|
||
|
|
if "ix" in attrs:
|
||
|
|
star.outer_radius_ix = int(attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(inner_roundness'):
|
||
|
|
attrs = parse_tag_attrs(line)
|
||
|
|
value = extract_number(line)
|
||
|
|
star.inner_roundness = Value(value)
|
||
|
|
if "ix" in attrs:
|
||
|
|
star.inner_roundness_ix = int(attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(outer_roundness'):
|
||
|
|
attrs = parse_tag_attrs(line)
|
||
|
|
value = extract_number(line)
|
||
|
|
star.outer_roundness = Value(value)
|
||
|
|
if "ix" in attrs:
|
||
|
|
star.outer_roundness_ix = int(attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(points_star'):
|
||
|
|
attrs = parse_tag_attrs(line)
|
||
|
|
value = extract_number(line)
|
||
|
|
star.points = Value(value)
|
||
|
|
if "ix" in attrs:
|
||
|
|
star.points_ix = int(attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(star_rotation'):
|
||
|
|
attrs = parse_tag_attrs(line)
|
||
|
|
value = extract_number(line)
|
||
|
|
star.rotation = Value(value)
|
||
|
|
if "ix" in attrs:
|
||
|
|
star.rotation_ix = int(attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
elif line.strip() == '(/star)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return star, idx
|
||
|
|
|
||
|
|
def parse_repeater_tag(lines, idx):
|
||
|
|
"""Parse repeater tag and its contents - Fixed to parse all transform values"""
|
||
|
|
repeater_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
repeater = Repeater()
|
||
|
|
repeater.name = repeater_attrs.get("name", "")
|
||
|
|
|
||
|
|
if "ix" in repeater_attrs:
|
||
|
|
repeater.property_index = int(repeater_attrs.get("ix", 1))
|
||
|
|
|
||
|
|
# Initialize default values
|
||
|
|
repeater.copies = Value(1)
|
||
|
|
repeater.offset = Value(0)
|
||
|
|
repeater.composite = 1 # Default value
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if line.startswith('(copies'):
|
||
|
|
attrs = parse_tag_attrs(line)
|
||
|
|
if 'animated=true' in line:
|
||
|
|
# Handle animated copies
|
||
|
|
value = extract_number(line)
|
||
|
|
repeater.copies = Value(value)
|
||
|
|
repeater.copies.animated = True
|
||
|
|
else:
|
||
|
|
value = extract_number(line)
|
||
|
|
repeater.copies = Value(value)
|
||
|
|
# Extract ix value
|
||
|
|
if "ix" in attrs:
|
||
|
|
repeater.copies_ix = int(attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(repeater_offset'):
|
||
|
|
attrs = parse_tag_attrs(line)
|
||
|
|
if 'animated=true' in line:
|
||
|
|
# Handle animated offset
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
offset_ix = None
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/repeater_offset)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
value = float(attrs.get('s', 0))
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
# Handle easing parameters
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
if i_x_str.startswith('[') and i_x_str.endswith(']'):
|
||
|
|
i_x = float(i_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
|
||
|
|
if i_y_str.startswith('[') and i_y_str.endswith(']'):
|
||
|
|
i_y = float(i_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
if o_x_str.startswith('[') and o_x_str.endswith(']'):
|
||
|
|
o_x = float(o_x_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
|
||
|
|
if o_y_str.startswith('[') and o_y_str.endswith(']'):
|
||
|
|
o_y = float(o_y_str[1:-1])
|
||
|
|
else:
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
elif lines[idx].startswith('(offset_ix'):
|
||
|
|
offset_ix = int(extract_number(lines[idx]))
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
repeater.offset = Value(keyframes[0].value if keyframes else 0)
|
||
|
|
repeater.offset.keyframes = keyframes
|
||
|
|
repeater.offset.animated = True
|
||
|
|
|
||
|
|
if offset_ix is not None:
|
||
|
|
repeater.offset_ix = offset_ix
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/repeater_offset)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
value = extract_number(line)
|
||
|
|
repeater.offset = Value(value)
|
||
|
|
# Extract ix value
|
||
|
|
if "ix" in attrs:
|
||
|
|
repeater.offset_ix = int(attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(composite'):
|
||
|
|
value = int(extract_number(line))
|
||
|
|
repeater.composite = value
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(repeater_transform'):
|
||
|
|
# Parse transform
|
||
|
|
transform = TransformShape()
|
||
|
|
transform.type = "tr"
|
||
|
|
transform.name = "Transform"
|
||
|
|
|
||
|
|
# Set default values
|
||
|
|
transform.anchor = MultiDimensional(NVector(0, 0))
|
||
|
|
transform.position = MultiDimensional(NVector(0, 0))
|
||
|
|
transform.scale = MultiDimensional(NVector(100, 100))
|
||
|
|
transform.rotation = Value(0)
|
||
|
|
transform.start_opacity = Value(100)
|
||
|
|
transform.end_opacity = Value(100)
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/repeater_transform)'):
|
||
|
|
if lines[idx].startswith('(tr_position'):
|
||
|
|
components = extract_numbers(lines[idx])
|
||
|
|
if len(components) >= 2:
|
||
|
|
transform.position = MultiDimensional(NVector(components[0], components[1]))
|
||
|
|
elif lines[idx].startswith('(tr_anchor'):
|
||
|
|
components = extract_numbers(lines[idx])
|
||
|
|
if len(components) >= 2:
|
||
|
|
transform.anchor = MultiDimensional(NVector(components[0], components[1]))
|
||
|
|
elif lines[idx].startswith('(tr_scale'):
|
||
|
|
components = extract_numbers(lines[idx])
|
||
|
|
if len(components) >= 2:
|
||
|
|
transform.scale = MultiDimensional(NVector(components[0], components[1]))
|
||
|
|
elif lines[idx].startswith('(tr_rotation'):
|
||
|
|
# PARSE ROTATION VALUE - THIS WAS MISSING!
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.rotation = Value(value)
|
||
|
|
elif lines[idx].startswith('(tr_start_opacity'):
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.start_opacity = Value(value)
|
||
|
|
elif lines[idx].startswith('(tr_end_opacity'):
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.end_opacity = Value(value)
|
||
|
|
elif lines[idx].startswith('(tr_p_ix'):
|
||
|
|
transform.position_ix = int(extract_number(lines[idx]))
|
||
|
|
elif lines[idx].startswith('(tr_a_ix'):
|
||
|
|
transform.anchor_ix = int(extract_number(lines[idx]))
|
||
|
|
elif lines[idx].startswith('(tr_s_ix'):
|
||
|
|
transform.scale_ix = int(extract_number(lines[idx]))
|
||
|
|
elif lines[idx].startswith('(tr_r_ix'):
|
||
|
|
transform.rotation_ix = int(extract_number(lines[idx]))
|
||
|
|
elif lines[idx].startswith('(tr_so_ix'):
|
||
|
|
transform.start_opacity_ix = int(extract_number(lines[idx]))
|
||
|
|
elif lines[idx].startswith('(tr_eo_ix'):
|
||
|
|
transform.end_opacity_ix = int(extract_number(lines[idx]))
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/repeater_transform)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
repeater.transform = transform
|
||
|
|
elif line.strip() == '(/repeater)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return repeater, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_merge_tag(lines, idx):
|
||
|
|
"""Parse merge tag and its contents"""
|
||
|
|
merge_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
merge = Merge()
|
||
|
|
merge.name = merge_attrs.get("name", "")
|
||
|
|
|
||
|
|
if "ix" in merge_attrs:
|
||
|
|
merge.property_index = int(merge_attrs.get("ix", 1))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if line.startswith('(merge_mode'):
|
||
|
|
value = int(extract_number(line))
|
||
|
|
merge.merge_mode = value
|
||
|
|
idx += 1
|
||
|
|
elif line.strip() == '(/merge)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return merge, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_rounded_corners_tag(lines, idx):
|
||
|
|
"""Parse rounded corners tag and its contents"""
|
||
|
|
rc_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
rounded_corners = RoundedCorners()
|
||
|
|
rounded_corners.name = rc_attrs.get("name", "")
|
||
|
|
|
||
|
|
if "ix" in rc_attrs:
|
||
|
|
rounded_corners.property_index = int(rc_attrs.get("ix", 1))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if line.startswith('(radius'):
|
||
|
|
value = extract_number(line)
|
||
|
|
rounded_corners.radius = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.strip() == '(/rounded_corners)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return rounded_corners, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_twist_tag(lines, idx):
|
||
|
|
"""Parse twist tag and its contents"""
|
||
|
|
twist_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
twist = Twist()
|
||
|
|
twist.name = twist_attrs.get("name", "")
|
||
|
|
|
||
|
|
if "ix" in twist_attrs:
|
||
|
|
twist.property_index = int(twist_attrs.get("ix", 1))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if line.startswith('(angle'):
|
||
|
|
value = extract_number(line)
|
||
|
|
twist.angle = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(center'):
|
||
|
|
components = extract_numbers(line)
|
||
|
|
if components:
|
||
|
|
twist.center = MultiDimensional(NVector(*components))
|
||
|
|
idx += 1
|
||
|
|
elif line.strip() == '(/twist)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return twist, idx
|
||
|
|
|
||
|
|
def parse_stroke_tag(lines, idx):
|
||
|
|
""""""
|
||
|
|
stroke_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
stroke = Stroke()
|
||
|
|
stroke.name = stroke_attrs.get("name", "")
|
||
|
|
|
||
|
|
# property_index
|
||
|
|
if "ix" in stroke_attrs:
|
||
|
|
stroke.property_index = int(stroke_attrs["ix"])
|
||
|
|
|
||
|
|
# bm
|
||
|
|
if "bm" in stroke_attrs:
|
||
|
|
stroke.bm = int(stroke_attrs.get("bm", 0))
|
||
|
|
else:
|
||
|
|
stroke.bm = 0
|
||
|
|
|
||
|
|
#
|
||
|
|
if stroke_attrs.get("color_animated") == "true":
|
||
|
|
# - look for color_keyframe tags
|
||
|
|
stroke.color = ColorValue(Color(0, 0, 0))
|
||
|
|
stroke.color.keyframes = []
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/color_animated)'):
|
||
|
|
if lines[idx].startswith('(color_keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(kf_attrs.get('t', 0))
|
||
|
|
r = float(kf_attrs.get('r', 0))
|
||
|
|
g = float(kf_attrs.get('g', 0))
|
||
|
|
b = float(kf_attrs.get('b', 0))
|
||
|
|
|
||
|
|
color = Color(r, g, b)
|
||
|
|
kf = Keyframe(time, color)
|
||
|
|
|
||
|
|
if 'i_x' in kf_attrs and 'i_y' in kf_attrs:
|
||
|
|
kf.in_tan = {
|
||
|
|
'x': [float(kf_attrs['i_x'])],
|
||
|
|
'y': [float(kf_attrs['i_y'])]
|
||
|
|
}
|
||
|
|
|
||
|
|
if 'o_x' in kf_attrs and 'o_y' in kf_attrs:
|
||
|
|
kf.out_tan = {
|
||
|
|
'x': [float(kf_attrs['o_x'])],
|
||
|
|
'y': [float(kf_attrs['o_y'])]
|
||
|
|
}
|
||
|
|
|
||
|
|
stroke.color.keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
r = float(stroke_attrs.get("r", 0))
|
||
|
|
g = float(stroke_attrs.get("g", 0))
|
||
|
|
b = float(stroke_attrs.get("b", 0))
|
||
|
|
stroke.color = ColorValue(Color(r, g, b))
|
||
|
|
|
||
|
|
# -
|
||
|
|
stroke.color_dimensions = int(stroke_attrs.get("color_dim", 3))
|
||
|
|
|
||
|
|
# aix
|
||
|
|
if "has_c_a" in stroke_attrs:
|
||
|
|
stroke.has_c_a = stroke_attrs["has_c_a"] == "True"
|
||
|
|
if "has_c_ix" in stroke_attrs:
|
||
|
|
stroke.has_c_ix = stroke_attrs["has_c_ix"] == "True"
|
||
|
|
if "c_ix" in stroke_attrs:
|
||
|
|
stroke.c_ix = int(stroke_attrs["c_ix"])
|
||
|
|
|
||
|
|
# line_capline_join -
|
||
|
|
if "lc" in stroke_attrs:
|
||
|
|
try:
|
||
|
|
lc_value = int(stroke_attrs["lc"])
|
||
|
|
# LineCap: 1(Butt), 2(Round), 3(Square)
|
||
|
|
if lc_value in [1, 2, 3]:
|
||
|
|
stroke.line_cap = LineCap(lc_value)
|
||
|
|
else:
|
||
|
|
# Round(2)
|
||
|
|
stroke.line_cap = LineCap(2)
|
||
|
|
except (ValueError, KeyError):
|
||
|
|
stroke.line_cap = LineCap(2)
|
||
|
|
else:
|
||
|
|
stroke.line_cap = LineCap(2)
|
||
|
|
|
||
|
|
if "lj" in stroke_attrs:
|
||
|
|
try:
|
||
|
|
lj_value = int(stroke_attrs["lj"])
|
||
|
|
# LineJoin: 1(Miter), 2(Round), 3(Bevel)
|
||
|
|
if lj_value in [1, 2, 3]:
|
||
|
|
stroke.line_join = LineJoin(lj_value)
|
||
|
|
else:
|
||
|
|
# Round(2)
|
||
|
|
stroke.line_join = LineJoin(2)
|
||
|
|
except (ValueError, KeyError):
|
||
|
|
stroke.line_join = LineJoin(2)
|
||
|
|
else:
|
||
|
|
stroke.line_join = LineJoin(2)
|
||
|
|
|
||
|
|
# miter_limit
|
||
|
|
if "ml" in stroke_attrs:
|
||
|
|
stroke.miter_limit = float(stroke_attrs["ml"])
|
||
|
|
|
||
|
|
# ml2
|
||
|
|
if "ml2_animated" in stroke_attrs:
|
||
|
|
try:
|
||
|
|
keyframes_data = json.loads(stroke_attrs["ml2_animated"])
|
||
|
|
ml2_value = Value(10)
|
||
|
|
ml2_value.keyframes = []
|
||
|
|
for kf_data in keyframes_data:
|
||
|
|
time = kf_data.get('t', 0)
|
||
|
|
value = kf_data.get('s', 10)
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
if 'i' in kf_data:
|
||
|
|
kf.in_tan = kf_data['i']
|
||
|
|
if 'o' in kf_data:
|
||
|
|
kf.out_tan = kf_data['o']
|
||
|
|
ml2_value.keyframes.append(kf)
|
||
|
|
stroke.ml2 = ml2_value
|
||
|
|
except:
|
||
|
|
stroke.ml2 = Value(10)
|
||
|
|
elif "ml2" in stroke_attrs:
|
||
|
|
stroke.ml2 = Value(float(stroke_attrs["ml2"]))
|
||
|
|
|
||
|
|
if "ml2_ix" in stroke_attrs:
|
||
|
|
stroke.ml2_ix = int(stroke_attrs["ml2_ix"])
|
||
|
|
|
||
|
|
# Parse width - check next line
|
||
|
|
if idx + 1 < len(lines):
|
||
|
|
if lines[idx + 1].startswith('(width_animated'):
|
||
|
|
stroke.width = Value(1)
|
||
|
|
stroke.width.keyframes = []
|
||
|
|
idx += 2 # Skip the width_animated tag
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/width_animated)'):
|
||
|
|
if lines[idx].startswith('(width_keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(kf_attrs.get('t', 0))
|
||
|
|
value = float(kf_attrs.get('s', 1))
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in kf_attrs and 'i_y' in kf_attrs:
|
||
|
|
kf.in_tan = {
|
||
|
|
'x': float(kf_attrs['i_x']),
|
||
|
|
'y': float(kf_attrs['i_y'])
|
||
|
|
}
|
||
|
|
|
||
|
|
if 'o_x' in kf_attrs and 'o_y' in kf_attrs:
|
||
|
|
kf.out_tan = {
|
||
|
|
'x': float(kf_attrs['o_x']),
|
||
|
|
'y': float(kf_attrs['o_y'])
|
||
|
|
}
|
||
|
|
|
||
|
|
stroke.width.keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if stroke.width.keyframes:
|
||
|
|
stroke.width.value = stroke.width.keyframes[0].value
|
||
|
|
idx += 1 # Skip the /width_animated tag
|
||
|
|
elif lines[idx + 1].startswith('(width '):
|
||
|
|
# Static width in separate tag
|
||
|
|
idx += 1
|
||
|
|
width_value = extract_number(lines[idx])
|
||
|
|
stroke.width = Value(width_value)
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
stroke.width = Value(1)
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
stroke.width = Value(1)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# Parse opacity - check next line
|
||
|
|
if idx < len(lines):
|
||
|
|
if lines[idx].startswith('(opacity_animated'):
|
||
|
|
stroke.opacity = Value(100)
|
||
|
|
stroke.opacity.keyframes = []
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/opacity_animated)'):
|
||
|
|
if lines[idx].startswith('(opacity_keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(kf_attrs.get('t', 0))
|
||
|
|
value = float(kf_attrs.get('s', 100))
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
if 'i_x' in kf_attrs and 'i_y' in kf_attrs:
|
||
|
|
kf.in_tan = {
|
||
|
|
'x': float(kf_attrs['i_x']),
|
||
|
|
'y': float(kf_attrs['i_y'])
|
||
|
|
}
|
||
|
|
|
||
|
|
if 'o_x' in kf_attrs and 'o_y' in kf_attrs:
|
||
|
|
kf.out_tan = {
|
||
|
|
'x': float(kf_attrs['o_x']),
|
||
|
|
'y': float(kf_attrs['o_y'])
|
||
|
|
}
|
||
|
|
|
||
|
|
stroke.opacity.keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if stroke.opacity.keyframes:
|
||
|
|
stroke.opacity.value = stroke.opacity.keyframes[0].value
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(opacity '):
|
||
|
|
# Static opacity in separate tag
|
||
|
|
opacity_value = extract_number(lines[idx])
|
||
|
|
stroke.opacity = Value(opacity_value)
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
stroke.opacity = Value(100)
|
||
|
|
|
||
|
|
# Dashes handling - updated to parse keyframe format
|
||
|
|
if idx < len(lines):
|
||
|
|
stroke.dashes = []
|
||
|
|
|
||
|
|
# Keep parsing dashes until we hit something that's not dash-related
|
||
|
|
while idx < len(lines):
|
||
|
|
if lines[idx].startswith('(dash_animated'):
|
||
|
|
# Animated dash
|
||
|
|
dash_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
dash = StrokeDash()
|
||
|
|
dash.type = StrokeDashType(dash_attrs.get('type', 'd'))
|
||
|
|
dash.name = dash_attrs.get('name', '')
|
||
|
|
if 'v_ix' in dash_attrs:
|
||
|
|
dash.v_ix = int(dash_attrs['v_ix'])
|
||
|
|
|
||
|
|
dash.length = Value(0)
|
||
|
|
dash.length.keyframes = []
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/dash_animated)'):
|
||
|
|
if lines[idx].startswith('(dash_keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(kf_attrs.get('t', 0))
|
||
|
|
value = float(kf_attrs.get('s', 0))
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
# Restore hold property if present
|
||
|
|
if 'h' in kf_attrs:
|
||
|
|
kf.hold = kf_attrs['h'] == 'True' if kf_attrs['h'] in ['True', 'False'] else bool(kf_attrs['h'])
|
||
|
|
|
||
|
|
if 'i_x' in kf_attrs and 'i_y' in kf_attrs:
|
||
|
|
kf.in_tan = {
|
||
|
|
'x': float(kf_attrs['i_x']),
|
||
|
|
'y': float(kf_attrs['i_y'])
|
||
|
|
}
|
||
|
|
|
||
|
|
if 'o_x' in kf_attrs and 'o_y' in kf_attrs:
|
||
|
|
kf.out_tan = {
|
||
|
|
'x': float(kf_attrs['o_x']),
|
||
|
|
'y': float(kf_attrs['o_y'])
|
||
|
|
}
|
||
|
|
|
||
|
|
dash.length.keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if dash.length.keyframes:
|
||
|
|
dash.length.value = dash.length.keyframes[0].value
|
||
|
|
|
||
|
|
stroke.dashes.append(dash)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(dash '):
|
||
|
|
# Static dash
|
||
|
|
dash_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
dash = StrokeDash()
|
||
|
|
dash.type = StrokeDashType(dash_attrs.get('type', 'd'))
|
||
|
|
dash.length = Value(float(dash_attrs.get('length', 0)))
|
||
|
|
dash.name = dash_attrs.get('name', '')
|
||
|
|
if 'v_ix' in dash_attrs:
|
||
|
|
dash.v_ix = int(dash_attrs['v_ix'])
|
||
|
|
|
||
|
|
stroke.dashes.append(dash)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(dashes'):
|
||
|
|
# Old format for backwards compatibility
|
||
|
|
dashes_str = extract_string_value(lines[idx])
|
||
|
|
if dashes_str:
|
||
|
|
dash_pairs = dashes_str.split(';')
|
||
|
|
for dash_pair in dash_pairs:
|
||
|
|
parts = dash_pair.split('|')
|
||
|
|
if len(parts) >= 2:
|
||
|
|
dash = StrokeDash()
|
||
|
|
dash.type = StrokeDashType(parts[0])
|
||
|
|
|
||
|
|
try:
|
||
|
|
dash.length = Value(float(parts[1]))
|
||
|
|
except ValueError:
|
||
|
|
dash.length = Value(0)
|
||
|
|
|
||
|
|
if len(parts) > 2 and parts[2]:
|
||
|
|
dash.name = parts[2]
|
||
|
|
if len(parts) > 3 and parts[3]:
|
||
|
|
try:
|
||
|
|
dash.v_ix = int(parts[3])
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
stroke.dashes.append(dash)
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
# Not a dash-related tag, stop parsing dashes
|
||
|
|
break
|
||
|
|
|
||
|
|
return stroke, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_rect_tag(lines, idx):
|
||
|
|
""""""
|
||
|
|
rect_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
rect = Rect()
|
||
|
|
rect.name = rect_attrs.get("name", "")
|
||
|
|
|
||
|
|
# property_index
|
||
|
|
if "ix" in rect_attrs:
|
||
|
|
rect.property_index = int(rect_attrs.get("ix", 1))
|
||
|
|
# hd
|
||
|
|
if "hd" in rect_attrs:
|
||
|
|
rect.hd = rect_attrs.get("hd", "false").lower() == "true"
|
||
|
|
|
||
|
|
# direction
|
||
|
|
if "d" in rect_attrs:
|
||
|
|
rect.direction = float(rect_attrs.get("d", 1))
|
||
|
|
else:
|
||
|
|
rect.direction = 1
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
# Handle animated position
|
||
|
|
if line.startswith('(position') and 'animated=true' in line:
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/position)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
s_str = attrs.get('s', '')
|
||
|
|
if s_str:
|
||
|
|
values = [float(x) for x in s_str.split()]
|
||
|
|
value = NVector(*values) if values else NVector(0, 0)
|
||
|
|
else:
|
||
|
|
value = NVector(0, 0)
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
# Handle easing parameters -
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x = attrs['i_x']
|
||
|
|
i_y = attrs['i_y']
|
||
|
|
#
|
||
|
|
if ' ' in i_x:
|
||
|
|
i_x_vals = [float(v) for v in i_x.split()]
|
||
|
|
i_y_vals = [float(v) for v in i_y.split()]
|
||
|
|
else:
|
||
|
|
i_x_vals = [float(i_x)]
|
||
|
|
i_y_vals = [float(i_y)]
|
||
|
|
kf.in_tan = {
|
||
|
|
'x': i_x_vals,
|
||
|
|
'y': i_y_vals
|
||
|
|
}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x = attrs['o_x']
|
||
|
|
o_y = attrs['o_y']
|
||
|
|
#
|
||
|
|
if ' ' in o_x:
|
||
|
|
o_x_vals = [float(v) for v in o_x.split()]
|
||
|
|
o_y_vals = [float(v) for v in o_y.split()]
|
||
|
|
else:
|
||
|
|
o_x_vals = [float(o_x)]
|
||
|
|
o_y_vals = [float(o_y)]
|
||
|
|
kf.out_tan = {
|
||
|
|
'x': o_x_vals,
|
||
|
|
'y': o_y_vals
|
||
|
|
}
|
||
|
|
|
||
|
|
if 'to' in attrs:
|
||
|
|
try:
|
||
|
|
kf.to = json.loads(attrs['to'])
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
if 'ti' in attrs:
|
||
|
|
try:
|
||
|
|
kf.ti = json.loads(attrs['ti'])
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
if keyframes:
|
||
|
|
rect.position = Value(keyframes[0].value)
|
||
|
|
rect.position.keyframes = keyframes
|
||
|
|
rect.position.animated = True
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/position)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# Handle static position
|
||
|
|
elif line.startswith('(position') and 'animated=true' not in line:
|
||
|
|
components = extract_numbers(line)
|
||
|
|
if components:
|
||
|
|
rect.position = Value(NVector(*components))
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# Handle animated size
|
||
|
|
elif line.startswith('(size') and 'animated=true' in line:
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/size)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
s_str = attrs.get('s', '')
|
||
|
|
if s_str:
|
||
|
|
values = [float(x) for x in s_str.split()]
|
||
|
|
value = NVector(*values) if values else NVector(100, 100)
|
||
|
|
else:
|
||
|
|
value = NVector(100, 100)
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
# Handle easing parameters -
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x = attrs['i_x']
|
||
|
|
i_y = attrs['i_y']
|
||
|
|
#
|
||
|
|
if ' ' in i_x:
|
||
|
|
i_x_vals = [float(v) for v in i_x.split()]
|
||
|
|
i_y_vals = [float(v) for v in i_y.split()]
|
||
|
|
else:
|
||
|
|
i_x_vals = [float(i_x)]
|
||
|
|
i_y_vals = [float(i_y)]
|
||
|
|
kf.in_tan = {
|
||
|
|
'x': i_x_vals,
|
||
|
|
'y': i_y_vals
|
||
|
|
}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x = attrs['o_x']
|
||
|
|
o_y = attrs['o_y']
|
||
|
|
#
|
||
|
|
if ' ' in o_x:
|
||
|
|
o_x_vals = [float(v) for v in o_x.split()]
|
||
|
|
o_y_vals = [float(v) for v in o_y.split()]
|
||
|
|
else:
|
||
|
|
o_x_vals = [float(o_x)]
|
||
|
|
o_y_vals = [float(o_y)]
|
||
|
|
kf.out_tan = {
|
||
|
|
'x': o_x_vals,
|
||
|
|
'y': o_y_vals
|
||
|
|
}
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
if keyframes:
|
||
|
|
rect.size = Value(keyframes[0].value)
|
||
|
|
rect.size.keyframes = keyframes
|
||
|
|
rect.size.animated = True
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/size)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# Handle static size
|
||
|
|
elif line.startswith('(rect_size') and 'animated=true' not in line:
|
||
|
|
components = extract_numbers(line)
|
||
|
|
if components:
|
||
|
|
rect.size = Value(NVector(*components))
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# Handle animated rounded
|
||
|
|
elif line.startswith('(rounded') and 'animated=true' in line:
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/rounded)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
s_str = attrs.get('s', '')
|
||
|
|
if s_str:
|
||
|
|
values = [float(x) for x in s_str.split()]
|
||
|
|
value = values[0] if values else 0
|
||
|
|
else:
|
||
|
|
value = 0
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
# Handle easing parameters -
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x = attrs['i_x']
|
||
|
|
i_y = attrs['i_y']
|
||
|
|
#
|
||
|
|
if ' ' in i_x:
|
||
|
|
i_x_vals = [float(v) for v in i_x.split()]
|
||
|
|
i_y_vals = [float(v) for v in i_y.split()]
|
||
|
|
else:
|
||
|
|
i_x_vals = [float(i_x)]
|
||
|
|
i_y_vals = [float(i_y)]
|
||
|
|
kf.in_tan = {
|
||
|
|
'x': i_x_vals,
|
||
|
|
'y': i_y_vals
|
||
|
|
}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x = attrs['o_x']
|
||
|
|
o_y = attrs['o_y']
|
||
|
|
#
|
||
|
|
if ' ' in o_x:
|
||
|
|
o_x_vals = [float(v) for v in o_x.split()]
|
||
|
|
o_y_vals = [float(v) for v in o_y.split()]
|
||
|
|
else:
|
||
|
|
o_x_vals = [float(o_x)]
|
||
|
|
o_y_vals = [float(o_y)]
|
||
|
|
kf.out_tan = {
|
||
|
|
'x': o_x_vals,
|
||
|
|
'y': o_y_vals
|
||
|
|
}
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
if keyframes:
|
||
|
|
rect.rounded = Value(keyframes[0].value)
|
||
|
|
rect.rounded.keyframes = keyframes
|
||
|
|
rect.rounded.animated = True
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/rounded)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# Handle static rounded - FIX: Parse numeric values correctly
|
||
|
|
elif line.startswith('(rounded') and 'animated=true' not in line:
|
||
|
|
components = extract_numbers(line)
|
||
|
|
if components:
|
||
|
|
# For rounded, we typically want just a single value
|
||
|
|
rect.rounded = Value(components[0] if components else 0)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.strip() == '(/rect)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return rect, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_fill_tag(lines, idx):
|
||
|
|
""" - """
|
||
|
|
fill_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
fill = Fill()
|
||
|
|
fill.name = fill_attrs.get("name", "")
|
||
|
|
|
||
|
|
# property_index
|
||
|
|
if "ix" in fill_attrs:
|
||
|
|
fill.property_index = int(fill_attrs.get("ix", 1))
|
||
|
|
|
||
|
|
# bm
|
||
|
|
if "bm" in fill_attrs:
|
||
|
|
fill.bm = int(fill_attrs.get("bm", 0))
|
||
|
|
else:
|
||
|
|
fill.bm = 0
|
||
|
|
|
||
|
|
# Check for animated color
|
||
|
|
if fill_attrs.get("color_animated") == "true":
|
||
|
|
# Parse animated color keyframes
|
||
|
|
kf_count = int(fill_attrs.get("c_kf_count", 0))
|
||
|
|
fill.color = ColorValue(Color(0, 0, 0))
|
||
|
|
|
||
|
|
# FIX: Ensure keyframes is a list, not None
|
||
|
|
if not hasattr(fill.color, 'keyframes'):
|
||
|
|
fill.color.keyframes = []
|
||
|
|
elif fill.color.keyframes is None:
|
||
|
|
fill.color.keyframes = []
|
||
|
|
|
||
|
|
for i in range(kf_count):
|
||
|
|
time = float(fill_attrs.get(f"c_kf_{i}_t", 0))
|
||
|
|
r = float(fill_attrs.get(f"c_kf_{i}_r", 0))
|
||
|
|
g = float(fill_attrs.get(f"c_kf_{i}_g", 0))
|
||
|
|
b = float(fill_attrs.get(f"c_kf_{i}_b", 0))
|
||
|
|
|
||
|
|
color = Color(r, g, b)
|
||
|
|
kf = Keyframe(time, color)
|
||
|
|
|
||
|
|
# Parse tangents if present
|
||
|
|
if f"c_kf_{i}_i_x" in fill_attrs:
|
||
|
|
kf.in_tan = {
|
||
|
|
'x': [float(fill_attrs.get(f"c_kf_{i}_i_x", 0.667))],
|
||
|
|
'y': [float(fill_attrs.get(f"c_kf_{i}_i_y", 1))]
|
||
|
|
}
|
||
|
|
|
||
|
|
if f"c_kf_{i}_o_x" in fill_attrs:
|
||
|
|
kf.out_tan = {
|
||
|
|
'x': [float(fill_attrs.get(f"c_kf_{i}_o_x", 0.333))],
|
||
|
|
'y': [float(fill_attrs.get(f"c_kf_{i}_o_y", 0))]
|
||
|
|
}
|
||
|
|
|
||
|
|
fill.color.keyframes.append(kf)
|
||
|
|
else:
|
||
|
|
# Static color
|
||
|
|
try:
|
||
|
|
r = float(fill_attrs.get("r", 0))
|
||
|
|
except ValueError:
|
||
|
|
r = 0
|
||
|
|
try:
|
||
|
|
g = float(fill_attrs.get("g", 0))
|
||
|
|
except ValueError:
|
||
|
|
g = 0
|
||
|
|
try:
|
||
|
|
b = float(fill_attrs.get("b", 0))
|
||
|
|
except ValueError:
|
||
|
|
b = 0
|
||
|
|
|
||
|
|
fill.color = ColorValue(Color(r, g, b))
|
||
|
|
|
||
|
|
#
|
||
|
|
fill.color_dimensions = int(fill_attrs.get("color_dim", 3))
|
||
|
|
|
||
|
|
# aix
|
||
|
|
if "has_c_a" in fill_attrs:
|
||
|
|
fill.has_c_a = fill_attrs["has_c_a"] == "True"
|
||
|
|
if "has_c_ix" in fill_attrs:
|
||
|
|
fill.has_c_ix = fill_attrs["has_c_ix"] == "True"
|
||
|
|
if "c_ix" in fill_attrs:
|
||
|
|
fill.c_ix = int(fill_attrs["c_ix"])
|
||
|
|
|
||
|
|
# Parse fill_rule if present -
|
||
|
|
if "fill_rule" in fill_attrs:
|
||
|
|
try:
|
||
|
|
fill_rule_value = int(fill_attrs.get("fill_rule", 1))
|
||
|
|
# FillRule: 1(NonZero), 2(EvenOdd)
|
||
|
|
if fill_rule_value in [1, 2]:
|
||
|
|
fill.fill_rule = FillRule(fill_rule_value)
|
||
|
|
else:
|
||
|
|
# NonZero(1)
|
||
|
|
fill.fill_rule = FillRule(1)
|
||
|
|
except (ValueError, KeyError):
|
||
|
|
fill.fill_rule = FillRule(1)
|
||
|
|
|
||
|
|
# Handle opacity - Fixed: properly parse animated opacity keyframes
|
||
|
|
if fill_attrs.get("opacity_animated") == "true":
|
||
|
|
# Parse animated opacity keyframes
|
||
|
|
kf_count = int(fill_attrs.get("o_kf_count", 0))
|
||
|
|
opacity_value = Value(100)
|
||
|
|
opacity_value.animated = True
|
||
|
|
|
||
|
|
# FIX: Ensure keyframes is a list, not None
|
||
|
|
if not hasattr(opacity_value, 'keyframes'):
|
||
|
|
opacity_value.keyframes = []
|
||
|
|
elif opacity_value.keyframes is None:
|
||
|
|
opacity_value.keyframes = []
|
||
|
|
|
||
|
|
for i in range(kf_count):
|
||
|
|
time = float(fill_attrs.get(f"o_kf_{i}_t", 0))
|
||
|
|
value = float(fill_attrs.get(f"o_kf_{i}_s", 100))
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
# Parse tangents if present
|
||
|
|
if f"o_kf_{i}_i_x" in fill_attrs:
|
||
|
|
kf.in_tan = {
|
||
|
|
'x': float(fill_attrs.get(f"o_kf_{i}_i_x", 0.667)),
|
||
|
|
'y': float(fill_attrs.get(f"o_kf_{i}_i_y", 1))
|
||
|
|
}
|
||
|
|
|
||
|
|
if f"o_kf_{i}_o_x" in fill_attrs:
|
||
|
|
kf.out_tan = {
|
||
|
|
'x': float(fill_attrs.get(f"o_kf_{i}_o_x", 0.333)),
|
||
|
|
'y': float(fill_attrs.get(f"o_kf_{i}_o_y", 0))
|
||
|
|
}
|
||
|
|
|
||
|
|
opacity_value.keyframes.append(kf)
|
||
|
|
|
||
|
|
fill.opacity = opacity_value
|
||
|
|
else:
|
||
|
|
# Static opacity
|
||
|
|
try:
|
||
|
|
fill.opacity = Value(float(fill_attrs.get("opacity", 100)))
|
||
|
|
except:
|
||
|
|
fill.opacity = Value(100)
|
||
|
|
|
||
|
|
# aix
|
||
|
|
if "has_o_a" in fill_attrs:
|
||
|
|
fill.has_o_a = fill_attrs["has_o_a"] == "True"
|
||
|
|
if "has_o_ix" in fill_attrs:
|
||
|
|
fill.has_o_ix = fill_attrs["has_o_ix"] == "True"
|
||
|
|
if "o_ix" in fill_attrs:
|
||
|
|
fill.o_ix = int(fill_attrs["o_ix"])
|
||
|
|
|
||
|
|
return fill, idx + 1
|
||
|
|
|
||
|
|
def parse_effects_tag(lines, start_idx):
|
||
|
|
""""""
|
||
|
|
effects_list = []
|
||
|
|
idx = start_idx
|
||
|
|
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx].strip()
|
||
|
|
|
||
|
|
if line.startswith('(effect '):
|
||
|
|
#
|
||
|
|
effect_dict, new_idx = parse_effect_tag(lines, idx)
|
||
|
|
effects_list.append(effect_dict)
|
||
|
|
idx = new_idx
|
||
|
|
elif line.startswith('(/'):
|
||
|
|
#
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return effects_list, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_effect_tag(lines, idx):
|
||
|
|
""""""
|
||
|
|
import json
|
||
|
|
|
||
|
|
effect_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
#
|
||
|
|
effect_dict = {
|
||
|
|
'nm': effect_attrs.get("name", ""),
|
||
|
|
'ty': int(effect_attrs.get("type", 5)),
|
||
|
|
'ix': int(effect_attrs.get("index", 1)),
|
||
|
|
'mn': effect_attrs.get("match_name", ""),
|
||
|
|
'en': int(effect_attrs.get("enabled", 1)),
|
||
|
|
'ef': []
|
||
|
|
}
|
||
|
|
|
||
|
|
# np
|
||
|
|
if "np" in effect_attrs:
|
||
|
|
effect_dict['np'] = int(effect_attrs.get("np", 0))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
def parse_keyframes(lines, start_idx, end_tag):
|
||
|
|
""""""
|
||
|
|
keyframes = []
|
||
|
|
idx = start_idx
|
||
|
|
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx].strip()
|
||
|
|
|
||
|
|
if line.startswith('(keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(line)
|
||
|
|
kf = {
|
||
|
|
't': float(kf_attrs.get('t', 0))
|
||
|
|
}
|
||
|
|
|
||
|
|
#
|
||
|
|
if 's' in kf_attrs:
|
||
|
|
# slider, angle, checkbox, dropdown
|
||
|
|
kf['s'] = [float(kf_attrs['s'])]
|
||
|
|
elif 'x' in kf_attrs and 'y' in kf_attrs:
|
||
|
|
# Point
|
||
|
|
kf['s'] = [float(kf_attrs['x']), float(kf_attrs['y'])]
|
||
|
|
elif 'r' in kf_attrs and 'g' in kf_attrs and 'b' in kf_attrs:
|
||
|
|
# Color
|
||
|
|
color_val = [
|
||
|
|
float(kf_attrs['r']),
|
||
|
|
float(kf_attrs['g']),
|
||
|
|
float(kf_attrs['b'])
|
||
|
|
]
|
||
|
|
if 'a' in kf_attrs:
|
||
|
|
color_val.append(float(kf_attrs['a']))
|
||
|
|
else:
|
||
|
|
color_val.append(1)
|
||
|
|
kf['s'] = color_val
|
||
|
|
|
||
|
|
#
|
||
|
|
if 'i_x' in kf_attrs and 'i_y' in kf_attrs:
|
||
|
|
kf['i'] = {
|
||
|
|
'x': [float(kf_attrs['i_x'])],
|
||
|
|
'y': [float(kf_attrs['i_y'])]
|
||
|
|
}
|
||
|
|
|
||
|
|
#
|
||
|
|
if 'o_x' in kf_attrs and 'o_y' in kf_attrs:
|
||
|
|
kf['o'] = {
|
||
|
|
'x': [float(kf_attrs['o_x'])],
|
||
|
|
'y': [float(kf_attrs['o_y'])]
|
||
|
|
}
|
||
|
|
|
||
|
|
# hold
|
||
|
|
if 'h' in kf_attrs:
|
||
|
|
kf['h'] = int(kf_attrs['h'])
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith(end_tag):
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return keyframes, idx
|
||
|
|
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx].strip()
|
||
|
|
|
||
|
|
if line.startswith('(slider'):
|
||
|
|
slider_attrs = parse_tag_attrs(line)
|
||
|
|
|
||
|
|
#
|
||
|
|
if slider_attrs.get('animated') == 'true':
|
||
|
|
#
|
||
|
|
idx += 1
|
||
|
|
print("lines", lines)
|
||
|
|
keyframes, idx = parse_keyframes(lines, idx, '(/slider)')
|
||
|
|
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 0,
|
||
|
|
'nm': slider_attrs.get("name", ""),
|
||
|
|
'mn': slider_attrs.get("match_name", ""),
|
||
|
|
'ix': int(slider_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 1,
|
||
|
|
'k': keyframes,
|
||
|
|
'ix': int(slider_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
try:
|
||
|
|
slider_value = float(slider_attrs.get("value", 0))
|
||
|
|
except (ValueError, TypeError):
|
||
|
|
slider_value = 0
|
||
|
|
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 0,
|
||
|
|
'nm': slider_attrs.get("name", ""),
|
||
|
|
'mn': slider_attrs.get("match_name", ""),
|
||
|
|
'ix': int(slider_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 0,
|
||
|
|
'k': slider_value,
|
||
|
|
'ix': int(slider_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
effect_dict['ef'].append(sub_effect)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(color'):
|
||
|
|
color_attrs = parse_tag_attrs(line)
|
||
|
|
|
||
|
|
if color_attrs.get('animated') == 'true':
|
||
|
|
idx += 1
|
||
|
|
keyframes, idx = parse_keyframes(lines, idx, '(/color)')
|
||
|
|
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 2,
|
||
|
|
'nm': color_attrs.get("name", ""),
|
||
|
|
'mn': color_attrs.get("match_name", ""),
|
||
|
|
'ix': int(color_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 1,
|
||
|
|
'k': keyframes,
|
||
|
|
'ix': int(color_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 2,
|
||
|
|
'nm': color_attrs.get("name", ""),
|
||
|
|
'mn': color_attrs.get("match_name", ""),
|
||
|
|
'ix': int(color_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 0,
|
||
|
|
'k': [
|
||
|
|
float(color_attrs.get("r", 0)),
|
||
|
|
float(color_attrs.get("g", 0)),
|
||
|
|
float(color_attrs.get("b", 0)),
|
||
|
|
1
|
||
|
|
],
|
||
|
|
'ix': int(color_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
effect_dict['ef'].append(sub_effect)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(angle'):
|
||
|
|
angle_attrs = parse_tag_attrs(line)
|
||
|
|
|
||
|
|
if angle_attrs.get('animated') == 'true':
|
||
|
|
idx += 1
|
||
|
|
keyframes, idx = parse_keyframes(lines, idx, '(/angle)')
|
||
|
|
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 1,
|
||
|
|
'nm': angle_attrs.get("name", ""),
|
||
|
|
'mn': angle_attrs.get("match_name", ""),
|
||
|
|
'ix': int(angle_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 1,
|
||
|
|
'k': keyframes,
|
||
|
|
'ix': int(angle_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 1,
|
||
|
|
'nm': angle_attrs.get("name", ""),
|
||
|
|
'mn': angle_attrs.get("match_name", ""),
|
||
|
|
'ix': int(angle_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 0,
|
||
|
|
'k': float(angle_attrs.get("value", 0)),
|
||
|
|
'ix': int(angle_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
effect_dict['ef'].append(sub_effect)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(point'):
|
||
|
|
point_attrs = parse_tag_attrs(line)
|
||
|
|
|
||
|
|
if point_attrs.get('animated') == 'true':
|
||
|
|
idx += 1
|
||
|
|
keyframes, idx = parse_keyframes(lines, idx, '(/point)')
|
||
|
|
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 3,
|
||
|
|
'nm': point_attrs.get("name", ""),
|
||
|
|
'mn': point_attrs.get("match_name", ""),
|
||
|
|
'ix': int(point_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 1,
|
||
|
|
'k': keyframes,
|
||
|
|
'ix': int(point_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 3,
|
||
|
|
'nm': point_attrs.get("name", ""),
|
||
|
|
'mn': point_attrs.get("match_name", ""),
|
||
|
|
'ix': int(point_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 0,
|
||
|
|
'k': [
|
||
|
|
float(point_attrs.get("x", 0)),
|
||
|
|
float(point_attrs.get("y", 0))
|
||
|
|
],
|
||
|
|
'ix': int(point_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
effect_dict['ef'].append(sub_effect)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(checkbox'):
|
||
|
|
checkbox_attrs = parse_tag_attrs(line)
|
||
|
|
|
||
|
|
if checkbox_attrs.get('animated') == 'true':
|
||
|
|
idx += 1
|
||
|
|
keyframes, idx = parse_keyframes(lines, idx, '(/checkbox)')
|
||
|
|
|
||
|
|
#
|
||
|
|
for kf in keyframes:
|
||
|
|
if 's' in kf and isinstance(kf['s'], list):
|
||
|
|
kf['s'] = [int(kf['s'][0])]
|
||
|
|
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 4,
|
||
|
|
'nm': checkbox_attrs.get("name", ""),
|
||
|
|
'mn': checkbox_attrs.get("match_name", ""),
|
||
|
|
'ix': int(checkbox_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 1,
|
||
|
|
'k': keyframes,
|
||
|
|
'ix': int(checkbox_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 4,
|
||
|
|
'nm': checkbox_attrs.get("name", ""),
|
||
|
|
'mn': checkbox_attrs.get("match_name", ""),
|
||
|
|
'ix': int(checkbox_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 0,
|
||
|
|
'k': int(checkbox_attrs.get("value", 0)),
|
||
|
|
'ix': int(checkbox_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
effect_dict['ef'].append(sub_effect)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(dropdown'):
|
||
|
|
dropdown_attrs = parse_tag_attrs(line)
|
||
|
|
|
||
|
|
if dropdown_attrs.get('animated') == 'true':
|
||
|
|
idx += 1
|
||
|
|
keyframes, idx = parse_keyframes(lines, idx, '(/dropdown)')
|
||
|
|
|
||
|
|
#
|
||
|
|
for kf in keyframes:
|
||
|
|
if 's' in kf and isinstance(kf['s'], list):
|
||
|
|
kf['s'] = [int(kf['s'][0])]
|
||
|
|
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 7,
|
||
|
|
'nm': dropdown_attrs.get("name", ""),
|
||
|
|
'mn': dropdown_attrs.get("match_name", ""),
|
||
|
|
'ix': int(dropdown_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 1,
|
||
|
|
'k': keyframes,
|
||
|
|
'ix': int(dropdown_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 7,
|
||
|
|
'nm': dropdown_attrs.get("name", ""),
|
||
|
|
'mn': dropdown_attrs.get("match_name", ""),
|
||
|
|
'ix': int(dropdown_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 0,
|
||
|
|
'k': int(dropdown_attrs.get("value", 1)),
|
||
|
|
'ix': int(dropdown_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
effect_dict['ef'].append(sub_effect)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(layer_effect'):
|
||
|
|
layer_attrs = parse_tag_attrs(line)
|
||
|
|
|
||
|
|
if layer_attrs.get('animated') == 'true':
|
||
|
|
idx += 1
|
||
|
|
keyframes, idx = parse_keyframes(lines, idx, '(/layer_effect)')
|
||
|
|
|
||
|
|
#
|
||
|
|
for kf in keyframes:
|
||
|
|
if 's' in kf and isinstance(kf['s'], list):
|
||
|
|
kf['s'] = [int(kf['s'][0])]
|
||
|
|
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 10,
|
||
|
|
'nm': layer_attrs.get("name", ""),
|
||
|
|
'mn': layer_attrs.get("match_name", ""),
|
||
|
|
'ix': int(layer_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 1,
|
||
|
|
'k': keyframes,
|
||
|
|
'ix': int(layer_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
try:
|
||
|
|
layer_value = int(layer_attrs.get("value", 0))
|
||
|
|
except (ValueError, TypeError):
|
||
|
|
layer_value = 0
|
||
|
|
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 10,
|
||
|
|
'nm': layer_attrs.get("name", ""),
|
||
|
|
'mn': layer_attrs.get("match_name", ""),
|
||
|
|
'ix': int(layer_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 0,
|
||
|
|
'k': layer_value,
|
||
|
|
'ix': int(layer_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
effect_dict['ef'].append(sub_effect)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(ignored'):
|
||
|
|
ignored_attrs = parse_tag_attrs(line)
|
||
|
|
|
||
|
|
if ignored_attrs.get('animated') == 'true':
|
||
|
|
idx += 1
|
||
|
|
keyframes, idx = parse_keyframes(lines, idx, '(/ignored)')
|
||
|
|
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 0, # slider
|
||
|
|
'nm': ignored_attrs.get("name", ""),
|
||
|
|
'mn': ignored_attrs.get("match_name", ""),
|
||
|
|
'ix': int(ignored_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 1,
|
||
|
|
'k': keyframes,
|
||
|
|
'ix': int(ignored_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
try:
|
||
|
|
ignored_value = float(ignored_attrs.get("value", 0))
|
||
|
|
except (ValueError, TypeError):
|
||
|
|
ignored_value = 0
|
||
|
|
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 0, # slider
|
||
|
|
'nm': ignored_attrs.get("name", ""),
|
||
|
|
'mn': ignored_attrs.get("match_name", ""),
|
||
|
|
'ix': int(ignored_attrs.get("index", 0)),
|
||
|
|
'v': {
|
||
|
|
'a': 0,
|
||
|
|
'k': ignored_value,
|
||
|
|
'ix': int(ignored_attrs.get("index", 0))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
effect_dict['ef'].append(sub_effect)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(no_value'):
|
||
|
|
no_value_attrs = parse_tag_attrs(line)
|
||
|
|
sub_effect = {
|
||
|
|
'ty': 6,
|
||
|
|
'nm': no_value_attrs.get("name", ""),
|
||
|
|
'mn': no_value_attrs.get("match_name", ""),
|
||
|
|
'ix': int(no_value_attrs.get("index", 0)),
|
||
|
|
'v': 0
|
||
|
|
}
|
||
|
|
effect_dict['ef'].append(sub_effect)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line == '(/effect)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return effect_dict, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_gradient_fill_tag(lines, idx):
|
||
|
|
""" - """
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
gradient_fill = GradientFill()
|
||
|
|
gradient_fill.name = attrs.get("name", "")
|
||
|
|
|
||
|
|
if "ix" in attrs:
|
||
|
|
gradient_fill.property_index = int(attrs.get("ix", 1))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
colors_data = []
|
||
|
|
original_array = None
|
||
|
|
color_points = 0
|
||
|
|
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if line.startswith('(opacity'):
|
||
|
|
value = extract_number(line)
|
||
|
|
gradient_fill.opacity = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(fill_rule'):
|
||
|
|
value = int(extract_number(line))
|
||
|
|
gradient_fill.fill_rule = FillRule(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(start_point'):
|
||
|
|
components = extract_numbers(line)
|
||
|
|
if components:
|
||
|
|
gradient_fill.start_point = MultiDimensional(NVector(*components))
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(end_point'):
|
||
|
|
components = extract_numbers(line)
|
||
|
|
if components:
|
||
|
|
gradient_fill.end_point = MultiDimensional(NVector(*components))
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(gradient_type'):
|
||
|
|
value = int(extract_number(line))
|
||
|
|
gradient_fill.gradient_type = GradientType(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(highlight_length'):
|
||
|
|
value = extract_number(line)
|
||
|
|
gradient_fill.highlight_length = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(highlight_angle'):
|
||
|
|
value = extract_number(line)
|
||
|
|
gradient_fill.highlight_angle = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(original_colors'):
|
||
|
|
#
|
||
|
|
text = line[line.find(' ') + 1:].strip().rstrip(')')
|
||
|
|
if text:
|
||
|
|
try:
|
||
|
|
original_array = json.loads(text)
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(color_points'):
|
||
|
|
color_points = int(extract_number(line))
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(colors'):
|
||
|
|
#
|
||
|
|
text = line[line.find(' ') + 1:].strip().rstrip(')')
|
||
|
|
if text:
|
||
|
|
for color_data in text.split():
|
||
|
|
parts = color_data.split(',')
|
||
|
|
if len(parts) >= 4:
|
||
|
|
pos = float(parts[0])
|
||
|
|
r = float(parts[1])
|
||
|
|
g = float(parts[2])
|
||
|
|
b = float(parts[3])
|
||
|
|
colors_data.append((pos, Color(r, g, b)))
|
||
|
|
idx += 1
|
||
|
|
elif line.strip() == '(/gradient_fill)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
#
|
||
|
|
if original_array:
|
||
|
|
#
|
||
|
|
gradient_fill._original_color_array = original_array
|
||
|
|
gradient_fill._color_points = color_points
|
||
|
|
|
||
|
|
#
|
||
|
|
if color_points > 0:
|
||
|
|
values_per_point = len(original_array) / color_points
|
||
|
|
colors = []
|
||
|
|
|
||
|
|
if values_per_point >= 4:
|
||
|
|
step = int(values_per_point)
|
||
|
|
for i in range(color_points):
|
||
|
|
base = i * step
|
||
|
|
pos = original_array[base]
|
||
|
|
r = original_array[base + 1]
|
||
|
|
g = original_array[base + 2]
|
||
|
|
b = original_array[base + 3]
|
||
|
|
colors.append((pos, Color(r, g, b)))
|
||
|
|
gradient_fill.colors = GradientColors(colors)
|
||
|
|
elif colors_data:
|
||
|
|
gradient_fill.colors = GradientColors(colors_data)
|
||
|
|
else:
|
||
|
|
#
|
||
|
|
gradient_fill.colors = GradientColors([
|
||
|
|
(0.0, Color(0.85, 0.36, 0.33)),
|
||
|
|
(0.15, Color(0.84, 1.0, 0.0))
|
||
|
|
])
|
||
|
|
|
||
|
|
return gradient_fill, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_transform_shape_tag(lines, idx):
|
||
|
|
"""TransformShape - """
|
||
|
|
transform_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
transform = TransformShape()
|
||
|
|
transform.name = transform_attrs.get("name", "")
|
||
|
|
|
||
|
|
# property_index
|
||
|
|
if "ix" in transform_attrs:
|
||
|
|
transform.property_index = int(transform_attrs.get("ix"))
|
||
|
|
|
||
|
|
if "hd" in transform_attrs:
|
||
|
|
transform.hd = transform_attrs.get("hd", "false").lower() == "true"
|
||
|
|
|
||
|
|
#
|
||
|
|
if "position" in transform_attrs:
|
||
|
|
values = [float(x) for x in transform_attrs["position"].split()]
|
||
|
|
transform.position = MultiDimensional(NVector(*values))
|
||
|
|
|
||
|
|
if "scale" in transform_attrs:
|
||
|
|
values = [float(x) for x in transform_attrs["scale"].split()]
|
||
|
|
if len(values) == 2:
|
||
|
|
transform.scale = MultiDimensional(NVector(values[0], values[1]))
|
||
|
|
elif len(values) == 3:
|
||
|
|
transform.scale = MultiDimensional(NVector(values[0], values[1], values[2]))
|
||
|
|
else:
|
||
|
|
transform.scale = MultiDimensional(NVector(values[0], values[0]))
|
||
|
|
|
||
|
|
if "rotation" in transform_attrs:
|
||
|
|
transform.rotation = Value(float(transform_attrs["rotation"]))
|
||
|
|
|
||
|
|
if "opacity" in transform_attrs:
|
||
|
|
transform.opacity = Value(float(transform_attrs["opacity"]))
|
||
|
|
|
||
|
|
if "anchor" in transform_attrs:
|
||
|
|
values = [float(x) for x in transform_attrs["anchor"].split()]
|
||
|
|
transform.anchor = MultiDimensional(NVector(*values))
|
||
|
|
|
||
|
|
if "skew" in transform_attrs:
|
||
|
|
transform.skew = Value(float(transform_attrs["skew"]))
|
||
|
|
|
||
|
|
if "skew_axis" in transform_attrs:
|
||
|
|
transform.skew_axis = Value(float(transform_attrs["skew_axis"]))
|
||
|
|
|
||
|
|
#
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if line.startswith('(position') and 'animated=true' in line:
|
||
|
|
# position
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/position)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
s_str = attrs.get('s', '')
|
||
|
|
if s_str:
|
||
|
|
values = [float(x) for x in s_str.split()]
|
||
|
|
value = NVector(*values) if values else NVector(0, 0, 0)
|
||
|
|
else:
|
||
|
|
value = NVector(0, 0, 0)
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
# - float
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
kf.in_tan = {
|
||
|
|
'x': float(attrs['i_x']),
|
||
|
|
'y': float(attrs['i_y'])
|
||
|
|
}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
kf.out_tan = {
|
||
|
|
'x': float(attrs['o_x']),
|
||
|
|
'y': float(attrs['o_y'])
|
||
|
|
}
|
||
|
|
|
||
|
|
if 'to' in attrs:
|
||
|
|
try:
|
||
|
|
kf.to = json.loads(attrs['to'])
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
if 'ti' in attrs:
|
||
|
|
try:
|
||
|
|
kf.ti = json.loads(attrs['ti'])
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
if keyframes:
|
||
|
|
transform.position = MultiDimensional(keyframes[0].value)
|
||
|
|
transform.position.keyframes = keyframes
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/position)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
|
||
|
|
elif line.startswith('(scale'):
|
||
|
|
if 'separated=true' in line:
|
||
|
|
# Handle separated scale
|
||
|
|
transform.scale = Value(NVector(100, 100))
|
||
|
|
transform.scale.separated = True
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/scale)'):
|
||
|
|
if lines[idx].startswith('(scale_x'):
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.scale.x = Value(value)
|
||
|
|
elif lines[idx].startswith('(scale_y'):
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.scale.y = Value(value)
|
||
|
|
elif lines[idx].startswith('(scale_z'):
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.scale.z = Value(value)
|
||
|
|
idx += 1
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/scale)'):
|
||
|
|
idx += 1
|
||
|
|
elif 'animated=true' in line:
|
||
|
|
# scale
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/scale)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
s_str = attrs.get('s', '')
|
||
|
|
if s_str:
|
||
|
|
values = [float(x) for x in s_str.split()]
|
||
|
|
if len(values) == 2:
|
||
|
|
value = NVector(values[0], values[1])
|
||
|
|
elif len(values) == 3:
|
||
|
|
value = NVector(values[0], values[1], values[2])
|
||
|
|
else:
|
||
|
|
value = NVector(100, 100)
|
||
|
|
else:
|
||
|
|
value = NVector(100, 100)
|
||
|
|
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
# -
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x_str = attrs['i_x']
|
||
|
|
i_y_str = attrs['i_y']
|
||
|
|
|
||
|
|
if ' ' in i_x_str:
|
||
|
|
i_x = [float(v) for v in i_x_str.split()]
|
||
|
|
i_y = [float(v) for v in i_y_str.split()]
|
||
|
|
else:
|
||
|
|
i_x = float(i_x_str)
|
||
|
|
i_y = float(i_y_str)
|
||
|
|
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x_str = attrs['o_x']
|
||
|
|
o_y_str = attrs['o_y']
|
||
|
|
|
||
|
|
if ' ' in o_x_str:
|
||
|
|
o_x = [float(v) for v in o_x_str.split()]
|
||
|
|
o_y = [float(v) for v in o_y_str.split()]
|
||
|
|
else:
|
||
|
|
o_x = float(o_x_str)
|
||
|
|
o_y = float(o_y_str)
|
||
|
|
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
transform.scale = MultiDimensional(keyframes[0].value)
|
||
|
|
transform.scale.keyframes = keyframes
|
||
|
|
else:
|
||
|
|
if not hasattr(transform, 'scale'):
|
||
|
|
transform.scale = MultiDimensional(NVector(100, 100))
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/scale)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(rotation'):
|
||
|
|
if 'separated=true' in line:
|
||
|
|
transform.rotation = Value(0)
|
||
|
|
transform.rotation.separated = True
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/rotation)'):
|
||
|
|
if lines[idx].startswith('(rotation_x'):
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.rotation.x = Value(value)
|
||
|
|
elif lines[idx].startswith('(rotation_y'):
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.rotation.y = Value(value)
|
||
|
|
elif lines[idx].startswith('(rotation_z'):
|
||
|
|
value = extract_number(lines[idx])
|
||
|
|
transform.rotation.z = Value(value)
|
||
|
|
idx += 1
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/rotation)'):
|
||
|
|
idx += 1
|
||
|
|
elif 'animated=true' in line:
|
||
|
|
# rotation
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/rotation)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
value = float(attrs.get('s', 0))
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
#
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x = float(attrs['i_x'])
|
||
|
|
i_y = float(attrs['i_y'])
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x = float(attrs['o_x'])
|
||
|
|
o_y = float(attrs['o_y'])
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
if keyframes:
|
||
|
|
transform.rotation = Value(keyframes[0].value)
|
||
|
|
transform.rotation.keyframes = keyframes
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/rotation)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(opacity') and 'animated=true' in line:
|
||
|
|
# opacity
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/opacity)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(attrs.get('t', 0))
|
||
|
|
value = float(attrs.get('s', 100))
|
||
|
|
kf = Keyframe(time, value)
|
||
|
|
|
||
|
|
#
|
||
|
|
if 'i_x' in attrs and 'i_y' in attrs:
|
||
|
|
i_x = float(attrs['i_x'])
|
||
|
|
i_y = float(attrs['i_y'])
|
||
|
|
kf.in_tan = {'x': i_x, 'y': i_y}
|
||
|
|
|
||
|
|
if 'o_x' in attrs and 'o_y' in attrs:
|
||
|
|
o_x = float(attrs['o_x'])
|
||
|
|
o_y = float(attrs['o_y'])
|
||
|
|
kf.out_tan = {'x': o_x, 'y': o_y}
|
||
|
|
|
||
|
|
# h
|
||
|
|
if 'h' in attrs:
|
||
|
|
kf.h = int(attrs['h'])
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
if keyframes:
|
||
|
|
transform.opacity = Value(keyframes[0].value)
|
||
|
|
transform.opacity.keyframes = keyframes
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/opacity)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(/'): # Any closing tag
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return transform, idx
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
def parse_path_tag(lines, idx):
|
||
|
|
""" - h"""
|
||
|
|
path_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
path = Path()
|
||
|
|
path.name = path_attrs.get("name", "")
|
||
|
|
|
||
|
|
# property_index
|
||
|
|
if "ix" in path_attrs:
|
||
|
|
path.property_index = int(path_attrs.get("ix", 1))
|
||
|
|
if "d" in path_attrs:
|
||
|
|
path.d = int(path_attrs.get("d", 1))
|
||
|
|
|
||
|
|
if "ind" in path_attrs:
|
||
|
|
path.ind = int(path_attrs.get("ind", 1))
|
||
|
|
else:
|
||
|
|
path.ind = 1 # Default value
|
||
|
|
|
||
|
|
if "hd" in path_attrs:
|
||
|
|
path.hd = path_attrs.get("hd", "false").lower() == "true"
|
||
|
|
|
||
|
|
if "mn" in path_attrs:
|
||
|
|
path.mn = path_attrs.get("mn", "")
|
||
|
|
|
||
|
|
# Check if animated
|
||
|
|
is_animated = path_attrs.get("animated", "").lower() == "true"
|
||
|
|
|
||
|
|
if is_animated:
|
||
|
|
# Handle animated path
|
||
|
|
keyframes = []
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/path)'):
|
||
|
|
if lines[idx].startswith('(keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
time = float(kf_attrs.get("t", 0))
|
||
|
|
|
||
|
|
kf = Keyframe(time, None)
|
||
|
|
|
||
|
|
if "i_x" in kf_attrs and "i_y" in kf_attrs:
|
||
|
|
kf.in_tan = {
|
||
|
|
"x": float(kf_attrs["i_x"]),
|
||
|
|
"y": float(kf_attrs["i_y"])
|
||
|
|
}
|
||
|
|
|
||
|
|
if "o_x" in kf_attrs and "o_y" in kf_attrs:
|
||
|
|
kf.out_tan = {
|
||
|
|
"x": float(kf_attrs["o_x"]),
|
||
|
|
"y": float(kf_attrs["o_y"])
|
||
|
|
}
|
||
|
|
|
||
|
|
# h
|
||
|
|
if "h" in kf_attrs:
|
||
|
|
kf.h = int(kf_attrs["h"])
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(bezier'):
|
||
|
|
bezier_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
bezier = Bezier()
|
||
|
|
bezier.closed = bezier_attrs.get("closed", "").lower() == "true"
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/bezier)'):
|
||
|
|
if lines[idx].startswith('(point'):
|
||
|
|
point_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
x = float(point_attrs.get("x", 0))
|
||
|
|
y = float(point_attrs.get("y", 0))
|
||
|
|
in_x = float(point_attrs.get("in_x", 0))
|
||
|
|
in_y = float(point_attrs.get("in_y", 0))
|
||
|
|
out_x = float(point_attrs.get("out_x", 0))
|
||
|
|
out_y = float(point_attrs.get("out_y", 0))
|
||
|
|
|
||
|
|
bezier.add_point(
|
||
|
|
NVector(x, y),
|
||
|
|
NVector(in_x, in_y),
|
||
|
|
NVector(out_x, out_y)
|
||
|
|
)
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
kf.value = bezier
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/bezier)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/keyframe)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if keyframes:
|
||
|
|
path.shape = Value(keyframes[0].value)
|
||
|
|
path.shape.keyframes = keyframes
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/path)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
# Static path
|
||
|
|
bezier = Bezier()
|
||
|
|
bezier.closed = path_attrs.get("closed", "true").lower() == "true"
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
if lines[idx].startswith('(point'):
|
||
|
|
point_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
x = float(point_attrs.get("x", 0))
|
||
|
|
y = float(point_attrs.get("y", 0))
|
||
|
|
in_x = float(point_attrs.get("in_x", 0))
|
||
|
|
in_y = float(point_attrs.get("in_y", 0))
|
||
|
|
out_x = float(point_attrs.get("out_x", 0))
|
||
|
|
out_y = float(point_attrs.get("out_y", 0))
|
||
|
|
|
||
|
|
bezier.add_point(NVector(x, y), NVector(in_x, in_y), NVector(out_x, out_y))
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].strip() == '(/path)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
path.shape = Value(bezier)
|
||
|
|
|
||
|
|
return path, idx
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
def parse_ellipse_tag(lines, idx):
|
||
|
|
""""""
|
||
|
|
ellipse_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
ellipse = Ellipse()
|
||
|
|
ellipse.name = ellipse_attrs.get("name", "")
|
||
|
|
|
||
|
|
# property_index
|
||
|
|
if "ix" in ellipse_attrs:
|
||
|
|
ellipse.property_index = int(ellipse_attrs.get("ix", 1))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if line.startswith('(ellipse_position animated'):
|
||
|
|
# animated position keyframes
|
||
|
|
keyframes = []
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
if lines[idx].startswith('(/ellipse_position)'):
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
elif lines[idx].startswith('(keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
kf_dict = {'t': float(kf_attrs.get('t', 0))}
|
||
|
|
if 's' in kf_attrs:
|
||
|
|
s_vals = [float(v) for v in kf_attrs['s'].split()]
|
||
|
|
kf_dict['s'] = s_vals
|
||
|
|
if 'i_x' in kf_attrs and 'i_y' in kf_attrs:
|
||
|
|
i_x = [float(v) for v in kf_attrs['i_x'].split()] if ' ' in kf_attrs['i_x'] else float(kf_attrs['i_x'])
|
||
|
|
i_y = [float(v) for v in kf_attrs['i_y'].split()] if ' ' in kf_attrs['i_y'] else float(kf_attrs['i_y'])
|
||
|
|
kf_dict['i'] = {'x': i_x, 'y': i_y}
|
||
|
|
if 'o_x' in kf_attrs and 'o_y' in kf_attrs:
|
||
|
|
o_x = [float(v) for v in kf_attrs['o_x'].split()] if ' ' in kf_attrs['o_x'] else float(kf_attrs['o_x'])
|
||
|
|
o_y = [float(v) for v in kf_attrs['o_y'].split()] if ' ' in kf_attrs['o_y'] else float(kf_attrs['o_y'])
|
||
|
|
kf_dict['o'] = {'x': o_x, 'y': o_y}
|
||
|
|
if 'h' in kf_attrs:
|
||
|
|
kf_dict['h'] = int(kf_attrs['h'])
|
||
|
|
if 'to' in kf_attrs:
|
||
|
|
kf_dict['to'] = [float(v) for v in kf_attrs['to'].split()]
|
||
|
|
if 'ti' in kf_attrs:
|
||
|
|
kf_dict['ti'] = [float(v) for v in kf_attrs['ti'].split()]
|
||
|
|
keyframes.append(kf_dict)
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
# keyframes
|
||
|
|
break
|
||
|
|
ellipse.position = MultiDimensional()
|
||
|
|
nvec = NVector()
|
||
|
|
nvec.components = keyframes
|
||
|
|
ellipse.position.value = nvec
|
||
|
|
ellipse.position.animated = True
|
||
|
|
elif line.startswith('(ellipse_position'):
|
||
|
|
components = extract_numbers(line)
|
||
|
|
if components:
|
||
|
|
ellipse.position = MultiDimensional(NVector(*components))
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(ellipse_size animated'):
|
||
|
|
# animated size keyframes
|
||
|
|
keyframes = []
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
if lines[idx].startswith('(/ellipse_size)'):
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
elif lines[idx].startswith('(keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
kf_dict = {'t': float(kf_attrs.get('t', 0))}
|
||
|
|
if 's' in kf_attrs:
|
||
|
|
s_vals = [float(v) for v in kf_attrs['s'].split()]
|
||
|
|
kf_dict['s'] = s_vals
|
||
|
|
if 'i_x' in kf_attrs and 'i_y' in kf_attrs:
|
||
|
|
i_x = [float(v) for v in kf_attrs['i_x'].split()] if ' ' in kf_attrs['i_x'] else float(kf_attrs['i_x'])
|
||
|
|
i_y = [float(v) for v in kf_attrs['i_y'].split()] if ' ' in kf_attrs['i_y'] else float(kf_attrs['i_y'])
|
||
|
|
kf_dict['i'] = {'x': i_x, 'y': i_y}
|
||
|
|
if 'o_x' in kf_attrs and 'o_y' in kf_attrs:
|
||
|
|
o_x = [float(v) for v in kf_attrs['o_x'].split()] if ' ' in kf_attrs['o_x'] else float(kf_attrs['o_x'])
|
||
|
|
o_y = [float(v) for v in kf_attrs['o_y'].split()] if ' ' in kf_attrs['o_y'] else float(kf_attrs['o_y'])
|
||
|
|
kf_dict['o'] = {'x': o_x, 'y': o_y}
|
||
|
|
if 'h' in kf_attrs:
|
||
|
|
kf_dict['h'] = int(kf_attrs['h'])
|
||
|
|
keyframes.append(kf_dict)
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
# keyframes
|
||
|
|
break
|
||
|
|
ellipse.size = MultiDimensional()
|
||
|
|
nvec = NVector()
|
||
|
|
nvec.components = keyframes
|
||
|
|
ellipse.size.value = nvec
|
||
|
|
ellipse.size.animated = True
|
||
|
|
elif line.startswith('(ellipse_size'):
|
||
|
|
components = extract_numbers(line)
|
||
|
|
if components:
|
||
|
|
ellipse.size = MultiDimensional(NVector(*components))
|
||
|
|
idx += 1
|
||
|
|
elif line.strip() == '(/ellipse)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return ellipse, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_gradient_stroke_tag(lines, idx):
|
||
|
|
""" - ml2"""
|
||
|
|
gradient_stroke_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
gradient_stroke = GradientStroke()
|
||
|
|
gradient_stroke.name = gradient_stroke_attrs.get("name", "")
|
||
|
|
|
||
|
|
if "ix" in gradient_stroke_attrs:
|
||
|
|
gradient_stroke.property_index = int(gradient_stroke_attrs.get("ix", 1))
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
original_array = None
|
||
|
|
color_points = 0
|
||
|
|
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if line.startswith('(opacity'):
|
||
|
|
value = extract_number(line)
|
||
|
|
gradient_stroke.opacity = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(width'):
|
||
|
|
value = extract_number(line)
|
||
|
|
gradient_stroke.width = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(line_cap'):
|
||
|
|
value = int(extract_number(line))
|
||
|
|
gradient_stroke.line_cap = LineCap(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(line_join'):
|
||
|
|
value = int(extract_number(line))
|
||
|
|
gradient_stroke.line_join = LineJoin(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(miter_limit'):
|
||
|
|
value = extract_number(line)
|
||
|
|
gradient_stroke.miter_limit = value
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(ml2_ix'):
|
||
|
|
gradient_stroke.ml2_ix = int(extract_number(line))
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(ml2'):
|
||
|
|
value = extract_number(line)
|
||
|
|
gradient_stroke.ml2 = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(start_point'):
|
||
|
|
components = extract_numbers(line)
|
||
|
|
gradient_stroke.start_point = MultiDimensional(NVector(*components))
|
||
|
|
# animated
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(end_point'):
|
||
|
|
components = extract_numbers(line)
|
||
|
|
gradient_stroke.end_point = MultiDimensional(NVector(*components))
|
||
|
|
# animated
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(gradient_type'):
|
||
|
|
value = int(extract_number(line))
|
||
|
|
gradient_stroke.gradient_type = GradientType(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(highlight_length'):
|
||
|
|
value = extract_number(line)
|
||
|
|
gradient_stroke.highlight_length = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(highlight_angle'):
|
||
|
|
value = extract_number(line)
|
||
|
|
gradient_stroke.highlight_angle = Value(value)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(original_colors'):
|
||
|
|
#
|
||
|
|
text = line[line.find(' ') + 1:].strip().rstrip(')')
|
||
|
|
if text:
|
||
|
|
try:
|
||
|
|
original_array = json.loads(text)
|
||
|
|
gradient_stroke._original_color_array = original_array
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(color_points'):
|
||
|
|
color_points = int(extract_number(line))
|
||
|
|
gradient_stroke._color_points = color_points
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(colors'):
|
||
|
|
#
|
||
|
|
text = line[line.find(' ') + 1:].strip().rstrip(')')
|
||
|
|
colors = []
|
||
|
|
|
||
|
|
for color_data in text.split():
|
||
|
|
parts = color_data.split(',')
|
||
|
|
if len(parts) >= 4:
|
||
|
|
pos = float(parts[0])
|
||
|
|
r = float(parts[1])
|
||
|
|
g = float(parts[2])
|
||
|
|
b = float(parts[3])
|
||
|
|
colors.append((pos, Color(r, g, b)))
|
||
|
|
|
||
|
|
gradient_stroke.colors = GradientColors(colors)
|
||
|
|
idx += 1
|
||
|
|
elif line.strip() == '(/gradient_stroke)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
#
|
||
|
|
if original_array and color_points > 0:
|
||
|
|
gradient_stroke._original_color_array = original_array
|
||
|
|
gradient_stroke._color_points = color_points
|
||
|
|
|
||
|
|
#
|
||
|
|
colors = []
|
||
|
|
values_per_point = len(original_array) / color_points
|
||
|
|
if values_per_point >= 4:
|
||
|
|
step = int(values_per_point)
|
||
|
|
for i in range(color_points):
|
||
|
|
base = i * step
|
||
|
|
pos = original_array[base]
|
||
|
|
r = original_array[base + 1]
|
||
|
|
g = original_array[base + 2]
|
||
|
|
b = original_array[base + 3]
|
||
|
|
colors.append((pos, Color(r, g, b)))
|
||
|
|
gradient_stroke.colors = GradientColors(colors)
|
||
|
|
|
||
|
|
return gradient_stroke, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_precomp_layer_tag(lines, idx):
|
||
|
|
"""Parse a precomp layer tag and its contents"""
|
||
|
|
precomp_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
|
||
|
|
precomp_layer = PreCompLayer()
|
||
|
|
precomp_layer.index = int(float(precomp_attrs.get("index", 0)))
|
||
|
|
precomp_layer.name = precomp_attrs.get("name", "PreComp Layer")
|
||
|
|
precomp_layer.in_point = float(precomp_attrs.get("in_point", 0))
|
||
|
|
precomp_layer.out_point = float(precomp_attrs.get("out_point", 60))
|
||
|
|
precomp_layer.start_time = float(precomp_attrs.get("start_time", 0))
|
||
|
|
|
||
|
|
if "w" in precomp_attrs:
|
||
|
|
precomp_layer.w = float(precomp_attrs["w"])
|
||
|
|
if "h" in precomp_attrs:
|
||
|
|
precomp_layer.h = float(precomp_attrs["h"])
|
||
|
|
|
||
|
|
if "tt" in precomp_attrs:
|
||
|
|
precomp_layer.tt = float(precomp_attrs["tt"])
|
||
|
|
if "tp" in precomp_attrs:
|
||
|
|
precomp_layer.tp = float(precomp_attrs["tp"])
|
||
|
|
if "td" in precomp_attrs:
|
||
|
|
precomp_layer.td = float(precomp_attrs["td"])
|
||
|
|
|
||
|
|
if "hasMask" in precomp_attrs:
|
||
|
|
precomp_layer.hasMask = precomp_attrs["hasMask"] == "true"
|
||
|
|
if "hd" in precomp_attrs:
|
||
|
|
precomp_layer.hd = precomp_attrs["hd"] == "true"
|
||
|
|
if "cp" in precomp_attrs:
|
||
|
|
precomp_layer.cp = precomp_attrs["cp"] == "true"
|
||
|
|
|
||
|
|
|
||
|
|
#if "ln" in precomp_attrs:
|
||
|
|
# precomp_layer.ln = float(precomp_attrs["ln"])
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
|
||
|
|
if line.startswith('(reference_id'):
|
||
|
|
reference_id = line[line.find('"')+1:line.rfind('"')]
|
||
|
|
precomp_layer.reference_id = reference_id
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(dimensions'):
|
||
|
|
dim_attrs = parse_tag_attrs(line)
|
||
|
|
precomp_layer.width = int(float(dim_attrs.get("width", 512)))
|
||
|
|
precomp_layer.height = int(float(dim_attrs.get("height", 512)))
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(masksProperties'):
|
||
|
|
# masksProperties
|
||
|
|
precomp_layer.masksProperties = []
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/masksProperties)'):
|
||
|
|
if lines[idx].startswith('(mask '):
|
||
|
|
# mask
|
||
|
|
mask_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask = {}
|
||
|
|
|
||
|
|
#
|
||
|
|
if "inv" in mask_attrs:
|
||
|
|
mask["inv"] = mask_attrs["inv"].lower() == "true"
|
||
|
|
if "mode" in mask_attrs:
|
||
|
|
mask["mode"] = mask_attrs["mode"]
|
||
|
|
if "nm" in mask_attrs:
|
||
|
|
mask["nm"] = mask_attrs["nm"]
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# mask
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask)'):
|
||
|
|
if lines[idx].startswith('(mask_pt '):
|
||
|
|
# pt
|
||
|
|
pt_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask["pt"] = {}
|
||
|
|
if "a" in pt_attrs:
|
||
|
|
mask["pt"]["a"] = int(pt_attrs["a"])
|
||
|
|
if "ix" in pt_attrs:
|
||
|
|
mask["pt"]["ix"] = int(pt_attrs["ix"])
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# pt.k
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(mask_pt_k'):
|
||
|
|
if lines[idx].startswith('(mask_pt_k_array'):
|
||
|
|
# k
|
||
|
|
mask["pt"]["k"] = []
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
#
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_k_array)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe = {}
|
||
|
|
|
||
|
|
if "t" in kf_attrs:
|
||
|
|
keyframe["t"] = float(kf_attrs["t"])
|
||
|
|
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# ...
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_keyframe)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_kf_i'):
|
||
|
|
i_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe["i"] = {
|
||
|
|
"x": float(i_attrs.get("x", 0)),
|
||
|
|
"y": float(i_attrs.get("y", 0))
|
||
|
|
}
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_o'):
|
||
|
|
o_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
keyframe["o"] = {
|
||
|
|
"x": float(o_attrs.get("x", 0)),
|
||
|
|
"y": float(o_attrs.get("y", 0))
|
||
|
|
}
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_s'):
|
||
|
|
keyframe["s"] = []
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_kf_s)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_kf_shape'):
|
||
|
|
shape_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
shape = {}
|
||
|
|
if "c" in shape_attrs:
|
||
|
|
shape["c"] = shape_attrs["c"].lower() == "true"
|
||
|
|
idx += 1
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_kf_shape)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_kf_shape_i'):
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
shape["i"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
shape["i"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_shape_o'):
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
shape["o"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
shape["o"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
elif lines[idx].startswith('(mask_pt_kf_shape_v'):
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
shape["v"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
shape["v"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
if lines[idx].startswith('(/mask_pt_kf_shape)'):
|
||
|
|
idx += 1
|
||
|
|
keyframe["s"].append(shape)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
if lines[idx].startswith('(/mask_pt_kf_s)'):
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask_pt_keyframe)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
mask["pt"]["k"].append(keyframe)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask_pt_k_array)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
else:
|
||
|
|
# kshape
|
||
|
|
mask["pt"]["k"] = {}
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
# shape
|
||
|
|
while idx < len(lines) and not lines[idx].startswith('(/mask_pt_k)'):
|
||
|
|
if lines[idx].startswith('(mask_pt_k_c'):
|
||
|
|
# closed
|
||
|
|
parts = lines[idx].split()
|
||
|
|
if len(parts) > 1:
|
||
|
|
mask["pt"]["k"]["c"] = parts[1].rstrip(')').lower() == "true"
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_pt_k_i'):
|
||
|
|
# i
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
mask["pt"]["k"]["i"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
mask["pt"]["k"]["i"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_pt_k_o'):
|
||
|
|
# o
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
mask["pt"]["k"]["o"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
mask["pt"]["k"]["o"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_pt_k_v'):
|
||
|
|
# v
|
||
|
|
values = extract_numbers(lines[idx])
|
||
|
|
mask["pt"]["k"]["v"] = []
|
||
|
|
for i in range(0, len(values), 2):
|
||
|
|
if i + 1 < len(values):
|
||
|
|
mask["pt"]["k"]["v"].append([values[i], values[i+1]])
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask_pt_k)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if idx < len(lines) and lines[idx].startswith('(/mask_pt)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_o '):
|
||
|
|
# opacity
|
||
|
|
o_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask["o"] = {}
|
||
|
|
if "a" in o_attrs:
|
||
|
|
mask["o"]["a"] = int(o_attrs["a"])
|
||
|
|
if "k" in o_attrs:
|
||
|
|
mask["o"]["k"] = float(o_attrs["k"])
|
||
|
|
if "ix" in o_attrs:
|
||
|
|
mask["o"]["ix"] = int(o_attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(mask_x '):
|
||
|
|
# dilate
|
||
|
|
x_attrs = parse_tag_attrs(lines[idx])
|
||
|
|
mask["x"] = {}
|
||
|
|
if "a" in x_attrs:
|
||
|
|
mask["x"]["a"] = int(x_attrs["a"])
|
||
|
|
if "k" in x_attrs:
|
||
|
|
mask["x"]["k"] = float(x_attrs["k"])
|
||
|
|
if "ix" in x_attrs:
|
||
|
|
mask["x"]["ix"] = int(x_attrs["ix"])
|
||
|
|
idx += 1
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/mask)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
precomp_layer.masksProperties.append(mask)
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
if lines[idx].startswith('(/masksProperties)'):
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
elif lines[idx].startswith('(effects'):
|
||
|
|
# effects
|
||
|
|
effects, new_idx = parse_effects_tag(lines, idx)
|
||
|
|
precomp_layer.ef = effects
|
||
|
|
idx = new_idx
|
||
|
|
|
||
|
|
elif line.startswith('(ct'):
|
||
|
|
# ct
|
||
|
|
ct_value = extract_number(line)
|
||
|
|
precomp_layer.ct = ct_value
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
elif line.startswith('(tm '):
|
||
|
|
# tm
|
||
|
|
tm_attrs = parse_tag_attrs(line)
|
||
|
|
tm_data = {}
|
||
|
|
if 'a' in tm_attrs:
|
||
|
|
tm_data['a'] = int(tm_attrs['a'])
|
||
|
|
if 'ix' in tm_attrs:
|
||
|
|
tm_data['ix'] = int(tm_attrs['ix'])
|
||
|
|
|
||
|
|
# keyframes
|
||
|
|
idx += 1
|
||
|
|
keyframes = []
|
||
|
|
while idx < len(lines):
|
||
|
|
line = lines[idx]
|
||
|
|
if line.startswith('(keyframe'):
|
||
|
|
kf_attrs = parse_tag_attrs(line)
|
||
|
|
kf = {}
|
||
|
|
if 't' in kf_attrs:
|
||
|
|
kf['t'] = float(kf_attrs['t'])
|
||
|
|
if 's' in kf_attrs:
|
||
|
|
kf['s'] = [float(kf_attrs['s'])]
|
||
|
|
if 'h' in kf_attrs:
|
||
|
|
kf['h'] = int(kf_attrs['h'])
|
||
|
|
|
||
|
|
# -
|
||
|
|
if 'i_x' in kf_attrs or 'i_y' in kf_attrs:
|
||
|
|
kf['i'] = {}
|
||
|
|
if 'i_x' in kf_attrs:
|
||
|
|
kf['i']['x'] = float(kf_attrs['i_x']) #
|
||
|
|
if 'i_y' in kf_attrs:
|
||
|
|
kf['i']['y'] = float(kf_attrs['i_y']) #
|
||
|
|
|
||
|
|
if 'o_x' in kf_attrs or 'o_y' in kf_attrs:
|
||
|
|
kf['o'] = {}
|
||
|
|
if 'o_x' in kf_attrs:
|
||
|
|
kf['o']['x'] = float(kf_attrs['o_x']) #
|
||
|
|
if 'o_y' in kf_attrs:
|
||
|
|
kf['o']['y'] = float(kf_attrs['o_y']) #
|
||
|
|
|
||
|
|
keyframes.append(kf)
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(value'):
|
||
|
|
#
|
||
|
|
val = extract_number(line)
|
||
|
|
tm_data['k'] = val
|
||
|
|
idx += 1
|
||
|
|
elif line.strip() == '(/tm)':
|
||
|
|
if keyframes:
|
||
|
|
tm_data['k'] = keyframes
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
precomp_layer.tm = tm_data
|
||
|
|
|
||
|
|
elif line.startswith('(parent'):
|
||
|
|
parent_index = int(extract_number(line))
|
||
|
|
precomp_layer.parent_index = parent_index
|
||
|
|
idx += 1
|
||
|
|
elif line.startswith('(transform'):
|
||
|
|
transform, new_idx = parse_transform_tag(lines, idx)
|
||
|
|
precomp_layer.transform = transform
|
||
|
|
idx = new_idx
|
||
|
|
elif line.strip() == '(/precomp_layer)':
|
||
|
|
idx += 1
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
idx += 1
|
||
|
|
|
||
|
|
return precomp_layer, idx
|
||
|
|
|
||
|
|
|
||
|
|
def parse_tag_attrs(line):
|
||
|
|
"""Parse tag attributes, including complex values like JSON arrays"""
|
||
|
|
# Extract tag content
|
||
|
|
content = line[1:-1] # Remove parentheses
|
||
|
|
|
||
|
|
# Find first space to separate tag name and attributes
|
||
|
|
first_space = content.find(' ')
|
||
|
|
if first_space == -1:
|
||
|
|
return {}
|
||
|
|
|
||
|
|
tag_name = content[:first_space]
|
||
|
|
attrs_part = content[first_space+1:]
|
||
|
|
|
||
|
|
# Handle quoted tag name
|
||
|
|
if tag_name.startswith('"') and tag_name.endswith('"'):
|
||
|
|
tag_name = tag_name[1:-1]
|
||
|
|
|
||
|
|
# Parse attributes
|
||
|
|
attrs = {}
|
||
|
|
|
||
|
|
# Process the attributes string with proper handling for complex values
|
||
|
|
i = 0
|
||
|
|
while i < len(attrs_part):
|
||
|
|
# Skip whitespace
|
||
|
|
while i < len(attrs_part) and attrs_part[i].isspace():
|
||
|
|
i += 1
|
||
|
|
|
||
|
|
if i >= len(attrs_part):
|
||
|
|
break
|
||
|
|
|
||
|
|
# Find attribute name
|
||
|
|
start = i
|
||
|
|
while i < len(attrs_part) and attrs_part[i] not in "= \t\n\r":
|
||
|
|
i += 1
|
||
|
|
|
||
|
|
if i >= len(attrs_part):
|
||
|
|
break
|
||
|
|
|
||
|
|
attr_name = attrs_part[start:i]
|
||
|
|
|
||
|
|
# Skip to the value
|
||
|
|
while i < len(attrs_part) and (attrs_part[i].isspace() or attrs_part[i] == '='):
|
||
|
|
i += 1
|
||
|
|
|
||
|
|
if i >= len(attrs_part):
|
||
|
|
break
|
||
|
|
|
||
|
|
# Parse attribute value based on its format
|
||
|
|
if attrs_part[i] == '"':
|
||
|
|
# Quoted string
|
||
|
|
i += 1 # Skip opening quote
|
||
|
|
start = i
|
||
|
|
while i < len(attrs_part) and attrs_part[i] != '"':
|
||
|
|
i += 1
|
||
|
|
|
||
|
|
attr_value = attrs_part[start:i]
|
||
|
|
i += 1 # Skip closing quote
|
||
|
|
elif attrs_part[i] == '[':
|
||
|
|
# Handle JSON array or list
|
||
|
|
bracket_count = 1
|
||
|
|
start = i
|
||
|
|
i += 1
|
||
|
|
|
||
|
|
while i < len(attrs_part) and bracket_count > 0:
|
||
|
|
if attrs_part[i] == '[':
|
||
|
|
bracket_count += 1
|
||
|
|
elif attrs_part[i] == ']':
|
||
|
|
bracket_count -= 1
|
||
|
|
|
||
|
|
i += 1
|
||
|
|
|
||
|
|
attr_value = attrs_part[start:i]
|
||
|
|
else:
|
||
|
|
# Regular value
|
||
|
|
start = i
|
||
|
|
while i < len(attrs_part) and not attrs_part[i].isspace():
|
||
|
|
i += 1
|
||
|
|
|
||
|
|
attr_value = attrs_part[start:i]
|
||
|
|
|
||
|
|
attrs[attr_name] = attr_value
|
||
|
|
|
||
|
|
return attrs
|
||
|
|
|
||
|
|
|
||
|
|
def extract_numbers(line):
|
||
|
|
""""""
|
||
|
|
#
|
||
|
|
start_idx = line.find(' ')
|
||
|
|
if start_idx == -1:
|
||
|
|
return []
|
||
|
|
content = line[start_idx:].strip()
|
||
|
|
|
||
|
|
#
|
||
|
|
return [float(x) for x in re.findall(r'-?\d+\.?\d*', content)]
|
||
|
|
|
||
|
|
def extract_number(line):
|
||
|
|
""""""
|
||
|
|
numbers = extract_numbers(line)
|
||
|
|
return numbers[0] if numbers else 0
|
||
|
|
|
||
|
|
|
||
|
|
# Multi-threaded folder processing
|
||
|
|
solid_color_layer_to_json = solid_layer_to_json
|
||
|
|
|