mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-26 13:52:15 +01:00
Merge branch 'develop' into feature/PYPE-25-nuke-backdrop-publish
This commit is contained in:
commit
2e82c80e8a
10 changed files with 599 additions and 12 deletions
347
pype/ftrack/actions/action_seed.py
Normal file
347
pype/ftrack/actions/action_seed.py
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
import os
|
||||
from operator import itemgetter
|
||||
from pype.ftrack import BaseAction
|
||||
|
||||
|
||||
class SeedDebugProject(BaseAction):
|
||||
'''Edit meta data action.'''
|
||||
|
||||
#: Action identifier.
|
||||
identifier = "seed.debug.project"
|
||||
#: Action label.
|
||||
label = "SeedDebugProject"
|
||||
#: Action description.
|
||||
description = "Description"
|
||||
#: priority
|
||||
priority = 100
|
||||
#: roles that are allowed to register this action
|
||||
role_list = ["Pypeclub"]
|
||||
icon = "{}/ftrack/action_icons/SeedProject.svg".format(
|
||||
os.environ.get("PYPE_STATICS_SERVER", "")
|
||||
)
|
||||
|
||||
# Asset names which will be created in `Assets` entity
|
||||
assets = [
|
||||
"Addax", "Alpaca", "Ant", "Antelope", "Aye", "Badger", "Bear", "Bee",
|
||||
"Beetle", "Bluebird", "Bongo", "Bontebok", "Butterflie", "Caiman",
|
||||
"Capuchin", "Capybara", "Cat", "Caterpillar", "Coyote", "Crocodile",
|
||||
"Cuckoo", "Deer", "Dragonfly", "Duck", "Eagle", "Egret", "Elephant",
|
||||
"Falcon", "Fossa", "Fox", "Gazelle", "Gecko", "Gerbil",
|
||||
"GiantArmadillo", "Gibbon", "Giraffe", "Goose", "Gorilla",
|
||||
"Grasshoper", "Hare", "Hawk", "Hedgehog", "Heron", "Hog",
|
||||
"Hummingbird", "Hyena", "Chameleon", "Cheetah", "Iguana", "Jackal",
|
||||
"Jaguar", "Kingfisher", "Kinglet", "Kite", "Komodo", "Lemur",
|
||||
"Leopard", "Lion", "Lizard", "Macaw", "Malachite", "Mandrill",
|
||||
"Mantis", "Marmoset", "Meadowlark", "Meerkat", "Mockingbird",
|
||||
"Mongoose", "Monkey", "Nyal", "Ocelot", "Okapi", "Oribi", "Oriole",
|
||||
"Otter", "Owl", "Panda", "Parrot", "Pelican", "Pig", "Porcupine",
|
||||
"Reedbuck", "Rhinocero", "Sandpiper", "Servil", "Skink", "Sloth",
|
||||
"Snake", "Spider", "Squirrel", "Sunbird", "Swallow", "Swift", "Tiger",
|
||||
"Sylph", "Tanager", "Vulture", "Warthog", "Waterbuck", "Woodpecker",
|
||||
"Zebra"
|
||||
]
|
||||
|
||||
# Tasks which will be created for Assets
|
||||
asset_tasks = [
|
||||
"Modeling", "Lookdev", "Rigging"
|
||||
]
|
||||
# Tasks which will be created for Shots
|
||||
shot_tasks = [
|
||||
"Animation", "Lighting", "Compositing", "FX"
|
||||
]
|
||||
|
||||
# Define how much sequences will be created
|
||||
default_seq_count = 5
|
||||
# Define how much shots will be created for each sequence
|
||||
default_shots_count = 10
|
||||
|
||||
existing_projects = None
|
||||
new_project_item = "< New Project >"
|
||||
current_project_item = "< Current Project >"
|
||||
|
||||
def discover(self, session, entities, event):
|
||||
''' Validation '''
|
||||
return True
|
||||
|
||||
def interface(self, session, entities, event):
|
||||
if event["data"].get("values", {}):
|
||||
return
|
||||
|
||||
title = "Select Project where you want to create seed data"
|
||||
|
||||
items = []
|
||||
item_splitter = {"type": "label", "value": "---"}
|
||||
|
||||
description_label = {
|
||||
"type": "label",
|
||||
"value": (
|
||||
"WARNING: Action does NOT check if entities already exist !!!"
|
||||
)
|
||||
}
|
||||
items.append(description_label)
|
||||
|
||||
all_projects = session.query("select full_name from Project").all()
|
||||
self.existing_projects = [proj["full_name"] for proj in all_projects]
|
||||
projects_items = [
|
||||
{"label": proj, "value": proj} for proj in self.existing_projects
|
||||
]
|
||||
|
||||
data_items = []
|
||||
|
||||
data_items.append({
|
||||
"label": self.new_project_item,
|
||||
"value": self.new_project_item
|
||||
})
|
||||
|
||||
data_items.append({
|
||||
"label": self.current_project_item,
|
||||
"value": self.current_project_item
|
||||
})
|
||||
|
||||
data_items.extend(sorted(
|
||||
projects_items,
|
||||
key=itemgetter("label"),
|
||||
reverse=False
|
||||
))
|
||||
projects_item = {
|
||||
"label": "Choose Project",
|
||||
"type": "enumerator",
|
||||
"name": "project_name",
|
||||
"data": data_items,
|
||||
"value": self.current_project_item
|
||||
}
|
||||
items.append(projects_item)
|
||||
items.append(item_splitter)
|
||||
|
||||
items.append({
|
||||
"label": "Number of assets",
|
||||
"type": "number",
|
||||
"name": "asset_count",
|
||||
"value": len(self.assets)
|
||||
})
|
||||
items.append({
|
||||
"label": "Number of sequences",
|
||||
"type": "number",
|
||||
"name": "seq_count",
|
||||
"value": self.default_seq_count
|
||||
})
|
||||
items.append({
|
||||
"label": "Number of shots",
|
||||
"type": "number",
|
||||
"name": "shots_count",
|
||||
"value": self.default_shots_count
|
||||
})
|
||||
items.append(item_splitter)
|
||||
|
||||
note_label = {
|
||||
"type": "label",
|
||||
"value": (
|
||||
"<p><i>NOTE: Enter project name and choose schema if you "
|
||||
"chose `\"< New Project >\"`(code is optional)</i><p>"
|
||||
)
|
||||
}
|
||||
items.append(note_label)
|
||||
items.append({
|
||||
"label": "Project name",
|
||||
"name": "new_project_name",
|
||||
"type": "text",
|
||||
"value": ""
|
||||
})
|
||||
|
||||
project_schemas = [
|
||||
sch["name"] for sch in self.session.query("ProjectSchema").all()
|
||||
]
|
||||
schemas_item = {
|
||||
"label": "Choose Schema",
|
||||
"type": "enumerator",
|
||||
"name": "new_schema_name",
|
||||
"data": [
|
||||
{"label": sch, "value": sch} for sch in project_schemas
|
||||
],
|
||||
"value": project_schemas[0]
|
||||
}
|
||||
items.append(schemas_item)
|
||||
|
||||
items.append({
|
||||
"label": "*Project code",
|
||||
"name": "new_project_code",
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"empty_text": "Optional..."
|
||||
})
|
||||
|
||||
return {
|
||||
"items": items,
|
||||
"title": title
|
||||
}
|
||||
|
||||
def launch(self, session, in_entities, event):
|
||||
if "values" not in event["data"]:
|
||||
return
|
||||
|
||||
# THIS IS THE PROJECT PART
|
||||
values = event["data"]["values"]
|
||||
selected_project = values["project_name"]
|
||||
if selected_project == self.new_project_item:
|
||||
project_name = values["new_project_name"]
|
||||
if project_name in self.existing_projects:
|
||||
msg = "Project \"{}\" already exist".format(project_name)
|
||||
self.log.error(msg)
|
||||
return {"success": False, "message": msg}
|
||||
|
||||
project_code = values["new_project_code"]
|
||||
project_schema_name = values["new_schema_name"]
|
||||
if not project_code:
|
||||
project_code = project_name
|
||||
project_code = project_code.lower().replace(" ", "_").strip()
|
||||
_project = session.query(
|
||||
"Project where name is \"{}\"".format(project_code)
|
||||
).first()
|
||||
if _project:
|
||||
msg = "Project with code \"{}\" already exist".format(
|
||||
project_code
|
||||
)
|
||||
self.log.error(msg)
|
||||
return {"success": False, "message": msg}
|
||||
|
||||
project_schema = session.query(
|
||||
"ProjectSchema where name is \"{}\"".format(
|
||||
project_schema_name
|
||||
)
|
||||
).one()
|
||||
# Create the project with the chosen schema.
|
||||
self.log.debug((
|
||||
"*** Creating Project: name <{}>, code <{}>, schema <{}>"
|
||||
).format(project_name, project_code, project_schema_name))
|
||||
project = session.create("Project", {
|
||||
"name": project_code,
|
||||
"full_name": project_name,
|
||||
"project_schema": project_schema
|
||||
})
|
||||
session.commit()
|
||||
|
||||
elif selected_project == self.current_project_item:
|
||||
entity = in_entities[0]
|
||||
if entity.entity_type.lower() == "project":
|
||||
project = entity
|
||||
else:
|
||||
if "project" in entity:
|
||||
project = entity["project"]
|
||||
else:
|
||||
project = entity["parent"]["project"]
|
||||
project_schema = project["project_schema"]
|
||||
self.log.debug((
|
||||
"*** Using Project: name <{}>, code <{}>, schema <{}>"
|
||||
).format(
|
||||
project["full_name"], project["name"], project_schema["name"]
|
||||
))
|
||||
else:
|
||||
project = session.query("Project where full_name is \"{}\"".format(
|
||||
selected_project
|
||||
)).one()
|
||||
project_schema = project["project_schema"]
|
||||
self.log.debug((
|
||||
"*** Using Project: name <{}>, code <{}>, schema <{}>"
|
||||
).format(
|
||||
project["full_name"], project["name"], project_schema["name"]
|
||||
))
|
||||
|
||||
# THIS IS THE MAGIC PART
|
||||
task_types = {}
|
||||
for _type in project_schema["_task_type_schema"]["types"]:
|
||||
if _type["name"] not in task_types:
|
||||
task_types[_type["name"]] = _type
|
||||
self.task_types = task_types
|
||||
|
||||
asset_count = values.get("asset_count") or len(self.assets)
|
||||
seq_count = values.get("seq_count") or self.default_seq_count
|
||||
shots_count = values.get("shots_count") or self.default_shots_count
|
||||
|
||||
self.create_assets(project, asset_count)
|
||||
self.create_shots(project, seq_count, shots_count)
|
||||
|
||||
return True
|
||||
|
||||
def create_assets(self, project, asset_count):
|
||||
self.log.debug("*** Creating assets:")
|
||||
|
||||
main_entity = self.session.create("Folder", {
|
||||
"name": "Assets",
|
||||
"parent": project
|
||||
})
|
||||
self.log.debug("- Assets")
|
||||
available_assets = len(self.assets)
|
||||
repetitive_times = (
|
||||
int(asset_count / available_assets) +
|
||||
(asset_count % available_assets > 0)
|
||||
)
|
||||
created_assets = 0
|
||||
for _asset_name in self.assets:
|
||||
if created_assets >= asset_count:
|
||||
break
|
||||
for asset_num in range(1, repetitive_times + 1):
|
||||
if created_assets >= asset_count:
|
||||
break
|
||||
asset_name = "%s_%02d" % (_asset_name, asset_num)
|
||||
asset = self.session.create("AssetBuild", {
|
||||
"name": asset_name,
|
||||
"parent": main_entity
|
||||
})
|
||||
created_assets += 1
|
||||
self.log.debug("- Assets/{}".format(asset_name))
|
||||
|
||||
for task_name in self.asset_tasks:
|
||||
self.session.create("Task", {
|
||||
"name": task_name,
|
||||
"parent": asset,
|
||||
"type": self.task_types[task_name]
|
||||
})
|
||||
self.log.debug("- Assets/{}/{}".format(
|
||||
asset_name, task_name
|
||||
))
|
||||
|
||||
self.log.debug("*** Commiting Assets")
|
||||
self.session.commit()
|
||||
|
||||
def create_shots(self, project, seq_count, shots_count):
|
||||
self.log.debug("*** Creating shots:")
|
||||
main_entity = self.session.create("Folder", {
|
||||
"name": "Shots",
|
||||
"parent": project
|
||||
})
|
||||
self.log.debug("- Shots")
|
||||
|
||||
for seq_num in range(1, seq_count+1):
|
||||
seq_name = "sq%03d" % seq_num
|
||||
seq = self.session.create("Sequence", {
|
||||
"name": seq_name,
|
||||
"parent": main_entity
|
||||
})
|
||||
self.log.debug("- Shots/{}".format(seq_name))
|
||||
|
||||
for shot_num in range(1, shots_count+1):
|
||||
shot_name = "%ssh%04d" % (seq_name, (shot_num*10))
|
||||
shot = self.session.create("Shot", {
|
||||
"name": shot_name,
|
||||
"parent": seq
|
||||
})
|
||||
self.log.debug("- Shots/{}/{}".format(seq_name, shot_name))
|
||||
|
||||
for task_name in self.shot_tasks:
|
||||
self.session.create("Task", {
|
||||
"name": task_name,
|
||||
"parent": shot,
|
||||
"type": self.task_types[task_name]
|
||||
})
|
||||
self.log.debug("- Shots/{}/{}/{}".format(
|
||||
seq_name, shot_name, task_name
|
||||
))
|
||||
|
||||
self.log.debug("*** Commiting Shots")
|
||||
self.session.commit()
|
||||
|
||||
|
||||
def register(session, plugins_presets={}):
|
||||
'''Register plugin. Called when used as an plugin.'''
|
||||
|
||||
SeedDebugProject(session, plugins_presets).register()
|
||||
|
|
@ -49,7 +49,10 @@ class ThumbnailEvents(BaseEvent):
|
|||
|
||||
self.log.info(msg)
|
||||
|
||||
session.commit()
|
||||
try:
|
||||
session.commit()
|
||||
except Exception:
|
||||
session.rollback()
|
||||
|
||||
|
||||
def register(session, plugins_presets):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ class VersionToTaskStatus(BaseEvent):
|
|||
|
||||
def launch(self, session, event):
|
||||
'''Propagates status from version to task when changed'''
|
||||
session.commit()
|
||||
|
||||
# start of event procedure ----------------------------------
|
||||
for entity in event['data'].get('entities', []):
|
||||
|
|
@ -65,6 +64,7 @@ class VersionToTaskStatus(BaseEvent):
|
|||
session.rollback()
|
||||
self.log.warning('!!! [ {} ] status couldnt be set:\
|
||||
[ {} ]'.format(path, e))
|
||||
session.rollback()
|
||||
else:
|
||||
self.log.info('>>> [ {} ] updated to [ {} ]'.format(
|
||||
path, task_status['name']))
|
||||
|
|
|
|||
|
|
@ -30,8 +30,7 @@ def main(args):
|
|||
server.run_server(session)
|
||||
|
||||
except Exception as exc:
|
||||
import traceback
|
||||
traceback.print_tb(exc.__traceback__)
|
||||
log.error("Event server crashed. See traceback below", exc_info=True)
|
||||
|
||||
finally:
|
||||
log.debug("First closing socket")
|
||||
|
|
|
|||
|
|
@ -345,25 +345,44 @@ class AppAction(BaseHandler):
|
|||
statuses = presets['status_update']
|
||||
|
||||
actual_status = entity['status']['name'].lower()
|
||||
next_status_name = None
|
||||
for key, value in statuses.items():
|
||||
if actual_status in value or '_any_' in value:
|
||||
if key != '_ignore_':
|
||||
next_status_name = key
|
||||
already_tested = []
|
||||
ent_path = "/".join(
|
||||
[ent["name"] for ent in entity['link']]
|
||||
)
|
||||
while True:
|
||||
next_status_name = None
|
||||
for key, value in statuses.items():
|
||||
if key in already_tested:
|
||||
continue
|
||||
if actual_status in value or '_any_' in value:
|
||||
if key != '_ignore_':
|
||||
next_status_name = key
|
||||
already_tested.append(key)
|
||||
break
|
||||
already_tested.append(key)
|
||||
|
||||
if next_status_name is None:
|
||||
break
|
||||
|
||||
if next_status_name is not None:
|
||||
try:
|
||||
query = 'Status where name is "{}"'.format(
|
||||
next_status_name
|
||||
)
|
||||
status = session.query(query).one()
|
||||
|
||||
entity['status'] = status
|
||||
session.commit()
|
||||
self.log.debug("Changing status to \"{}\" <{}>".format(
|
||||
next_status_name, ent_path
|
||||
))
|
||||
break
|
||||
|
||||
except Exception:
|
||||
session.rollback()
|
||||
msg = (
|
||||
'Status "{}" in presets wasn\'t found on Ftrack'
|
||||
).format(next_status_name)
|
||||
'Status "{}" in presets wasn\'t found'
|
||||
' on Ftrack entity type "{}"'
|
||||
).format(next_status_name, entity.entity_type)
|
||||
self.log.warning(msg)
|
||||
|
||||
# Set origin avalon environments
|
||||
|
|
|
|||
|
|
@ -603,3 +603,24 @@ class BaseHandler(object):
|
|||
self.log.debug(
|
||||
"Action \"{}\" Triggered successfully".format(action_name)
|
||||
)
|
||||
|
||||
def trigger_event(
|
||||
self, topic, event_data={}, session=None, source=None,
|
||||
event=None, on_error="ignore"
|
||||
):
|
||||
if session is None:
|
||||
session = self.session
|
||||
|
||||
if not source and event:
|
||||
source = event.get("source")
|
||||
# Create and trigger event
|
||||
event = fa_session.ftrack_api.event.base.Event(
|
||||
topic=topic,
|
||||
data=event_data,
|
||||
source=source
|
||||
)
|
||||
session.event_hub.publish(event, on_error=on_error)
|
||||
|
||||
self.log.debug((
|
||||
"Publishing event: {}"
|
||||
).format(str(event.__dict__)))
|
||||
|
|
|
|||
|
|
@ -18,3 +18,6 @@ class CreateLook(avalon.maya.Creator):
|
|||
|
||||
# Whether to automatically convert the textures to .tx upon publish.
|
||||
self.data["maketx"] = True
|
||||
|
||||
# Enable users to force a copy.
|
||||
self.data["forceCopy"] = False
|
||||
|
|
|
|||
|
|
@ -206,6 +206,11 @@ class ExtractLook(pype.api.Extractor):
|
|||
destination = self.resource_destination(
|
||||
instance, source, do_maketx
|
||||
)
|
||||
|
||||
# Force copy is specified.
|
||||
if instance.data.get("forceCopy", False):
|
||||
mode = COPY
|
||||
|
||||
if mode == COPY:
|
||||
transfers.append((source, destination))
|
||||
elif mode == HARDLINK:
|
||||
|
|
|
|||
101
pype/plugins/nuke/publish/validate_write_knobs.py
Normal file
101
pype/plugins/nuke/publish/validate_write_knobs.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import nuke
|
||||
|
||||
import pyblish.api
|
||||
import pype.api
|
||||
|
||||
|
||||
class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin):
|
||||
"""Ensure knobs are consistent.
|
||||
|
||||
Knobs to validate and their values comes from the
|
||||
"nuke/knobs.json" preset, which needs this structure:
|
||||
{
|
||||
"family": {
|
||||
"knob_name": knob_value
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = "Knobs"
|
||||
hosts = ["nuke"]
|
||||
actions = [pype.api.RepairContextAction]
|
||||
optional = True
|
||||
|
||||
def process(self, context):
|
||||
# Check for preset existence.
|
||||
if not context.data["presets"]["nuke"].get("knobs"):
|
||||
return
|
||||
|
||||
invalid = self.get_invalid(context, compute=True)
|
||||
if invalid:
|
||||
raise RuntimeError(
|
||||
"Found knobs with invalid values: {}".format(invalid)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, context, compute=False):
|
||||
invalid = context.data.get("invalid_knobs", [])
|
||||
if compute:
|
||||
invalid = cls.get_invalid_knobs(context)
|
||||
|
||||
return invalid
|
||||
|
||||
@classmethod
|
||||
def get_invalid_knobs(cls, context):
|
||||
presets = context.data["presets"]["nuke"]["knobs"]
|
||||
invalid_knobs = []
|
||||
for instance in context:
|
||||
# Filter publisable instances.
|
||||
if not instance.data["publish"]:
|
||||
continue
|
||||
|
||||
# Filter families.
|
||||
families = [instance.data["family"]]
|
||||
families += instance.data.get("families", [])
|
||||
families = list(set(families) & set(presets.keys()))
|
||||
if not families:
|
||||
continue
|
||||
|
||||
# Get all knobs to validate.
|
||||
knobs = {}
|
||||
for family in families:
|
||||
for preset in presets[family]:
|
||||
knobs.update({preset: presets[family][preset]})
|
||||
|
||||
# Get invalid knobs.
|
||||
nodes = []
|
||||
|
||||
for node in nuke.allNodes():
|
||||
nodes.append(node)
|
||||
if node.Class() == "Group":
|
||||
node.begin()
|
||||
for i in nuke.allNodes():
|
||||
nodes.append(i)
|
||||
node.end()
|
||||
|
||||
for node in nodes:
|
||||
for knob in node.knobs():
|
||||
if knob in knobs.keys():
|
||||
expected = knobs[knob]
|
||||
if node[knob].value() != expected:
|
||||
invalid_knobs.append(
|
||||
{
|
||||
"knob": node[knob],
|
||||
"expected": expected,
|
||||
"current": node[knob].value()
|
||||
}
|
||||
)
|
||||
|
||||
context.data["invalid_knobs"] = invalid_knobs
|
||||
return invalid_knobs
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
invalid = cls.get_invalid(instance)
|
||||
for data in invalid:
|
||||
if isinstance(data["expected"], unicode):
|
||||
data["knob"].setValue(str(data["expected"]))
|
||||
continue
|
||||
|
||||
data["knob"].setValue(data["expected"])
|
||||
89
res/ftrack/action_icons/SeedProject.svg
Normal file
89
res/ftrack/action_icons/SeedProject.svg
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient gradientUnits="userSpaceOnUse" id="SVGID_1_" x1="-0.0000027" x2="512" y1="256" y2="256">
|
||||
<stop offset="0" style="stop-color:#85bf35"/>
|
||||
<stop offset="1" style="stop-color:#237a35"/>
|
||||
</linearGradient>
|
||||
<circle cx="256" cy="256" fill="url(#SVGID_1_)" r="256"/>
|
||||
<linearGradient gradientUnits="userSpaceOnUse" id="SVGID_2_" x1="42.6666641" x2="469.3333435" y1="256.0005188" y2="256.0005188">
|
||||
<stop offset="0" style="stop-color:#237a35"/>
|
||||
<stop offset="1" style="stop-color:#85bf35"/>
|
||||
</linearGradient>
|
||||
<path d="M256,469.3338623c-117.6314697,0-213.3333435-95.7023926-213.3333435-213.3333435 c0-117.6314545,95.7018661-213.333313,213.3333435-213.333313c117.6357422,0,213.3333435,95.7018661,213.3333435,213.333313 C469.3333435,373.6314697,373.6357422,469.3338623,256,469.3338623z" fill="url(#SVGID_2_)"/>
|
||||
</g>
|
||||
<g transform="translate(90 100) scale(1.6)">
|
||||
<path id="Selection"
|
||||
fill="#ffffff"
|
||||
d="M 79.00,50.00
|
||||
C 77.76,47.29 76.99,45.47 74.79,43.33
|
||||
66.81,35.57 52.60,37.35 43.00,40.69
|
||||
37.32,42.68 32.22,45.49 26.00,44.81
|
||||
20.95,44.26 15.90,41.85 12.00,38.67
|
||||
9.90,36.96 6.61,33.34 4.06,32.74
|
||||
-0.28,31.73 -0.07,37.28 0.04,40.00
|
||||
0.22,44.64 1.91,50.68 3.62,55.00
|
||||
10.75,73.08 24.47,80.94 43.00,83.15
|
||||
49.54,83.94 54.56,83.88 61.00,81.96
|
||||
66.94,80.18 71.59,77.09 75.20,72.00
|
||||
76.90,69.60 78.35,65.48 80.41,63.98
|
||||
81.78,63.00 86.94,61.72 89.00,60.94
|
||||
94.17,58.97 97.92,56.65 102.00,52.91
|
||||
110.15,45.43 115.89,35.16 128.00,36.09
|
||||
131.34,36.35 135.18,37.38 137.26,40.22
|
||||
139.47,43.25 138.73,48.77 136.28,51.49
|
||||
134.61,53.34 130.40,54.61 128.00,55.81
|
||||
123.25,58.19 118.99,61.28 115.09,64.87
|
||||
95.21,83.14 91.38,102.52 88.83,128.00
|
||||
88.83,128.00 87.01,144.00 87.01,144.00
|
||||
86.91,148.80 87.45,153.30 88.45,158.00
|
||||
89.14,161.21 90.18,164.22 91.97,167.00
|
||||
104.26,186.09 128.22,173.94 144.00,167.55
|
||||
172.38,156.05 199.62,129.40 200.00,97.00
|
||||
200.17,81.95 201.00,63.14 186.00,54.22
|
||||
179.10,50.12 172.58,50.98 165.00,51.13
|
||||
165.00,51.13 151.00,51.13 151.00,51.13
|
||||
155.73,34.35 145.69,23.20 129.00,23.00
|
||||
123.90,22.94 118.84,22.61 114.00,24.47
|
||||
104.95,27.95 99.13,36.58 92.00,42.82
|
||||
87.09,47.11 84.63,47.51 79.00,50.00 Z
|
||||
M 72.00,53.00
|
||||
C 66.11,51.95 63.71,51.62 58.00,54.18
|
||||
54.47,55.76 50.77,58.80 47.00,59.38
|
||||
41.63,60.20 38.38,55.72 35.37,57.82
|
||||
32.13,60.07 35.93,63.41 38.02,64.56
|
||||
52.89,72.76 56.83,54.74 72.00,61.00
|
||||
69.00,71.18 61.26,75.87 51.00,76.00
|
||||
31.76,76.23 12.34,68.31 9.00,47.00
|
||||
14.94,48.68 19.62,51.68 26.00,51.96
|
||||
41.56,52.64 63.36,34.95 72.00,53.00 Z
|
||||
M 9.00,46.00
|
||||
C 9.00,46.00 9.00,47.00 9.00,47.00
|
||||
9.00,47.00 8.00,46.00 8.00,46.00
|
||||
8.00,46.00 9.00,46.00 9.00,46.00 Z
|
||||
M 101.00,147.00
|
||||
C 99.73,142.53 101.89,130.73 103.15,126.00
|
||||
109.20,103.33 125.28,86.52 145.00,74.60
|
||||
154.93,68.60 175.37,56.39 183.59,71.02
|
||||
184.80,73.18 185.81,76.56 186.33,79.00
|
||||
187.24,83.26 187.01,87.67 187.00,92.00
|
||||
186.95,125.18 162.01,146.49 133.00,157.32
|
||||
126.64,159.69 119.83,163.83 113.00,163.76
|
||||
111.78,163.97 110.18,163.89 108.98,163.76
|
||||
96.75,160.18 108.57,149.47 108.98,146.02
|
||||
109.07,145.10 108.92,144.18 108.30,143.46
|
||||
105.74,140.52 102.38,145.26 101.00,147.00 Z
|
||||
M 175.74,73.01
|
||||
C 173.34,76.50 174.95,80.87 175.00,85.00
|
||||
175.10,93.89 175.38,101.54 171.91,110.00
|
||||
169.56,115.71 165.08,119.46 165.96,122.70
|
||||
166.68,125.33 170.45,125.48 173.32,122.70
|
||||
182.78,113.48 182.14,94.19 182.00,82.00
|
||||
181.95,77.78 181.84,70.88 175.74,73.01 Z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5 KiB |
Loading…
Add table
Add a link
Reference in a new issue