2 Commits

Author SHA1 Message Date
xsxx03-art 2846899097 rotation bug fixed 2026-06-08 18:54:42 +08:00
xsxx03-art 2ddd30e7bb update canvas.html 2026-06-05 20:32:16 +08:00
22 changed files with 1676 additions and 692 deletions
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -19
View File
@@ -1,6 +1,6 @@
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Description: Flask backend API server for authentication, project management, PDK library access, layout preview, and GDS build endpoints. # Description: Flask backend API server for authentication, project management, PDK library access, layout preview, and GDS build endpoints.
# Inside functions: no_cache_response, login_required_json, wrapper, request_ip, record_action, safe_name, user_layout_root, project_root, cell_file_path, cell_svg_path, file_version, cell_routes_path, write_route_points_sidecar, project_gds_path, technology_manifest_path_for_project, current_pdk_root, scoped_pdk_root_for_project, pdk_root_for_request_project, project_meta_path, read_project_meta # Inside functions: no_cache_response, login_required_json, wrapper, request_ip, record_action, safe_name, user_layout_root, project_root, cell_file_path, cell_svg_path, cell_routes_path, write_route_points_sidecar, project_gds_path, technology_manifest_path_for_project, current_pdk_root, scoped_pdk_root_for_project, pdk_root_for_request_project, project_meta_path, read_project_meta
# Developer : Qin Yue @ 2026 # Developer : Qin Yue @ 2026
# Organization : OptiHK Limited # Organization : OptiHK Limited
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -9,7 +9,6 @@ import os
import re import re
import shutil import shutil
import json import json
import uuid
import yaml import yaml
from collections import OrderedDict from collections import OrderedDict
from functools import wraps from functools import wraps
@@ -136,12 +135,6 @@ def cell_svg_path(project_name, cell_name):
return os.path.join(project_root(project_name), f"{safe_name(cell_name, 'canvas_1')}.svg") return os.path.join(project_root(project_name), f"{safe_name(cell_name, 'canvas_1')}.svg")
def file_version(path):
"""Return a cache-busting version token for a completed file."""
stat = os.stat(path)
return f"{stat.st_mtime_ns}-{stat.st_size}"
def cell_routes_path(project_name, cell_name): def cell_routes_path(project_name, cell_name):
"""Return the route sidecar JSON path for a project cell.""" """Return the route sidecar JSON path for a project cell."""
return os.path.join(project_root(project_name), f"{safe_name(cell_name, 'canvas_1')}.routes.yml") return os.path.join(project_root(project_name), f"{safe_name(cell_name, 'canvas_1')}.routes.yml")
@@ -746,31 +739,24 @@ def save_layout():
write_route_points_sidecar(content, cell_routes_path(project, cell)) write_route_points_sidecar(content, cell_routes_path(project, cell))
svg_path = None svg_path = None
svg_version = None
preview_status = "not_requested" preview_status = "not_requested"
preview_error = None preview_error = None
if create_preview: if create_preview:
svg_path = cell_svg_path(project, cell) svg_path = cell_svg_path(project, cell)
temp_svg_path = f"{svg_path}.building-{os.getpid()}-{uuid.uuid4().hex}.svg"
try: try:
create_routed_layout_svg( create_routed_layout_svg(
content, content,
temp_svg_path, svg_path,
pdk_root=current_pdk_root(), pdk_root=current_pdk_root(),
project_dir=project_root(project), project_dir=project_root(project),
technology_manifest_path=technology_manifest_path_for_project(project), technology_manifest_path=technology_manifest_path_for_project(project),
prefer_full_gds=prefer_full_gds_for_session(session), prefer_full_gds=prefer_full_gds_for_session(session),
) )
os.replace(temp_svg_path, svg_path)
svg_version = file_version(svg_path)
preview_status = "generated" preview_status = "generated"
except RouterStackUnavailable as e: except RouterStackUnavailable as e:
preview_status = "skipped" preview_status = "skipped"
preview_error = str(e) preview_error = str(e)
svg_path = None svg_path = None
finally:
if os.path.exists(temp_svg_path):
os.remove(temp_svg_path)
record_action('layout.save', project=project, cell=cell, detail={"bytes": len(content), "svg": svg_path}) record_action('layout.save', project=project, cell=cell, detail={"bytes": len(content), "svg": svg_path})
return jsonify({ return jsonify({
@@ -779,9 +765,7 @@ def save_layout():
"cell": cell, "cell": cell,
"path": save_path, "path": save_path,
"svg_path": svg_path, "svg_path": svg_path,
"svg_url": url_for('get_layout_svg', project_name=project, cell_name=cell, v=svg_version) if svg_path else None, "svg_url": url_for('get_layout_svg', project_name=project, cell_name=cell) if svg_path else None,
"svg_ready": bool(svg_path and svg_version),
"svg_version": svg_version,
"preview_status": preview_status, "preview_status": preview_status,
"preview_error": preview_error "preview_error": preview_error
}), 200 }), 200
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 319 KiB

@@ -1,211 +0,0 @@
# =============================================
# mxPIC Cell/Project Definition File
# =============================================
schema_version: "2.0.0"
kind: cell
coordinate_system: gds_y_up
canvas_size:
width: 500
height: 600
project: mxpic_project_1
name: canvas_1
type: composite
version: "1.0.0"
# 1. External Ports (How this cell connects to the outside world)
pins:
- name: port_io1
layer: WG_CORE
element: port
pin: io1
x: 40.0
y: -90.0
angle: 180.0
width: 0.5
- name: port_1_io1
layer: WG_CORE
element: port_1
pin: io1
x: 410.0
y: -35.0
angle: 0.0
width: 0.5
- name: port_1_io2
layer: WG_CORE
element: port_1
pin: io2
x: 410.0
y: -25.0
angle: 0.0
width: 0.5
- name: port_2_io1
layer: WG_CORE
element: port_2
pin: io1
x: 390.0
y: -215.0
angle: 0.0
width: 0.5
- name: port_2_io2
layer: WG_CORE
element: port_2
pin: io2
x: 390.0
y: -205.0
angle: 0.0
width: 0.5
# 2. Instances (The sub-components dropped onto this canvas)
instances:
MMI_1:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 130.0
y: -90.0
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_2:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 280.0
y: -30.0
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_3:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 320.1
y: -144.7
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
elements:
port:
type: port
x: 40.0
y: -90.0
angle: 180.0
pin_number: 1
pitch: 10
layer: WG_CORE
width: 0.5
description: ""
pins:
- name: port_io1
role: io1
port:
type: port
x: 40.0
y: -90.0
angle: 0.0
pin_number: 1
pitch: 10
layer: WG_CORE
width: 0.5
description: ""
pins:
- name: port_io1
role: io1
port_1:
type: port
x: 410.0
y: -30.0
angle: 180.0
pin_number: 2
pitch: 10
layer: WG_CORE
width: 0.5
description: ""
pins:
- name: port_1_io1
role: io1
- name: port_1_io2
role: io2
port_2:
type: port
x: 390.0
y: -210.0
angle: 180.0
pin_number: 2
pitch: 10
layer: WG_CORE
width: 0.5
description: ""
pins:
- name: port_2_io1
role: io1
- name: port_2_io2
role: io2
# 3. Bundles (Grouped links for multi-bus/parallel routing)
bundles:
output_bus:
routing_type: euler_bend
links:
- from: MMI_1:a1
to: port:port_io1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_2:a1
to: MMI_1:b1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_3:a1
to: MMI_1:b2
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_2:b1
to: port_1:port_1_io1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_2:b1
to: port_1:port_1_io2
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_2:b2
to: port_1:port_1_io1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_3:b1
to: port_2:port_2_io2
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_3:b2
to: port_2:port_2_io1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
@@ -1,90 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<<<<<<< HEAD
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3218.6" height="2963.6" viewBox="14680.2 22644.7 3218.6 2963.6">
<defs>
<style type="text/css">
.l275d0 {stroke: #654522; fill: #654522; fill-opacity: 0.5;}
.l1200d0 {stroke: #F38400; fill: #F38400; fill-opacity: 0.5;}
.l101d251 {stroke: #848482; fill: #848482; fill-opacity: 0.5;}
.l1205d0 {stroke: #008856; fill: #008856; fill-opacity: 0.5;}
.l1001t0 {stroke: none; fill: #A1CAF1;}
</style>
<g id="1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2">
<polygon id="000002B6365DCE60" class="l1200d0" points="-285,-147 585,-147 585,147 -285,147"/>
<polygon id="000002B6365DCB50" class="l1205d0" points="-281.5,3.5 -288.5,3.5 -288.5,-3.5 -281.5,-3.5"/>
<polygon id="000002B6365DD020" class="l1205d0" points="581.5,40 588.5,40 588.5,47 581.5,47"/>
<polygon id="000002B6365DCCA0" class="l1205d0" points="581.5,-47 588.5,-47 588.5,-40 581.5,-40"/>
<text id="000002B605621870" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(-285 0) scale(1 -1)">a1</text>
<text id="000002B605621240" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(585 43.5) scale(1 -1)">b1</text>
<text id="000002B605622830" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(585 -43.5) scale(1 -1)">b2</text>
<text id="000002B605621D80" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(0 0) scale(1 -1)">a0</text>
<text id="000002B605621750" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(0 0) scale(1 -1)">b0</text>
</g>
<g id="1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2_405">
<polygon id="000002B63618CA50" class="l1200d0" points="-285,-147 585,-147 585,147 -285,147"/>
<polygon id="000002B605652430" class="l1205d0" points="-281.5,3.5 -288.5,3.5 -288.5,-3.5 -281.5,-3.5"/>
<polygon id="000002B605651940" class="l1205d0" points="581.5,40 588.5,40 588.5,47 581.5,47"/>
<polygon id="000002B605651BE0" class="l1205d0" points="581.5,-47 588.5,-47 588.5,-40 581.5,-40"/>
<text id="000002B605620E50" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(-285 0) scale(1 -1)">a1</text>
<text id="000002B6056216C0" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(585 43.5) scale(1 -1)">b1</text>
<text id="000002B605622680" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(585 -43.5) scale(1 -1)">b2</text>
<text id="000002B605622B00" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(0 0) scale(1 -1)">a0</text>
<text id="000002B605621E10" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(0 0) scale(1 -1)">b0</text>
</g>
</defs>
<rect x="14680.2" y="22644.7" width="3218.6" height="2963.6" fill="#222222" stroke="none"/>
<g id="mxpic_project_1" transform="scale(1 -1)">
<polygon id="000002B6365DC8B0" class="l275d0" points="16679,-25360.75 15700,-25360.75 15700,-25356.25 16679,-25356.25"/>
<polygon id="000002B6365DC610" class="l275d0" points="16679,-25360.75 15700,-25360.75 15700,-25356.25 16679,-25356.25"/>
<polygon id="000002B6365DC290" class="l275d0" points="16679,-25360.75 15700,-25360.75 15700,-25356.25 16679,-25356.25"/>
<polygon id="000002B6365DC370" class="l275d0" points="16679,-25360.75 15700,-25360.75 15700,-25356.25 16679,-25356.25"/>
<polygon id="000002B6365DC300" class="l275d0" points="16679,-25360.75 15700,-25360.75 15700,-25356.25 16679,-25356.25"/>
<polygon id="000002B6365DCAE0" class="l275d0" points="16679,-25360.75 15700,-25360.75 15700,-25356.25 16679,-25356.25"/>
<polygon id="000002B6365DC680" class="l101d251" points="16719,-25400.75 15660,-25400.75 15660,-25316.25 16719,-25316.25"/>
<polygon id="000002B6365DCF40" class="l101d251" points="16719,-25400.75 15660,-25400.75 15660,-25316.25 16719,-25316.25"/>
<polygon id="000002B6365DC6F0" class="l101d251" points="16719,-25400.75 15660,-25400.75 15660,-25316.25 16719,-25316.25"/>
<polygon id="000002B6365DCFB0" class="l101d251" points="16719,-25400.75 15660,-25400.75 15660,-25316.25 16719,-25316.25"/>
<polygon id="000002B6365DC760" class="l101d251" points="16719,-25400.75 15660,-25400.75 15660,-25316.25 16719,-25316.25"/>
<polygon id="000002B6365DC140" class="l101d251" points="16719,-25400.75 15660,-25400.75 15660,-25316.25 16719,-25316.25"/>
<polygon id="000002B6365DC1B0" class="l275d0" points="16776.75,-25258.5 16781.25,-25258.5 16781.24,-25259.91 16781.17,-25262.73 16781.01,-25265.54 16780.78,-25268.35 16780.47,-25271.15 16780.08,-25273.94 16779.62,-25276.72 16779.08,-25279.48 16778.46,-25282.23 16777.77,-25284.97 16777,-25287.68 16776.16,-25290.37 16775.25,-25293.03 16774.26,-25295.67 16773.2,-25298.28 16772.07,-25300.86 16770.86,-25303.41 16769.59,-25305.92 16768.25,-25308.4 16766.84,-25310.84 16765.37,-25313.24 16763.82,-25315.6 16762.22,-25317.92 16760.55,-25320.19 16758.82,-25322.41 16757.03,-25324.59 16755.18,-25326.71 16753.27,-25328.78 16751.3,-25330.8 16749.28,-25332.77 16747.21,-25334.68 16745.09,-25336.53 16742.91,-25338.32 16740.69,-25340.05 16738.42,-25341.72 16736.1,-25343.32 16733.74,-25344.87 16731.34,-25346.34 16728.9,-25347.75 16726.42,-25349.09 16723.91,-25350.36 16721.36,-25351.57 16718.78,-25352.7 16716.17,-25353.76 16713.53,-25354.75 16710.87,-25355.66 16708.18,-25356.5 16705.47,-25357.27 16702.73,-25357.96 16699.98,-25358.58 16697.22,-25359.12 16694.44,-25359.58 16691.65,-25359.97 16688.85,-25360.28 16686.04,-25360.51 16683.23,-25360.67 16680.41,-25360.74 16679,-25360.75 16679,-25356.25 16680.35,-25356.24 16683.04,-25356.17 16685.73,-25356.02 16688.41,-25355.8 16691.09,-25355.5 16693.76,-25355.13 16696.42,-25354.69 16699.06,-25354.17 16701.69,-25353.58 16704.3,-25352.92 16706.89,-25352.19 16709.46,-25351.39 16712.01,-25350.51 16714.53,-25349.57 16717.03,-25348.55 16719.5,-25347.47 16721.93,-25346.32 16724.34,-25345.1 16726.71,-25343.82 16729.04,-25342.48 16731.33,-25341.06 16733.59,-25339.59 16735.8,-25338.06 16737.97,-25336.46 16740.1,-25334.81 16742.18,-25333.09 16744.21,-25331.32 16746.19,-25329.5 16748.12,-25327.62 16750,-25325.69 16751.82,-25323.71 16753.59,-25321.68 16755.31,-25319.6 16756.96,-25317.47 16758.56,-25315.3 16760.09,-25313.09 16761.56,-25310.83 16762.98,-25308.54 16764.32,-25306.21 16765.6,-25303.84 16766.82,-25301.43 16767.97,-25299 16769.05,-25296.53 16770.07,-25294.03 16771.01,-25291.51 16771.89,-25288.96 16772.69,-25286.39 16773.42,-25283.8 16774.08,-25281.19 16774.67,-25278.56 16775.19,-25275.92 16775.63,-25273.26 16776,-25270.59 16776.3,-25267.91 16776.52,-25265.23 16776.67,-25262.54 16776.74,-25259.85"/>
<polygon id="000002B6365DC3E0" class="l275d0" points="16776.75,-25258.5 16781.25,-25258.5 16781.24,-25259.91 16781.17,-25262.73 16781.01,-25265.54 16780.78,-25268.35 16780.47,-25271.15 16780.08,-25273.94 16779.62,-25276.72 16779.08,-25279.48 16778.46,-25282.23 16777.77,-25284.97 16777,-25287.68 16776.16,-25290.37 16775.25,-25293.03 16774.26,-25295.67 16773.2,-25298.28 16772.07,-25300.86 16770.86,-25303.41 16769.59,-25305.92 16768.25,-25308.4 16766.84,-25310.84 16765.37,-25313.24 16763.82,-25315.6 16762.22,-25317.92 16760.55,-25320.19 16758.82,-25322.41 16757.03,-25324.59 16755.18,-25326.71 16753.27,-25328.78 16751.3,-25330.8 16749.28,-25332.77 16747.21,-25334.68 16745.09,-25336.53 16742.91,-25338.32 16740.69,-25340.05 16738.42,-25341.72 16736.1,-25343.32 16733.74,-25344.87 16731.34,-25346.34 16728.9,-25347.75 16726.42,-25349.09 16723.91,-25350.36 16721.36,-25351.57 16718.78,-25352.7 16716.17,-25353.76 16713.53,-25354.75 16710.87,-25355.66 16708.18,-25356.5 16705.47,-25357.27 16702.73,-25357.96 16699.98,-25358.58 16697.22,-25359.12 16694.44,-25359.58 16691.65,-25359.97 16688.85,-25360.28 16686.04,-25360.51 16683.23,-25360.67 16680.41,-25360.74 16679,-25360.75 16679,-25356.25 16680.35,-25356.24 16683.04,-25356.17 16685.73,-25356.02 16688.41,-25355.8 16691.09,-25355.5 16693.76,-25355.13 16696.42,-25354.69 16699.06,-25354.17 16701.69,-25353.58 16704.3,-25352.92 16706.89,-25352.19 16709.46,-25351.39 16712.01,-25350.51 16714.53,-25349.57 16717.03,-25348.55 16719.5,-25347.47 16721.93,-25346.32 16724.34,-25345.1 16726.71,-25343.82 16729.04,-25342.48 16731.33,-25341.06 16733.59,-25339.59 16735.8,-25338.06 16737.97,-25336.46 16740.1,-25334.81 16742.18,-25333.09 16744.21,-25331.32 16746.19,-25329.5 16748.12,-25327.62 16750,-25325.69 16751.82,-25323.71 16753.59,-25321.68 16755.31,-25319.6 16756.96,-25317.47 16758.56,-25315.3 16760.09,-25313.09 16761.56,-25310.83 16762.98,-25308.54 16764.32,-25306.21 16765.6,-25303.84 16766.82,-25301.43 16767.97,-25299 16769.05,-25296.53 16770.07,-25294.03 16771.01,-25291.51 16771.89,-25288.96 16772.69,-25286.39 16773.42,-25283.8 16774.08,-25281.19 16774.67,-25278.56 16775.19,-25275.92 16775.63,-25273.26 16776,-25270.59 16776.3,-25267.91 16776.52,-25265.23 16776.67,-25262.54 16776.74,-25259.85"/>
<polygon id="000002B6365DC450" class="l275d0" points="16776.75,-25258.5 16781.25,-25258.5 16781.24,-25259.91 16781.17,-25262.73 16781.01,-25265.54 16780.78,-25268.35 16780.47,-25271.15 16780.08,-25273.94 16779.62,-25276.72 16779.08,-25279.48 16778.46,-25282.23 16777.77,-25284.97 16777,-25287.68 16776.16,-25290.37 16775.25,-25293.03 16774.26,-25295.67 16773.2,-25298.28 16772.07,-25300.86 16770.86,-25303.41 16769.59,-25305.92 16768.25,-25308.4 16766.84,-25310.84 16765.37,-25313.24 16763.82,-25315.6 16762.22,-25317.92 16760.55,-25320.19 16758.82,-25322.41 16757.03,-25324.59 16755.18,-25326.71 16753.27,-25328.78 16751.3,-25330.8 16749.28,-25332.77 16747.21,-25334.68 16745.09,-25336.53 16742.91,-25338.32 16740.69,-25340.05 16738.42,-25341.72 16736.1,-25343.32 16733.74,-25344.87 16731.34,-25346.34 16728.9,-25347.75 16726.42,-25349.09 16723.91,-25350.36 16721.36,-25351.57 16718.78,-25352.7 16716.17,-25353.76 16713.53,-25354.75 16710.87,-25355.66 16708.18,-25356.5 16705.47,-25357.27 16702.73,-25357.96 16699.98,-25358.58 16697.22,-25359.12 16694.44,-25359.58 16691.65,-25359.97 16688.85,-25360.28 16686.04,-25360.51 16683.23,-25360.67 16680.41,-25360.74 16679,-25360.75 16679,-25356.25 16680.35,-25356.24 16683.04,-25356.17 16685.73,-25356.02 16688.41,-25355.8 16691.09,-25355.5 16693.76,-25355.13 16696.42,-25354.69 16699.06,-25354.17 16701.69,-25353.58 16704.3,-25352.92 16706.89,-25352.19 16709.46,-25351.39 16712.01,-25350.51 16714.53,-25349.57 16717.03,-25348.55 16719.5,-25347.47 16721.93,-25346.32 16724.34,-25345.1 16726.71,-25343.82 16729.04,-25342.48 16731.33,-25341.06 16733.59,-25339.59 16735.8,-25338.06 16737.97,-25336.46 16740.1,-25334.81 16742.18,-25333.09 16744.21,-25331.32 16746.19,-25329.5 16748.12,-25327.62 16750,-25325.69 16751.82,-25323.71 16753.59,-25321.68 16755.31,-25319.6 16756.96,-25317.47 16758.56,-25315.3 16760.09,-25313.09 16761.56,-25310.83 16762.98,-25308.54 16764.32,-25306.21 16765.6,-25303.84 16766.82,-25301.43 16767.97,-25299 16769.05,-25296.53 16770.07,-25294.03 16771.01,-25291.51 16771.89,-25288.96 16772.69,-25286.39 16773.42,-25283.8 16774.08,-25281.19 16774.67,-25278.56 16775.19,-25275.92 16775.63,-25273.26 16776,-25270.59 16776.3,-25267.91 16776.52,-25265.23 16776.67,-25262.54 16776.74,-25259.85"/>
<polygon id="000002B6365DC220" class="l275d0" points="16776.75,-25258.5 16781.25,-25258.5 16781.24,-25259.91 16781.17,-25262.73 16781.01,-25265.54 16780.78,-25268.35 16780.47,-25271.15 16780.08,-25273.94 16779.62,-25276.72 16779.08,-25279.48 16778.46,-25282.23 16777.77,-25284.97 16777,-25287.68 16776.16,-25290.37 16775.25,-25293.03 16774.26,-25295.67 16773.2,-25298.28 16772.07,-25300.86 16770.86,-25303.41 16769.59,-25305.92 16768.25,-25308.4 16766.84,-25310.84 16765.37,-25313.24 16763.82,-25315.6 16762.22,-25317.92 16760.55,-25320.19 16758.82,-25322.41 16757.03,-25324.59 16755.18,-25326.71 16753.27,-25328.78 16751.3,-25330.8 16749.28,-25332.77 16747.21,-25334.68 16745.09,-25336.53 16742.91,-25338.32 16740.69,-25340.05 16738.42,-25341.72 16736.1,-25343.32 16733.74,-25344.87 16731.34,-25346.34 16728.9,-25347.75 16726.42,-25349.09 16723.91,-25350.36 16721.36,-25351.57 16718.78,-25352.7 16716.17,-25353.76 16713.53,-25354.75 16710.87,-25355.66 16708.18,-25356.5 16705.47,-25357.27 16702.73,-25357.96 16699.98,-25358.58 16697.22,-25359.12 16694.44,-25359.58 16691.65,-25359.97 16688.85,-25360.28 16686.04,-25360.51 16683.23,-25360.67 16680.41,-25360.74 16679,-25360.75 16679,-25356.25 16680.35,-25356.24 16683.04,-25356.17 16685.73,-25356.02 16688.41,-25355.8 16691.09,-25355.5 16693.76,-25355.13 16696.42,-25354.69 16699.06,-25354.17 16701.69,-25353.58 16704.3,-25352.92 16706.89,-25352.19 16709.46,-25351.39 16712.01,-25350.51 16714.53,-25349.57 16717.03,-25348.55 16719.5,-25347.47 16721.93,-25346.32 16724.34,-25345.1 16726.71,-25343.82 16729.04,-25342.48 16731.33,-25341.06 16733.59,-25339.59 16735.8,-25338.06 16737.97,-25336.46 16740.1,-25334.81 16742.18,-25333.09 16744.21,-25331.32 16746.19,-25329.5 16748.12,-25327.62 16750,-25325.69 16751.82,-25323.71 16753.59,-25321.68 16755.31,-25319.6 16756.96,-25317.47 16758.56,-25315.3 16760.09,-25313.09 16761.56,-25310.83 16762.98,-25308.54 16764.32,-25306.21 16765.6,-25303.84 16766.82,-25301.43 16767.97,-25299 16769.05,-25296.53 16770.07,-25294.03 16771.01,-25291.51 16771.89,-25288.96 16772.69,-25286.39 16773.42,-25283.8 16774.08,-25281.19 16774.67,-25278.56 16775.19,-25275.92 16775.63,-25273.26 16776,-25270.59 16776.3,-25267.91 16776.52,-25265.23 16776.67,-25262.54 16776.74,-25259.85"/>
<polygon id="000002B6365DC4C0" class="l275d0" points="16776.75,-25258.5 16781.25,-25258.5 16781.24,-25259.91 16781.17,-25262.73 16781.01,-25265.54 16780.78,-25268.35 16780.47,-25271.15 16780.08,-25273.94 16779.62,-25276.72 16779.08,-25279.48 16778.46,-25282.23 16777.77,-25284.97 16777,-25287.68 16776.16,-25290.37 16775.25,-25293.03 16774.26,-25295.67 16773.2,-25298.28 16772.07,-25300.86 16770.86,-25303.41 16769.59,-25305.92 16768.25,-25308.4 16766.84,-25310.84 16765.37,-25313.24 16763.82,-25315.6 16762.22,-25317.92 16760.55,-25320.19 16758.82,-25322.41 16757.03,-25324.59 16755.18,-25326.71 16753.27,-25328.78 16751.3,-25330.8 16749.28,-25332.77 16747.21,-25334.68 16745.09,-25336.53 16742.91,-25338.32 16740.69,-25340.05 16738.42,-25341.72 16736.1,-25343.32 16733.74,-25344.87 16731.34,-25346.34 16728.9,-25347.75 16726.42,-25349.09 16723.91,-25350.36 16721.36,-25351.57 16718.78,-25352.7 16716.17,-25353.76 16713.53,-25354.75 16710.87,-25355.66 16708.18,-25356.5 16705.47,-25357.27 16702.73,-25357.96 16699.98,-25358.58 16697.22,-25359.12 16694.44,-25359.58 16691.65,-25359.97 16688.85,-25360.28 16686.04,-25360.51 16683.23,-25360.67 16680.41,-25360.74 16679,-25360.75 16679,-25356.25 16680.35,-25356.24 16683.04,-25356.17 16685.73,-25356.02 16688.41,-25355.8 16691.09,-25355.5 16693.76,-25355.13 16696.42,-25354.69 16699.06,-25354.17 16701.69,-25353.58 16704.3,-25352.92 16706.89,-25352.19 16709.46,-25351.39 16712.01,-25350.51 16714.53,-25349.57 16717.03,-25348.55 16719.5,-25347.47 16721.93,-25346.32 16724.34,-25345.1 16726.71,-25343.82 16729.04,-25342.48 16731.33,-25341.06 16733.59,-25339.59 16735.8,-25338.06 16737.97,-25336.46 16740.1,-25334.81 16742.18,-25333.09 16744.21,-25331.32 16746.19,-25329.5 16748.12,-25327.62 16750,-25325.69 16751.82,-25323.71 16753.59,-25321.68 16755.31,-25319.6 16756.96,-25317.47 16758.56,-25315.3 16760.09,-25313.09 16761.56,-25310.83 16762.98,-25308.54 16764.32,-25306.21 16765.6,-25303.84 16766.82,-25301.43 16767.97,-25299 16769.05,-25296.53 16770.07,-25294.03 16771.01,-25291.51 16771.89,-25288.96 16772.69,-25286.39 16773.42,-25283.8 16774.08,-25281.19 16774.67,-25278.56 16775.19,-25275.92 16775.63,-25273.26 16776,-25270.59 16776.3,-25267.91 16776.52,-25265.23 16776.67,-25262.54 16776.74,-25259.85"/>
<polygon id="000002B6365DCBC0" class="l275d0" points="16776.75,-25258.5 16781.25,-25258.5 16781.24,-25259.91 16781.17,-25262.73 16781.01,-25265.54 16780.78,-25268.35 16780.47,-25271.15 16780.08,-25273.94 16779.62,-25276.72 16779.08,-25279.48 16778.46,-25282.23 16777.77,-25284.97 16777,-25287.68 16776.16,-25290.37 16775.25,-25293.03 16774.26,-25295.67 16773.2,-25298.28 16772.07,-25300.86 16770.86,-25303.41 16769.59,-25305.92 16768.25,-25308.4 16766.84,-25310.84 16765.37,-25313.24 16763.82,-25315.6 16762.22,-25317.92 16760.55,-25320.19 16758.82,-25322.41 16757.03,-25324.59 16755.18,-25326.71 16753.27,-25328.78 16751.3,-25330.8 16749.28,-25332.77 16747.21,-25334.68 16745.09,-25336.53 16742.91,-25338.32 16740.69,-25340.05 16738.42,-25341.72 16736.1,-25343.32 16733.74,-25344.87 16731.34,-25346.34 16728.9,-25347.75 16726.42,-25349.09 16723.91,-25350.36 16721.36,-25351.57 16718.78,-25352.7 16716.17,-25353.76 16713.53,-25354.75 16710.87,-25355.66 16708.18,-25356.5 16705.47,-25357.27 16702.73,-25357.96 16699.98,-25358.58 16697.22,-25359.12 16694.44,-25359.58 16691.65,-25359.97 16688.85,-25360.28 16686.04,-25360.51 16683.23,-25360.67 16680.41,-25360.74 16679,-25360.75 16679,-25356.25 16680.35,-25356.24 16683.04,-25356.17 16685.73,-25356.02 16688.41,-25355.8 16691.09,-25355.5 16693.76,-25355.13 16696.42,-25354.69 16699.06,-25354.17 16701.69,-25353.58 16704.3,-25352.92 16706.89,-25352.19 16709.46,-25351.39 16712.01,-25350.51 16714.53,-25349.57 16717.03,-25348.55 16719.5,-25347.47 16721.93,-25346.32 16724.34,-25345.1 16726.71,-25343.82 16729.04,-25342.48 16731.33,-25341.06 16733.59,-25339.59 16735.8,-25338.06 16737.97,-25336.46 16740.1,-25334.81 16742.18,-25333.09 16744.21,-25331.32 16746.19,-25329.5 16748.12,-25327.62 16750,-25325.69 16751.82,-25323.71 16753.59,-25321.68 16755.31,-25319.6 16756.96,-25317.47 16758.56,-25315.3 16760.09,-25313.09 16761.56,-25310.83 16762.98,-25308.54 16764.32,-25306.21 16765.6,-25303.84 16766.82,-25301.43 16767.97,-25299 16769.05,-25296.53 16770.07,-25294.03 16771.01,-25291.51 16771.89,-25288.96 16772.69,-25286.39 16773.42,-25283.8 16774.08,-25281.19 16774.67,-25278.56 16775.19,-25275.92 16775.63,-25273.26 16776,-25270.59 16776.3,-25267.91 16776.52,-25265.23 16776.67,-25262.54 16776.74,-25259.85"/>
<polygon id="000002B6365DCED0" class="l101d251" points="16736.75,-25258.5 16821.25,-25258.5 16821.24,-25260.17 16821.17,-25263.5 16821.01,-25266.83 16820.77,-25270.16 16820.46,-25273.48 16820.07,-25276.79 16819.6,-25280.09 16819.06,-25283.38 16818.44,-25286.66 16817.74,-25289.92 16816.96,-25293.17 16816.11,-25296.39 16815.19,-25299.59 16814.19,-25302.78 16813.11,-25305.93 16811.96,-25309.06 16810.74,-25312.17 16809.45,-25315.24 16808.08,-25318.28 16806.64,-25321.29 16805.14,-25324.27 16803.56,-25327.21 16801.92,-25330.11 16800.2,-25332.97 16798.42,-25335.79 16796.58,-25338.57 16794.67,-25341.3 16792.7,-25343.99 16790.66,-25346.63 16788.57,-25349.23 16786.41,-25351.77 16784.19,-25354.26 16781.92,-25356.7 16779.59,-25359.09 16777.2,-25361.42 16774.76,-25363.69 16772.27,-25365.91 16769.73,-25368.07 16767.13,-25370.16 16764.49,-25372.2 16761.8,-25374.17 16759.07,-25376.08 16756.29,-25377.92 16753.47,-25379.7 16750.61,-25381.42 16747.71,-25383.06 16744.77,-25384.64 16741.79,-25386.14 16738.78,-25387.58 16735.74,-25388.95 16732.67,-25390.24 16729.56,-25391.46 16726.43,-25392.61 16723.28,-25393.69 16720.09,-25394.69 16716.89,-25395.61 16713.67,-25396.46 16710.42,-25397.24 16707.16,-25397.94 16703.88,-25398.56 16700.59,-25399.1 16697.29,-25399.57 16693.98,-25399.96 16690.66,-25400.27 16687.33,-25400.51 16684,-25400.67 16680.67,-25400.74 16679,-25400.75 16679,-25316.25 16679.68,-25316.25 16681.03,-25316.22 16682.38,-25316.15 16683.73,-25316.06 16685.08,-25315.93 16686.43,-25315.77 16687.77,-25315.58 16689.1,-25315.36 16690.43,-25315.11 16691.76,-25314.82 16693.07,-25314.51 16694.38,-25314.16 16695.68,-25313.79 16696.97,-25313.38 16698.26,-25312.95 16699.53,-25312.48 16700.79,-25311.98 16702.04,-25311.46 16703.27,-25310.9 16704.49,-25310.32 16705.7,-25309.71 16706.89,-25309.07 16708.07,-25308.4 16709.23,-25307.71 16710.38,-25306.98 16711.51,-25306.23 16712.62,-25305.46 16713.71,-25304.66 16714.78,-25303.83 16715.83,-25302.98 16716.87,-25302.11 16717.88,-25301.21 16718.87,-25300.28 16719.84,-25299.34 16720.78,-25298.37 16721.71,-25297.38 16722.61,-25296.37 16723.48,-25295.33 16724.33,-25294.28 16725.16,-25293.21 16725.96,-25292.12 16726.73,-25291.01 16727.48,-25289.88 16728.21,-25288.73 16728.9,-25287.57 16729.57,-25286.39 16730.21,-25285.2 16730.82,-25283.99 16731.4,-25282.77 16731.96,-25281.54 16732.48,-25280.29 16732.98,-25279.03 16733.45,-25277.76 16733.88,-25276.47 16734.29,-25275.18 16734.66,-25273.88 16735.01,-25272.57 16735.32,-25271.26 16735.61,-25269.93 16735.86,-25268.6 16736.08,-25267.27 16736.27,-25265.93 16736.43,-25264.58 16736.56,-25263.23 16736.65,-25261.88 16736.72,-25260.53 16736.75,-25259.18"/>
<polygon id="000002B6365DC920" class="l101d251" points="16736.75,-25258.5 16821.25,-25258.5 16821.24,-25260.17 16821.17,-25263.5 16821.01,-25266.83 16820.77,-25270.16 16820.46,-25273.48 16820.07,-25276.79 16819.6,-25280.09 16819.06,-25283.38 16818.44,-25286.66 16817.74,-25289.92 16816.96,-25293.17 16816.11,-25296.39 16815.19,-25299.59 16814.19,-25302.78 16813.11,-25305.93 16811.96,-25309.06 16810.74,-25312.17 16809.45,-25315.24 16808.08,-25318.28 16806.64,-25321.29 16805.14,-25324.27 16803.56,-25327.21 16801.92,-25330.11 16800.2,-25332.97 16798.42,-25335.79 16796.58,-25338.57 16794.67,-25341.3 16792.7,-25343.99 16790.66,-25346.63 16788.57,-25349.23 16786.41,-25351.77 16784.19,-25354.26 16781.92,-25356.7 16779.59,-25359.09 16777.2,-25361.42 16774.76,-25363.69 16772.27,-25365.91 16769.73,-25368.07 16767.13,-25370.16 16764.49,-25372.2 16761.8,-25374.17 16759.07,-25376.08 16756.29,-25377.92 16753.47,-25379.7 16750.61,-25381.42 16747.71,-25383.06 16744.77,-25384.64 16741.79,-25386.14 16738.78,-25387.58 16735.74,-25388.95 16732.67,-25390.24 16729.56,-25391.46 16726.43,-25392.61 16723.28,-25393.69 16720.09,-25394.69 16716.89,-25395.61 16713.67,-25396.46 16710.42,-25397.24 16707.16,-25397.94 16703.88,-25398.56 16700.59,-25399.1 16697.29,-25399.57 16693.98,-25399.96 16690.66,-25400.27 16687.33,-25400.51 16684,-25400.67 16680.67,-25400.74 16679,-25400.75 16679,-25316.25 16679.68,-25316.25 16681.03,-25316.22 16682.38,-25316.15 16683.73,-25316.06 16685.08,-25315.93 16686.43,-25315.77 16687.77,-25315.58 16689.1,-25315.36 16690.43,-25315.11 16691.76,-25314.82 16693.07,-25314.51 16694.38,-25314.16 16695.68,-25313.79 16696.97,-25313.38 16698.26,-25312.95 16699.53,-25312.48 16700.79,-25311.98 16702.04,-25311.46 16703.27,-25310.9 16704.49,-25310.32 16705.7,-25309.71 16706.89,-25309.07 16708.07,-25308.4 16709.23,-25307.71 16710.38,-25306.98 16711.51,-25306.23 16712.62,-25305.46 16713.71,-25304.66 16714.78,-25303.83 16715.83,-25302.98 16716.87,-25302.11 16717.88,-25301.21 16718.87,-25300.28 16719.84,-25299.34 16720.78,-25298.37 16721.71,-25297.38 16722.61,-25296.37 16723.48,-25295.33 16724.33,-25294.28 16725.16,-25293.21 16725.96,-25292.12 16726.73,-25291.01 16727.48,-25289.88 16728.21,-25288.73 16728.9,-25287.57 16729.57,-25286.39 16730.21,-25285.2 16730.82,-25283.99 16731.4,-25282.77 16731.96,-25281.54 16732.48,-25280.29 16732.98,-25279.03 16733.45,-25277.76 16733.88,-25276.47 16734.29,-25275.18 16734.66,-25273.88 16735.01,-25272.57 16735.32,-25271.26 16735.61,-25269.93 16735.86,-25268.6 16736.08,-25267.27 16736.27,-25265.93 16736.43,-25264.58 16736.56,-25263.23 16736.65,-25261.88 16736.72,-25260.53 16736.75,-25259.18"/>
<polygon id="000002B6365DC840" class="l101d251" points="16736.75,-25258.5 16821.25,-25258.5 16821.24,-25260.17 16821.17,-25263.5 16821.01,-25266.83 16820.77,-25270.16 16820.46,-25273.48 16820.07,-25276.79 16819.6,-25280.09 16819.06,-25283.38 16818.44,-25286.66 16817.74,-25289.92 16816.96,-25293.17 16816.11,-25296.39 16815.19,-25299.59 16814.19,-25302.78 16813.11,-25305.93 16811.96,-25309.06 16810.74,-25312.17 16809.45,-25315.24 16808.08,-25318.28 16806.64,-25321.29 16805.14,-25324.27 16803.56,-25327.21 16801.92,-25330.11 16800.2,-25332.97 16798.42,-25335.79 16796.58,-25338.57 16794.67,-25341.3 16792.7,-25343.99 16790.66,-25346.63 16788.57,-25349.23 16786.41,-25351.77 16784.19,-25354.26 16781.92,-25356.7 16779.59,-25359.09 16777.2,-25361.42 16774.76,-25363.69 16772.27,-25365.91 16769.73,-25368.07 16767.13,-25370.16 16764.49,-25372.2 16761.8,-25374.17 16759.07,-25376.08 16756.29,-25377.92 16753.47,-25379.7 16750.61,-25381.42 16747.71,-25383.06 16744.77,-25384.64 16741.79,-25386.14 16738.78,-25387.58 16735.74,-25388.95 16732.67,-25390.24 16729.56,-25391.46 16726.43,-25392.61 16723.28,-25393.69 16720.09,-25394.69 16716.89,-25395.61 16713.67,-25396.46 16710.42,-25397.24 16707.16,-25397.94 16703.88,-25398.56 16700.59,-25399.1 16697.29,-25399.57 16693.98,-25399.96 16690.66,-25400.27 16687.33,-25400.51 16684,-25400.67 16680.67,-25400.74 16679,-25400.75 16679,-25316.25 16679.68,-25316.25 16681.03,-25316.22 16682.38,-25316.15 16683.73,-25316.06 16685.08,-25315.93 16686.43,-25315.77 16687.77,-25315.58 16689.1,-25315.36 16690.43,-25315.11 16691.76,-25314.82 16693.07,-25314.51 16694.38,-25314.16 16695.68,-25313.79 16696.97,-25313.38 16698.26,-25312.95 16699.53,-25312.48 16700.79,-25311.98 16702.04,-25311.46 16703.27,-25310.9 16704.49,-25310.32 16705.7,-25309.71 16706.89,-25309.07 16708.07,-25308.4 16709.23,-25307.71 16710.38,-25306.98 16711.51,-25306.23 16712.62,-25305.46 16713.71,-25304.66 16714.78,-25303.83 16715.83,-25302.98 16716.87,-25302.11 16717.88,-25301.21 16718.87,-25300.28 16719.84,-25299.34 16720.78,-25298.37 16721.71,-25297.38 16722.61,-25296.37 16723.48,-25295.33 16724.33,-25294.28 16725.16,-25293.21 16725.96,-25292.12 16726.73,-25291.01 16727.48,-25289.88 16728.21,-25288.73 16728.9,-25287.57 16729.57,-25286.39 16730.21,-25285.2 16730.82,-25283.99 16731.4,-25282.77 16731.96,-25281.54 16732.48,-25280.29 16732.98,-25279.03 16733.45,-25277.76 16733.88,-25276.47 16734.29,-25275.18 16734.66,-25273.88 16735.01,-25272.57 16735.32,-25271.26 16735.61,-25269.93 16735.86,-25268.6 16736.08,-25267.27 16736.27,-25265.93 16736.43,-25264.58 16736.56,-25263.23 16736.65,-25261.88 16736.72,-25260.53 16736.75,-25259.18"/>
<polygon id="000002B6365DCC30" class="l101d251" points="16736.75,-25258.5 16821.25,-25258.5 16821.24,-25260.17 16821.17,-25263.5 16821.01,-25266.83 16820.77,-25270.16 16820.46,-25273.48 16820.07,-25276.79 16819.6,-25280.09 16819.06,-25283.38 16818.44,-25286.66 16817.74,-25289.92 16816.96,-25293.17 16816.11,-25296.39 16815.19,-25299.59 16814.19,-25302.78 16813.11,-25305.93 16811.96,-25309.06 16810.74,-25312.17 16809.45,-25315.24 16808.08,-25318.28 16806.64,-25321.29 16805.14,-25324.27 16803.56,-25327.21 16801.92,-25330.11 16800.2,-25332.97 16798.42,-25335.79 16796.58,-25338.57 16794.67,-25341.3 16792.7,-25343.99 16790.66,-25346.63 16788.57,-25349.23 16786.41,-25351.77 16784.19,-25354.26 16781.92,-25356.7 16779.59,-25359.09 16777.2,-25361.42 16774.76,-25363.69 16772.27,-25365.91 16769.73,-25368.07 16767.13,-25370.16 16764.49,-25372.2 16761.8,-25374.17 16759.07,-25376.08 16756.29,-25377.92 16753.47,-25379.7 16750.61,-25381.42 16747.71,-25383.06 16744.77,-25384.64 16741.79,-25386.14 16738.78,-25387.58 16735.74,-25388.95 16732.67,-25390.24 16729.56,-25391.46 16726.43,-25392.61 16723.28,-25393.69 16720.09,-25394.69 16716.89,-25395.61 16713.67,-25396.46 16710.42,-25397.24 16707.16,-25397.94 16703.88,-25398.56 16700.59,-25399.1 16697.29,-25399.57 16693.98,-25399.96 16690.66,-25400.27 16687.33,-25400.51 16684,-25400.67 16680.67,-25400.74 16679,-25400.75 16679,-25316.25 16679.68,-25316.25 16681.03,-25316.22 16682.38,-25316.15 16683.73,-25316.06 16685.08,-25315.93 16686.43,-25315.77 16687.77,-25315.58 16689.1,-25315.36 16690.43,-25315.11 16691.76,-25314.82 16693.07,-25314.51 16694.38,-25314.16 16695.68,-25313.79 16696.97,-25313.38 16698.26,-25312.95 16699.53,-25312.48 16700.79,-25311.98 16702.04,-25311.46 16703.27,-25310.9 16704.49,-25310.32 16705.7,-25309.71 16706.89,-25309.07 16708.07,-25308.4 16709.23,-25307.71 16710.38,-25306.98 16711.51,-25306.23 16712.62,-25305.46 16713.71,-25304.66 16714.78,-25303.83 16715.83,-25302.98 16716.87,-25302.11 16717.88,-25301.21 16718.87,-25300.28 16719.84,-25299.34 16720.78,-25298.37 16721.71,-25297.38 16722.61,-25296.37 16723.48,-25295.33 16724.33,-25294.28 16725.16,-25293.21 16725.96,-25292.12 16726.73,-25291.01 16727.48,-25289.88 16728.21,-25288.73 16728.9,-25287.57 16729.57,-25286.39 16730.21,-25285.2 16730.82,-25283.99 16731.4,-25282.77 16731.96,-25281.54 16732.48,-25280.29 16732.98,-25279.03 16733.45,-25277.76 16733.88,-25276.47 16734.29,-25275.18 16734.66,-25273.88 16735.01,-25272.57 16735.32,-25271.26 16735.61,-25269.93 16735.86,-25268.6 16736.08,-25267.27 16736.27,-25265.93 16736.43,-25264.58 16736.56,-25263.23 16736.65,-25261.88 16736.72,-25260.53 16736.75,-25259.18"/>
<polygon id="000002B6365DCD10" class="l101d251" points="16736.75,-25258.5 16821.25,-25258.5 16821.24,-25260.17 16821.17,-25263.5 16821.01,-25266.83 16820.77,-25270.16 16820.46,-25273.48 16820.07,-25276.79 16819.6,-25280.09 16819.06,-25283.38 16818.44,-25286.66 16817.74,-25289.92 16816.96,-25293.17 16816.11,-25296.39 16815.19,-25299.59 16814.19,-25302.78 16813.11,-25305.93 16811.96,-25309.06 16810.74,-25312.17 16809.45,-25315.24 16808.08,-25318.28 16806.64,-25321.29 16805.14,-25324.27 16803.56,-25327.21 16801.92,-25330.11 16800.2,-25332.97 16798.42,-25335.79 16796.58,-25338.57 16794.67,-25341.3 16792.7,-25343.99 16790.66,-25346.63 16788.57,-25349.23 16786.41,-25351.77 16784.19,-25354.26 16781.92,-25356.7 16779.59,-25359.09 16777.2,-25361.42 16774.76,-25363.69 16772.27,-25365.91 16769.73,-25368.07 16767.13,-25370.16 16764.49,-25372.2 16761.8,-25374.17 16759.07,-25376.08 16756.29,-25377.92 16753.47,-25379.7 16750.61,-25381.42 16747.71,-25383.06 16744.77,-25384.64 16741.79,-25386.14 16738.78,-25387.58 16735.74,-25388.95 16732.67,-25390.24 16729.56,-25391.46 16726.43,-25392.61 16723.28,-25393.69 16720.09,-25394.69 16716.89,-25395.61 16713.67,-25396.46 16710.42,-25397.24 16707.16,-25397.94 16703.88,-25398.56 16700.59,-25399.1 16697.29,-25399.57 16693.98,-25399.96 16690.66,-25400.27 16687.33,-25400.51 16684,-25400.67 16680.67,-25400.74 16679,-25400.75 16679,-25316.25 16679.68,-25316.25 16681.03,-25316.22 16682.38,-25316.15 16683.73,-25316.06 16685.08,-25315.93 16686.43,-25315.77 16687.77,-25315.58 16689.1,-25315.36 16690.43,-25315.11 16691.76,-25314.82 16693.07,-25314.51 16694.38,-25314.16 16695.68,-25313.79 16696.97,-25313.38 16698.26,-25312.95 16699.53,-25312.48 16700.79,-25311.98 16702.04,-25311.46 16703.27,-25310.9 16704.49,-25310.32 16705.7,-25309.71 16706.89,-25309.07 16708.07,-25308.4 16709.23,-25307.71 16710.38,-25306.98 16711.51,-25306.23 16712.62,-25305.46 16713.71,-25304.66 16714.78,-25303.83 16715.83,-25302.98 16716.87,-25302.11 16717.88,-25301.21 16718.87,-25300.28 16719.84,-25299.34 16720.78,-25298.37 16721.71,-25297.38 16722.61,-25296.37 16723.48,-25295.33 16724.33,-25294.28 16725.16,-25293.21 16725.96,-25292.12 16726.73,-25291.01 16727.48,-25289.88 16728.21,-25288.73 16728.9,-25287.57 16729.57,-25286.39 16730.21,-25285.2 16730.82,-25283.99 16731.4,-25282.77 16731.96,-25281.54 16732.48,-25280.29 16732.98,-25279.03 16733.45,-25277.76 16733.88,-25276.47 16734.29,-25275.18 16734.66,-25273.88 16735.01,-25272.57 16735.32,-25271.26 16735.61,-25269.93 16735.86,-25268.6 16736.08,-25267.27 16736.27,-25265.93 16736.43,-25264.58 16736.56,-25263.23 16736.65,-25261.88 16736.72,-25260.53 16736.75,-25259.18"/>
<polygon id="000002B6365DC530" class="l101d251" points="16736.75,-25258.5 16821.25,-25258.5 16821.24,-25260.17 16821.17,-25263.5 16821.01,-25266.83 16820.77,-25270.16 16820.46,-25273.48 16820.07,-25276.79 16819.6,-25280.09 16819.06,-25283.38 16818.44,-25286.66 16817.74,-25289.92 16816.96,-25293.17 16816.11,-25296.39 16815.19,-25299.59 16814.19,-25302.78 16813.11,-25305.93 16811.96,-25309.06 16810.74,-25312.17 16809.45,-25315.24 16808.08,-25318.28 16806.64,-25321.29 16805.14,-25324.27 16803.56,-25327.21 16801.92,-25330.11 16800.2,-25332.97 16798.42,-25335.79 16796.58,-25338.57 16794.67,-25341.3 16792.7,-25343.99 16790.66,-25346.63 16788.57,-25349.23 16786.41,-25351.77 16784.19,-25354.26 16781.92,-25356.7 16779.59,-25359.09 16777.2,-25361.42 16774.76,-25363.69 16772.27,-25365.91 16769.73,-25368.07 16767.13,-25370.16 16764.49,-25372.2 16761.8,-25374.17 16759.07,-25376.08 16756.29,-25377.92 16753.47,-25379.7 16750.61,-25381.42 16747.71,-25383.06 16744.77,-25384.64 16741.79,-25386.14 16738.78,-25387.58 16735.74,-25388.95 16732.67,-25390.24 16729.56,-25391.46 16726.43,-25392.61 16723.28,-25393.69 16720.09,-25394.69 16716.89,-25395.61 16713.67,-25396.46 16710.42,-25397.24 16707.16,-25397.94 16703.88,-25398.56 16700.59,-25399.1 16697.29,-25399.57 16693.98,-25399.96 16690.66,-25400.27 16687.33,-25400.51 16684,-25400.67 16680.67,-25400.74 16679,-25400.75 16679,-25316.25 16679.68,-25316.25 16681.03,-25316.22 16682.38,-25316.15 16683.73,-25316.06 16685.08,-25315.93 16686.43,-25315.77 16687.77,-25315.58 16689.1,-25315.36 16690.43,-25315.11 16691.76,-25314.82 16693.07,-25314.51 16694.38,-25314.16 16695.68,-25313.79 16696.97,-25313.38 16698.26,-25312.95 16699.53,-25312.48 16700.79,-25311.98 16702.04,-25311.46 16703.27,-25310.9 16704.49,-25310.32 16705.7,-25309.71 16706.89,-25309.07 16708.07,-25308.4 16709.23,-25307.71 16710.38,-25306.98 16711.51,-25306.23 16712.62,-25305.46 16713.71,-25304.66 16714.78,-25303.83 16715.83,-25302.98 16716.87,-25302.11 16717.88,-25301.21 16718.87,-25300.28 16719.84,-25299.34 16720.78,-25298.37 16721.71,-25297.38 16722.61,-25296.37 16723.48,-25295.33 16724.33,-25294.28 16725.16,-25293.21 16725.96,-25292.12 16726.73,-25291.01 16727.48,-25289.88 16728.21,-25288.73 16728.9,-25287.57 16729.57,-25286.39 16730.21,-25285.2 16730.82,-25283.99 16731.4,-25282.77 16731.96,-25281.54 16732.48,-25280.29 16732.98,-25279.03 16733.45,-25277.76 16733.88,-25276.47 16734.29,-25275.18 16734.66,-25273.88 16735.01,-25272.57 16735.32,-25271.26 16735.61,-25269.93 16735.86,-25268.6 16736.08,-25267.27 16736.27,-25265.93 16736.43,-25264.58 16736.56,-25263.23 16736.65,-25261.88 16736.72,-25260.53 16736.75,-25259.18"/>
<polygon id="000002B6365DC5A0" class="l275d0" points="16781.25,-23038 16781.25,-25258.5 16776.75,-25258.5 16776.75,-23038"/>
<polygon id="000002B6365DC990" class="l275d0" points="16781.25,-23038 16781.25,-25258.5 16776.75,-25258.5 16776.75,-23038"/>
<polygon id="000002B6365DC7D0" class="l275d0" points="16781.25,-23038 16781.25,-25258.5 16776.75,-25258.5 16776.75,-23038"/>
<polygon id="000002B6365DCA00" class="l275d0" points="16781.25,-23038 16781.25,-25258.5 16776.75,-25258.5 16776.75,-23038"/>
<polygon id="000002B6365DCA70" class="l275d0" points="16781.25,-23038 16781.25,-25258.5 16776.75,-25258.5 16776.75,-23038"/>
<polygon id="000002B6365DCD80" class="l275d0" points="16781.25,-23038 16781.25,-25258.5 16776.75,-25258.5 16776.75,-23038"/>
<polygon id="000002B6365DCDF0" class="l101d251" points="16821.25,-22998 16821.25,-25298.5 16736.75,-25298.5 16736.75,-22998"/>
<polygon id="000002B6361D9040" class="l101d251" points="16821.25,-22998 16821.25,-25298.5 16736.75,-25298.5 16736.75,-22998"/>
<polygon id="000002B6361D92E0" class="l101d251" points="16821.25,-22998 16821.25,-25298.5 16736.75,-25298.5 16736.75,-22998"/>
<polygon id="000002B6361D9740" class="l101d251" points="16821.25,-22998 16821.25,-25298.5 16736.75,-25298.5 16736.75,-22998"/>
<polygon id="000002B6361D9190" class="l101d251" points="16821.25,-22998 16821.25,-25298.5 16736.75,-25298.5 16736.75,-22998"/>
<polygon id="000002B6361D8D30" class="l101d251" points="16821.25,-22998 16821.25,-25298.5 16736.75,-25298.5 16736.75,-22998"/>
<polygon id="000002B6361D8B00" class="l275d0" points="16879,-22935.75 16879,-22940.25 16877.65,-22940.26 16874.96,-22940.33 16872.27,-22940.48 16869.59,-22940.7 16866.91,-22941 16864.24,-22941.37 16861.58,-22941.81 16858.94,-22942.33 16856.31,-22942.92 16853.7,-22943.58 16851.11,-22944.31 16848.54,-22945.11 16845.99,-22945.99 16843.47,-22946.93 16840.97,-22947.95 16838.5,-22949.03 16836.07,-22950.18 16833.66,-22951.4 16831.29,-22952.68 16828.96,-22954.02 16826.67,-22955.44 16824.41,-22956.91 16822.2,-22958.44 16820.03,-22960.04 16817.9,-22961.69 16815.82,-22963.41 16813.79,-22965.18 16811.81,-22967 16809.88,-22968.88 16808,-22970.81 16806.18,-22972.79 16804.41,-22974.82 16802.69,-22976.9 16801.04,-22979.03 16799.44,-22981.2 16797.91,-22983.41 16796.44,-22985.67 16795.02,-22987.96 16793.68,-22990.29 16792.4,-22992.66 16791.18,-22995.07 16790.03,-22997.5 16788.95,-22999.97 16787.93,-23002.47 16786.99,-23004.99 16786.11,-23007.54 16785.31,-23010.11 16784.58,-23012.7 16783.92,-23015.31 16783.33,-23017.94 16782.81,-23020.58 16782.37,-23023.24 16782,-23025.91 16781.7,-23028.59 16781.48,-23031.27 16781.33,-23033.96 16781.26,-23036.65 16781.25,-23038 16776.75,-23038 16776.76,-23036.59 16776.83,-23033.77 16776.99,-23030.96 16777.22,-23028.15 16777.53,-23025.35 16777.92,-23022.56 16778.38,-23019.78 16778.92,-23017.02 16779.54,-23014.27 16780.23,-23011.53 16781,-23008.82 16781.84,-23006.13 16782.75,-23003.47 16783.74,-23000.83 16784.8,-22998.22 16785.93,-22995.64 16787.14,-22993.09 16788.41,-22990.58 16789.75,-22988.1 16791.16,-22985.66 16792.63,-22983.26 16794.18,-22980.9 16795.78,-22978.58 16797.45,-22976.31 16799.18,-22974.09 16800.97,-22971.91 16802.82,-22969.79 16804.73,-22967.72 16806.7,-22965.7 16808.72,-22963.73 16810.79,-22961.82 16812.91,-22959.97 16815.09,-22958.18 16817.31,-22956.45 16819.58,-22954.78 16821.9,-22953.18 16824.26,-22951.63 16826.66,-22950.16 16829.1,-22948.75 16831.58,-22947.41 16834.09,-22946.14 16836.64,-22944.93 16839.22,-22943.8 16841.83,-22942.74 16844.47,-22941.75 16847.13,-22940.84 16849.82,-22940 16852.53,-22939.23 16855.27,-22938.54 16858.02,-22937.92 16860.78,-22937.38 16863.56,-22936.92 16866.35,-22936.53 16869.15,-22936.22 16871.96,-22935.99 16874.77,-22935.83 16877.59,-22935.76"/>
<polygon id="000002B6361D8B70" class="l275d0" points="16879,-22935.75 16879,-22940.25 16877.65,-22940.26 16874.96,-22940.33 16872.27,-22940.48 16869.59,-22940.7 16866.91,-22941 16864.24,-22941.37 16861.58,-22941.81 16858.94,-22942.33 16856.31,-22942.92 16853.7,-22943.58 16851.11,-22944.31 16848.54,-22945.11 16845.99,-22945.99 16843.47,-22946.93 16840.97,-22947.95 16838.5,-22949.03 16836.07,-22950.18 16833.66,-22951.4 16831.29,-22952.68 16828.96,-22954.02 16826.67,-22955.44 16824.41,-22956.91 16822.2,-22958.44 16820.03,-22960.04 16817.9,-22961.69 16815.82,-22963.41 16813.79,-22965.18 16811.81,-22967 16809.88,-22968.88 16808,-22970.81 16806.18,-22972.79 16804.41,-22974.82 16802.69,-22976.9 16801.04,-22979.03 16799.44,-22981.2 16797.91,-22983.41 16796.44,-22985.67 16795.02,-22987.96 16793.68,-22990.29 16792.4,-22992.66 16791.18,-22995.07 16790.03,-22997.5 16788.95,-22999.97 16787.93,-23002.47 16786.99,-23004.99 16786.11,-23007.54 16785.31,-23010.11 16784.58,-23012.7 16783.92,-23015.31 16783.33,-23017.94 16782.81,-23020.58 16782.37,-23023.24 16782,-23025.91 16781.7,-23028.59 16781.48,-23031.27 16781.33,-23033.96 16781.26,-23036.65 16781.25,-23038 16776.75,-23038 16776.76,-23036.59 16776.83,-23033.77 16776.99,-23030.96 16777.22,-23028.15 16777.53,-23025.35 16777.92,-23022.56 16778.38,-23019.78 16778.92,-23017.02 16779.54,-23014.27 16780.23,-23011.53 16781,-23008.82 16781.84,-23006.13 16782.75,-23003.47 16783.74,-23000.83 16784.8,-22998.22 16785.93,-22995.64 16787.14,-22993.09 16788.41,-22990.58 16789.75,-22988.1 16791.16,-22985.66 16792.63,-22983.26 16794.18,-22980.9 16795.78,-22978.58 16797.45,-22976.31 16799.18,-22974.09 16800.97,-22971.91 16802.82,-22969.79 16804.73,-22967.72 16806.7,-22965.7 16808.72,-22963.73 16810.79,-22961.82 16812.91,-22959.97 16815.09,-22958.18 16817.31,-22956.45 16819.58,-22954.78 16821.9,-22953.18 16824.26,-22951.63 16826.66,-22950.16 16829.1,-22948.75 16831.58,-22947.41 16834.09,-22946.14 16836.64,-22944.93 16839.22,-22943.8 16841.83,-22942.74 16844.47,-22941.75 16847.13,-22940.84 16849.82,-22940 16852.53,-22939.23 16855.27,-22938.54 16858.02,-22937.92 16860.78,-22937.38 16863.56,-22936.92 16866.35,-22936.53 16869.15,-22936.22 16871.96,-22935.99 16874.77,-22935.83 16877.59,-22935.76"/>
<polygon id="000002B6361D97B0" class="l275d0" points="16879,-22935.75 16879,-22940.25 16877.65,-22940.26 16874.96,-22940.33 16872.27,-22940.48 16869.59,-22940.7 16866.91,-22941 16864.24,-22941.37 16861.58,-22941.81 16858.94,-22942.33 16856.31,-22942.92 16853.7,-22943.58 16851.11,-22944.31 16848.54,-22945.11 16845.99,-22945.99 16843.47,-22946.93 16840.97,-22947.95 16838.5,-22949.03 16836.07,-22950.18 16833.66,-22951.4 16831.29,-22952.68 16828.96,-22954.02 16826.67,-22955.44 16824.41,-22956.91 16822.2,-22958.44 16820.03,-22960.04 16817.9,-22961.69 16815.82,-22963.41 16813.79,-22965.18 16811.81,-22967 16809.88,-22968.88 16808,-22970.81 16806.18,-22972.79 16804.41,-22974.82 16802.69,-22976.9 16801.04,-22979.03 16799.44,-22981.2 16797.91,-22983.41 16796.44,-22985.67 16795.02,-22987.96 16793.68,-22990.29 16792.4,-22992.66 16791.18,-22995.07 16790.03,-22997.5 16788.95,-22999.97 16787.93,-23002.47 16786.99,-23004.99 16786.11,-23007.54 16785.31,-23010.11 16784.58,-23012.7 16783.92,-23015.31 16783.33,-23017.94 16782.81,-23020.58 16782.37,-23023.24 16782,-23025.91 16781.7,-23028.59 16781.48,-23031.27 16781.33,-23033.96 16781.26,-23036.65 16781.25,-23038 16776.75,-23038 16776.76,-23036.59 16776.83,-23033.77 16776.99,-23030.96 16777.22,-23028.15 16777.53,-23025.35 16777.92,-23022.56 16778.38,-23019.78 16778.92,-23017.02 16779.54,-23014.27 16780.23,-23011.53 16781,-23008.82 16781.84,-23006.13 16782.75,-23003.47 16783.74,-23000.83 16784.8,-22998.22 16785.93,-22995.64 16787.14,-22993.09 16788.41,-22990.58 16789.75,-22988.1 16791.16,-22985.66 16792.63,-22983.26 16794.18,-22980.9 16795.78,-22978.58 16797.45,-22976.31 16799.18,-22974.09 16800.97,-22971.91 16802.82,-22969.79 16804.73,-22967.72 16806.7,-22965.7 16808.72,-22963.73 16810.79,-22961.82 16812.91,-22959.97 16815.09,-22958.18 16817.31,-22956.45 16819.58,-22954.78 16821.9,-22953.18 16824.26,-22951.63 16826.66,-22950.16 16829.1,-22948.75 16831.58,-22947.41 16834.09,-22946.14 16836.64,-22944.93 16839.22,-22943.8 16841.83,-22942.74 16844.47,-22941.75 16847.13,-22940.84 16849.82,-22940 16852.53,-22939.23 16855.27,-22938.54 16858.02,-22937.92 16860.78,-22937.38 16863.56,-22936.92 16866.35,-22936.53 16869.15,-22936.22 16871.96,-22935.99 16874.77,-22935.83 16877.59,-22935.76"/>
<polygon id="000002B6361D8BE0" class="l275d0" points="16879,-22935.75 16879,-22940.25 16877.65,-22940.26 16874.96,-22940.33 16872.27,-22940.48 16869.59,-22940.7 16866.91,-22941 16864.24,-22941.37 16861.58,-22941.81 16858.94,-22942.33 16856.31,-22942.92 16853.7,-22943.58 16851.11,-22944.31 16848.54,-22945.11 16845.99,-22945.99 16843.47,-22946.93 16840.97,-22947.95 16838.5,-22949.03 16836.07,-22950.18 16833.66,-22951.4 16831.29,-22952.68 16828.96,-22954.02 16826.67,-22955.44 16824.41,-22956.91 16822.2,-22958.44 16820.03,-22960.04 16817.9,-22961.69 16815.82,-22963.41 16813.79,-22965.18 16811.81,-22967 16809.88,-22968.88 16808,-22970.81 16806.18,-22972.79 16804.41,-22974.82 16802.69,-22976.9 16801.04,-22979.03 16799.44,-22981.2 16797.91,-22983.41 16796.44,-22985.67 16795.02,-22987.96 16793.68,-22990.29 16792.4,-22992.66 16791.18,-22995.07 16790.03,-22997.5 16788.95,-22999.97 16787.93,-23002.47 16786.99,-23004.99 16786.11,-23007.54 16785.31,-23010.11 16784.58,-23012.7 16783.92,-23015.31 16783.33,-23017.94 16782.81,-23020.58 16782.37,-23023.24 16782,-23025.91 16781.7,-23028.59 16781.48,-23031.27 16781.33,-23033.96 16781.26,-23036.65 16781.25,-23038 16776.75,-23038 16776.76,-23036.59 16776.83,-23033.77 16776.99,-23030.96 16777.22,-23028.15 16777.53,-23025.35 16777.92,-23022.56 16778.38,-23019.78 16778.92,-23017.02 16779.54,-23014.27 16780.23,-23011.53 16781,-23008.82 16781.84,-23006.13 16782.75,-23003.47 16783.74,-23000.83 16784.8,-22998.22 16785.93,-22995.64 16787.14,-22993.09 16788.41,-22990.58 16789.75,-22988.1 16791.16,-22985.66 16792.63,-22983.26 16794.18,-22980.9 16795.78,-22978.58 16797.45,-22976.31 16799.18,-22974.09 16800.97,-22971.91 16802.82,-22969.79 16804.73,-22967.72 16806.7,-22965.7 16808.72,-22963.73 16810.79,-22961.82 16812.91,-22959.97 16815.09,-22958.18 16817.31,-22956.45 16819.58,-22954.78 16821.9,-22953.18 16824.26,-22951.63 16826.66,-22950.16 16829.1,-22948.75 16831.58,-22947.41 16834.09,-22946.14 16836.64,-22944.93 16839.22,-22943.8 16841.83,-22942.74 16844.47,-22941.75 16847.13,-22940.84 16849.82,-22940 16852.53,-22939.23 16855.27,-22938.54 16858.02,-22937.92 16860.78,-22937.38 16863.56,-22936.92 16866.35,-22936.53 16869.15,-22936.22 16871.96,-22935.99 16874.77,-22935.83 16877.59,-22935.76"/>
<polygon id="000002B6361D9820" class="l275d0" points="16879,-22935.75 16879,-22940.25 16877.65,-22940.26 16874.96,-22940.33 16872.27,-22940.48 16869.59,-22940.7 16866.91,-22941 16864.24,-22941.37 16861.58,-22941.81 16858.94,-22942.33 16856.31,-22942.92 16853.7,-22943.58 16851.11,-22944.31 16848.54,-22945.11 16845.99,-22945.99 16843.47,-22946.93 16840.97,-22947.95 16838.5,-22949.03 16836.07,-22950.18 16833.66,-22951.4 16831.29,-22952.68 16828.96,-22954.02 16826.67,-22955.44 16824.41,-22956.91 16822.2,-22958.44 16820.03,-22960.04 16817.9,-22961.69 16815.82,-22963.41 16813.79,-22965.18 16811.81,-22967 16809.88,-22968.88 16808,-22970.81 16806.18,-22972.79 16804.41,-22974.82 16802.69,-22976.9 16801.04,-22979.03 16799.44,-22981.2 16797.91,-22983.41 16796.44,-22985.67 16795.02,-22987.96 16793.68,-22990.29 16792.4,-22992.66 16791.18,-22995.07 16790.03,-22997.5 16788.95,-22999.97 16787.93,-23002.47 16786.99,-23004.99 16786.11,-23007.54 16785.31,-23010.11 16784.58,-23012.7 16783.92,-23015.31 16783.33,-23017.94 16782.81,-23020.58 16782.37,-23023.24 16782,-23025.91 16781.7,-23028.59 16781.48,-23031.27 16781.33,-23033.96 16781.26,-23036.65 16781.25,-23038 16776.75,-23038 16776.76,-23036.59 16776.83,-23033.77 16776.99,-23030.96 16777.22,-23028.15 16777.53,-23025.35 16777.92,-23022.56 16778.38,-23019.78 16778.92,-23017.02 16779.54,-23014.27 16780.23,-23011.53 16781,-23008.82 16781.84,-23006.13 16782.75,-23003.47 16783.74,-23000.83 16784.8,-22998.22 16785.93,-22995.64 16787.14,-22993.09 16788.41,-22990.58 16789.75,-22988.1 16791.16,-22985.66 16792.63,-22983.26 16794.18,-22980.9 16795.78,-22978.58 16797.45,-22976.31 16799.18,-22974.09 16800.97,-22971.91 16802.82,-22969.79 16804.73,-22967.72 16806.7,-22965.7 16808.72,-22963.73 16810.79,-22961.82 16812.91,-22959.97 16815.09,-22958.18 16817.31,-22956.45 16819.58,-22954.78 16821.9,-22953.18 16824.26,-22951.63 16826.66,-22950.16 16829.1,-22948.75 16831.58,-22947.41 16834.09,-22946.14 16836.64,-22944.93 16839.22,-22943.8 16841.83,-22942.74 16844.47,-22941.75 16847.13,-22940.84 16849.82,-22940 16852.53,-22939.23 16855.27,-22938.54 16858.02,-22937.92 16860.78,-22937.38 16863.56,-22936.92 16866.35,-22936.53 16869.15,-22936.22 16871.96,-22935.99 16874.77,-22935.83 16877.59,-22935.76"/>
<polygon id="000002B6361D8C50" class="l275d0" points="16879,-22935.75 16879,-22940.25 16877.65,-22940.26 16874.96,-22940.33 16872.27,-22940.48 16869.59,-22940.7 16866.91,-22941 16864.24,-22941.37 16861.58,-22941.81 16858.94,-22942.33 16856.31,-22942.92 16853.7,-22943.58 16851.11,-22944.31 16848.54,-22945.11 16845.99,-22945.99 16843.47,-22946.93 16840.97,-22947.95 16838.5,-22949.03 16836.07,-22950.18 16833.66,-22951.4 16831.29,-22952.68 16828.96,-22954.02 16826.67,-22955.44 16824.41,-22956.91 16822.2,-22958.44 16820.03,-22960.04 16817.9,-22961.69 16815.82,-22963.41 16813.79,-22965.18 16811.81,-22967 16809.88,-22968.88 16808,-22970.81 16806.18,-22972.79 16804.41,-22974.82 16802.69,-22976.9 16801.04,-22979.03 16799.44,-22981.2 16797.91,-22983.41 16796.44,-22985.67 16795.02,-22987.96 16793.68,-22990.29 16792.4,-22992.66 16791.18,-22995.07 16790.03,-22997.5 16788.95,-22999.97 16787.93,-23002.47 16786.99,-23004.99 16786.11,-23007.54 16785.31,-23010.11 16784.58,-23012.7 16783.92,-23015.31 16783.33,-23017.94 16782.81,-23020.58 16782.37,-23023.24 16782,-23025.91 16781.7,-23028.59 16781.48,-23031.27 16781.33,-23033.96 16781.26,-23036.65 16781.25,-23038 16776.75,-23038 16776.76,-23036.59 16776.83,-23033.77 16776.99,-23030.96 16777.22,-23028.15 16777.53,-23025.35 16777.92,-23022.56 16778.38,-23019.78 16778.92,-23017.02 16779.54,-23014.27 16780.23,-23011.53 16781,-23008.82 16781.84,-23006.13 16782.75,-23003.47 16783.74,-23000.83 16784.8,-22998.22 16785.93,-22995.64 16787.14,-22993.09 16788.41,-22990.58 16789.75,-22988.1 16791.16,-22985.66 16792.63,-22983.26 16794.18,-22980.9 16795.78,-22978.58 16797.45,-22976.31 16799.18,-22974.09 16800.97,-22971.91 16802.82,-22969.79 16804.73,-22967.72 16806.7,-22965.7 16808.72,-22963.73 16810.79,-22961.82 16812.91,-22959.97 16815.09,-22958.18 16817.31,-22956.45 16819.58,-22954.78 16821.9,-22953.18 16824.26,-22951.63 16826.66,-22950.16 16829.1,-22948.75 16831.58,-22947.41 16834.09,-22946.14 16836.64,-22944.93 16839.22,-22943.8 16841.83,-22942.74 16844.47,-22941.75 16847.13,-22940.84 16849.82,-22940 16852.53,-22939.23 16855.27,-22938.54 16858.02,-22937.92 16860.78,-22937.38 16863.56,-22936.92 16866.35,-22936.53 16869.15,-22936.22 16871.96,-22935.99 16874.77,-22935.83 16877.59,-22935.76"/>
<polygon id="000002B6361D9350" class="l101d251" points="16879,-22895.75 16879,-22980.25 16878.32,-22980.25 16876.97,-22980.28 16875.62,-22980.35 16874.27,-22980.44 16872.92,-22980.57 16871.57,-22980.73 16870.23,-22980.92 16868.9,-22981.14 16867.57,-22981.39 16866.24,-22981.68 16864.93,-22981.99 16863.62,-22982.34 16862.32,-22982.71 16861.03,-22983.12 16859.74,-22983.55 16858.47,-22984.02 16857.21,-22984.52 16855.96,-22985.04 16854.73,-22985.6 16853.51,-22986.18 16852.3,-22986.79 16851.11,-22987.43 16849.93,-22988.1 16848.77,-22988.79 16847.62,-22989.52 16846.49,-22990.27 16845.38,-22991.04 16844.29,-22991.84 16843.22,-22992.67 16842.17,-22993.52 16841.13,-22994.39 16840.12,-22995.29 16839.13,-22996.22 16838.16,-22997.16 16837.22,-22998.13 16836.29,-22999.12 16835.39,-23000.13 16834.52,-23001.17 16833.67,-23002.22 16832.84,-23003.29 16832.04,-23004.38 16831.27,-23005.49 16830.52,-23006.62 16829.79,-23007.77 16829.1,-23008.93 16828.43,-23010.11 16827.79,-23011.3 16827.18,-23012.51 16826.6,-23013.73 16826.04,-23014.96 16825.52,-23016.21 16825.02,-23017.47 16824.55,-23018.74 16824.12,-23020.03 16823.71,-23021.32 16823.34,-23022.62 16822.99,-23023.93 16822.68,-23025.24 16822.39,-23026.57 16822.14,-23027.9 16821.92,-23029.23 16821.73,-23030.57 16821.57,-23031.92 16821.44,-23033.27 16821.35,-23034.62 16821.28,-23035.97 16821.25,-23037.32 16821.25,-23038 16736.75,-23038 16736.76,-23036.33 16736.83,-23033 16736.99,-23029.67 16737.23,-23026.34 16737.54,-23023.02 16737.93,-23019.71 16738.4,-23016.41 16738.94,-23013.12 16739.56,-23009.84 16740.26,-23006.58 16741.04,-23003.33 16741.89,-23000.11 16742.81,-22996.91 16743.81,-22993.72 16744.89,-22990.57 16746.04,-22987.44 16747.26,-22984.33 16748.55,-22981.26 16749.92,-22978.22 16751.36,-22975.21 16752.86,-22972.23 16754.44,-22969.29 16756.08,-22966.39 16757.8,-22963.53 16759.58,-22960.71 16761.42,-22957.93 16763.33,-22955.2 16765.3,-22952.51 16767.34,-22949.87 16769.43,-22947.27 16771.59,-22944.73 16773.81,-22942.24 16776.08,-22939.8 16778.41,-22937.41 16780.8,-22935.08 16783.24,-22932.81 16785.73,-22930.59 16788.27,-22928.43 16790.87,-22926.34 16793.51,-22924.3 16796.2,-22922.33 16798.93,-22920.42 16801.71,-22918.58 16804.53,-22916.8 16807.39,-22915.08 16810.29,-22913.44 16813.23,-22911.86 16816.21,-22910.36 16819.22,-22908.92 16822.26,-22907.55 16825.33,-22906.26 16828.44,-22905.04 16831.57,-22903.89 16834.72,-22902.81 16837.91,-22901.81 16841.11,-22900.89 16844.33,-22900.04 16847.58,-22899.26 16850.84,-22898.56 16854.12,-22897.94 16857.41,-22897.4 16860.71,-22896.93 16864.02,-22896.54 16867.34,-22896.23 16870.67,-22895.99 16874,-22895.83 16877.33,-22895.76"/>
<polygon id="000002B6361D8A20" class="l101d251" points="16879,-22895.75 16879,-22980.25 16878.32,-22980.25 16876.97,-22980.28 16875.62,-22980.35 16874.27,-22980.44 16872.92,-22980.57 16871.57,-22980.73 16870.23,-22980.92 16868.9,-22981.14 16867.57,-22981.39 16866.24,-22981.68 16864.93,-22981.99 16863.62,-22982.34 16862.32,-22982.71 16861.03,-22983.12 16859.74,-22983.55 16858.47,-22984.02 16857.21,-22984.52 16855.96,-22985.04 16854.73,-22985.6 16853.51,-22986.18 16852.3,-22986.79 16851.11,-22987.43 16849.93,-22988.1 16848.77,-22988.79 16847.62,-22989.52 16846.49,-22990.27 16845.38,-22991.04 16844.29,-22991.84 16843.22,-22992.67 16842.17,-22993.52 16841.13,-22994.39 16840.12,-22995.29 16839.13,-22996.22 16838.16,-22997.16 16837.22,-22998.13 16836.29,-22999.12 16835.39,-23000.13 16834.52,-23001.17 16833.67,-23002.22 16832.84,-23003.29 16832.04,-23004.38 16831.27,-23005.49 16830.52,-23006.62 16829.79,-23007.77 16829.1,-23008.93 16828.43,-23010.11 16827.79,-23011.3 16827.18,-23012.51 16826.6,-23013.73 16826.04,-23014.96 16825.52,-23016.21 16825.02,-23017.47 16824.55,-23018.74 16824.12,-23020.03 16823.71,-23021.32 16823.34,-23022.62 16822.99,-23023.93 16822.68,-23025.24 16822.39,-23026.57 16822.14,-23027.9 16821.92,-23029.23 16821.73,-23030.57 16821.57,-23031.92 16821.44,-23033.27 16821.35,-23034.62 16821.28,-23035.97 16821.25,-23037.32 16821.25,-23038 16736.75,-23038 16736.76,-23036.33 16736.83,-23033 16736.99,-23029.67 16737.23,-23026.34 16737.54,-23023.02 16737.93,-23019.71 16738.4,-23016.41 16738.94,-23013.12 16739.56,-23009.84 16740.26,-23006.58 16741.04,-23003.33 16741.89,-23000.11 16742.81,-22996.91 16743.81,-22993.72 16744.89,-22990.57 16746.04,-22987.44 16747.26,-22984.33 16748.55,-22981.26 16749.92,-22978.22 16751.36,-22975.21 16752.86,-22972.23 16754.44,-22969.29 16756.08,-22966.39 16757.8,-22963.53 16759.58,-22960.71 16761.42,-22957.93 16763.33,-22955.2 16765.3,-22952.51 16767.34,-22949.87 16769.43,-22947.27 16771.59,-22944.73 16773.81,-22942.24 16776.08,-22939.8 16778.41,-22937.41 16780.8,-22935.08 16783.24,-22932.81 16785.73,-22930.59 16788.27,-22928.43 16790.87,-22926.34 16793.51,-22924.3 16796.2,-22922.33 16798.93,-22920.42 16801.71,-22918.58 16804.53,-22916.8 16807.39,-22915.08 16810.29,-22913.44 16813.23,-22911.86 16816.21,-22910.36 16819.22,-22908.92 16822.26,-22907.55 16825.33,-22906.26 16828.44,-22905.04 16831.57,-22903.89 16834.72,-22902.81 16837.91,-22901.81 16841.11,-22900.89 16844.33,-22900.04 16847.58,-22899.26 16850.84,-22898.56 16854.12,-22897.94 16857.41,-22897.4 16860.71,-22896.93 16864.02,-22896.54 16867.34,-22896.23 16870.67,-22895.99 16874,-22895.83 16877.33,-22895.76"/>
<polygon id="000002B6361D9660" class="l101d251" points="16879,-22895.75 16879,-22980.25 16878.32,-22980.25 16876.97,-22980.28 16875.62,-22980.35 16874.27,-22980.44 16872.92,-22980.57 16871.57,-22980.73 16870.23,-22980.92 16868.9,-22981.14 16867.57,-22981.39 16866.24,-22981.68 16864.93,-22981.99 16863.62,-22982.34 16862.32,-22982.71 16861.03,-22983.12 16859.74,-22983.55 16858.47,-22984.02 16857.21,-22984.52 16855.96,-22985.04 16854.73,-22985.6 16853.51,-22986.18 16852.3,-22986.79 16851.11,-22987.43 16849.93,-22988.1 16848.77,-22988.79 16847.62,-22989.52 16846.49,-22990.27 16845.38,-22991.04 16844.29,-22991.84 16843.22,-22992.67 16842.17,-22993.52 16841.13,-22994.39 16840.12,-22995.29 16839.13,-22996.22 16838.16,-22997.16 16837.22,-22998.13 16836.29,-22999.12 16835.39,-23000.13 16834.52,-23001.17 16833.67,-23002.22 16832.84,-23003.29 16832.04,-23004.38 16831.27,-23005.49 16830.52,-23006.62 16829.79,-23007.77 16829.1,-23008.93 16828.43,-23010.11 16827.79,-23011.3 16827.18,-23012.51 16826.6,-23013.73 16826.04,-23014.96 16825.52,-23016.21 16825.02,-23017.47 16824.55,-23018.74 16824.12,-23020.03 16823.71,-23021.32 16823.34,-23022.62 16822.99,-23023.93 16822.68,-23025.24 16822.39,-23026.57 16822.14,-23027.9 16821.92,-23029.23 16821.73,-23030.57 16821.57,-23031.92 16821.44,-23033.27 16821.35,-23034.62 16821.28,-23035.97 16821.25,-23037.32 16821.25,-23038 16736.75,-23038 16736.76,-23036.33 16736.83,-23033 16736.99,-23029.67 16737.23,-23026.34 16737.54,-23023.02 16737.93,-23019.71 16738.4,-23016.41 16738.94,-23013.12 16739.56,-23009.84 16740.26,-23006.58 16741.04,-23003.33 16741.89,-23000.11 16742.81,-22996.91 16743.81,-22993.72 16744.89,-22990.57 16746.04,-22987.44 16747.26,-22984.33 16748.55,-22981.26 16749.92,-22978.22 16751.36,-22975.21 16752.86,-22972.23 16754.44,-22969.29 16756.08,-22966.39 16757.8,-22963.53 16759.58,-22960.71 16761.42,-22957.93 16763.33,-22955.2 16765.3,-22952.51 16767.34,-22949.87 16769.43,-22947.27 16771.59,-22944.73 16773.81,-22942.24 16776.08,-22939.8 16778.41,-22937.41 16780.8,-22935.08 16783.24,-22932.81 16785.73,-22930.59 16788.27,-22928.43 16790.87,-22926.34 16793.51,-22924.3 16796.2,-22922.33 16798.93,-22920.42 16801.71,-22918.58 16804.53,-22916.8 16807.39,-22915.08 16810.29,-22913.44 16813.23,-22911.86 16816.21,-22910.36 16819.22,-22908.92 16822.26,-22907.55 16825.33,-22906.26 16828.44,-22905.04 16831.57,-22903.89 16834.72,-22902.81 16837.91,-22901.81 16841.11,-22900.89 16844.33,-22900.04 16847.58,-22899.26 16850.84,-22898.56 16854.12,-22897.94 16857.41,-22897.4 16860.71,-22896.93 16864.02,-22896.54 16867.34,-22896.23 16870.67,-22895.99 16874,-22895.83 16877.33,-22895.76"/>
<polygon id="000002B6361D90B0" class="l101d251" points="16879,-22895.75 16879,-22980.25 16878.32,-22980.25 16876.97,-22980.28 16875.62,-22980.35 16874.27,-22980.44 16872.92,-22980.57 16871.57,-22980.73 16870.23,-22980.92 16868.9,-22981.14 16867.57,-22981.39 16866.24,-22981.68 16864.93,-22981.99 16863.62,-22982.34 16862.32,-22982.71 16861.03,-22983.12 16859.74,-22983.55 16858.47,-22984.02 16857.21,-22984.52 16855.96,-22985.04 16854.73,-22985.6 16853.51,-22986.18 16852.3,-22986.79 16851.11,-22987.43 16849.93,-22988.1 16848.77,-22988.79 16847.62,-22989.52 16846.49,-22990.27 16845.38,-22991.04 16844.29,-22991.84 16843.22,-22992.67 16842.17,-22993.52 16841.13,-22994.39 16840.12,-22995.29 16839.13,-22996.22 16838.16,-22997.16 16837.22,-22998.13 16836.29,-22999.12 16835.39,-23000.13 16834.52,-23001.17 16833.67,-23002.22 16832.84,-23003.29 16832.04,-23004.38 16831.27,-23005.49 16830.52,-23006.62 16829.79,-23007.77 16829.1,-23008.93 16828.43,-23010.11 16827.79,-23011.3 16827.18,-23012.51 16826.6,-23013.73 16826.04,-23014.96 16825.52,-23016.21 16825.02,-23017.47 16824.55,-23018.74 16824.12,-23020.03 16823.71,-23021.32 16823.34,-23022.62 16822.99,-23023.93 16822.68,-23025.24 16822.39,-23026.57 16822.14,-23027.9 16821.92,-23029.23 16821.73,-23030.57 16821.57,-23031.92 16821.44,-23033.27 16821.35,-23034.62 16821.28,-23035.97 16821.25,-23037.32 16821.25,-23038 16736.75,-23038 16736.76,-23036.33 16736.83,-23033 16736.99,-23029.67 16737.23,-23026.34 16737.54,-23023.02 16737.93,-23019.71 16738.4,-23016.41 16738.94,-23013.12 16739.56,-23009.84 16740.26,-23006.58 16741.04,-23003.33 16741.89,-23000.11 16742.81,-22996.91 16743.81,-22993.72 16744.89,-22990.57 16746.04,-22987.44 16747.26,-22984.33 16748.55,-22981.26 16749.92,-22978.22 16751.36,-22975.21 16752.86,-22972.23 16754.44,-22969.29 16756.08,-22966.39 16757.8,-22963.53 16759.58,-22960.71 16761.42,-22957.93 16763.33,-22955.2 16765.3,-22952.51 16767.34,-22949.87 16769.43,-22947.27 16771.59,-22944.73 16773.81,-22942.24 16776.08,-22939.8 16778.41,-22937.41 16780.8,-22935.08 16783.24,-22932.81 16785.73,-22930.59 16788.27,-22928.43 16790.87,-22926.34 16793.51,-22924.3 16796.2,-22922.33 16798.93,-22920.42 16801.71,-22918.58 16804.53,-22916.8 16807.39,-22915.08 16810.29,-22913.44 16813.23,-22911.86 16816.21,-22910.36 16819.22,-22908.92 16822.26,-22907.55 16825.33,-22906.26 16828.44,-22905.04 16831.57,-22903.89 16834.72,-22902.81 16837.91,-22901.81 16841.11,-22900.89 16844.33,-22900.04 16847.58,-22899.26 16850.84,-22898.56 16854.12,-22897.94 16857.41,-22897.4 16860.71,-22896.93 16864.02,-22896.54 16867.34,-22896.23 16870.67,-22895.99 16874,-22895.83 16877.33,-22895.76"/>
<polygon id="000002B6361D9890" class="l101d251" points="16879,-22895.75 16879,-22980.25 16878.32,-22980.25 16876.97,-22980.28 16875.62,-22980.35 16874.27,-22980.44 16872.92,-22980.57 16871.57,-22980.73 16870.23,-22980.92 16868.9,-22981.14 16867.57,-22981.39 16866.24,-22981.68 16864.93,-22981.99 16863.62,-22982.34 16862.32,-22982.71 16861.03,-22983.12 16859.74,-22983.55 16858.47,-22984.02 16857.21,-22984.52 16855.96,-22985.04 16854.73,-22985.6 16853.51,-22986.18 16852.3,-22986.79 16851.11,-22987.43 16849.93,-22988.1 16848.77,-22988.79 16847.62,-22989.52 16846.49,-22990.27 16845.38,-22991.04 16844.29,-22991.84 16843.22,-22992.67 16842.17,-22993.52 16841.13,-22994.39 16840.12,-22995.29 16839.13,-22996.22 16838.16,-22997.16 16837.22,-22998.13 16836.29,-22999.12 16835.39,-23000.13 16834.52,-23001.17 16833.67,-23002.22 16832.84,-23003.29 16832.04,-23004.38 16831.27,-23005.49 16830.52,-23006.62 16829.79,-23007.77 16829.1,-23008.93 16828.43,-23010.11 16827.79,-23011.3 16827.18,-23012.51 16826.6,-23013.73 16826.04,-23014.96 16825.52,-23016.21 16825.02,-23017.47 16824.55,-23018.74 16824.12,-23020.03 16823.71,-23021.32 16823.34,-23022.62 16822.99,-23023.93 16822.68,-23025.24 16822.39,-23026.57 16822.14,-23027.9 16821.92,-23029.23 16821.73,-23030.57 16821.57,-23031.92 16821.44,-23033.27 16821.35,-23034.62 16821.28,-23035.97 16821.25,-23037.32 16821.25,-23038 16736.75,-23038 16736.76,-23036.33 16736.83,-23033 16736.99,-23029.67 16737.23,-23026.34 16737.54,-23023.02 16737.93,-23019.71 16738.4,-23016.41 16738.94,-23013.12 16739.56,-23009.84 16740.26,-23006.58 16741.04,-23003.33 16741.89,-23000.11 16742.81,-22996.91 16743.81,-22993.72 16744.89,-22990.57 16746.04,-22987.44 16747.26,-22984.33 16748.55,-22981.26 16749.92,-22978.22 16751.36,-22975.21 16752.86,-22972.23 16754.44,-22969.29 16756.08,-22966.39 16757.8,-22963.53 16759.58,-22960.71 16761.42,-22957.93 16763.33,-22955.2 16765.3,-22952.51 16767.34,-22949.87 16769.43,-22947.27 16771.59,-22944.73 16773.81,-22942.24 16776.08,-22939.8 16778.41,-22937.41 16780.8,-22935.08 16783.24,-22932.81 16785.73,-22930.59 16788.27,-22928.43 16790.87,-22926.34 16793.51,-22924.3 16796.2,-22922.33 16798.93,-22920.42 16801.71,-22918.58 16804.53,-22916.8 16807.39,-22915.08 16810.29,-22913.44 16813.23,-22911.86 16816.21,-22910.36 16819.22,-22908.92 16822.26,-22907.55 16825.33,-22906.26 16828.44,-22905.04 16831.57,-22903.89 16834.72,-22902.81 16837.91,-22901.81 16841.11,-22900.89 16844.33,-22900.04 16847.58,-22899.26 16850.84,-22898.56 16854.12,-22897.94 16857.41,-22897.4 16860.71,-22896.93 16864.02,-22896.54 16867.34,-22896.23 16870.67,-22895.99 16874,-22895.83 16877.33,-22895.76"/>
<polygon id="000002B6361D9270" class="l101d251" points="16879,-22895.75 16879,-22980.25 16878.32,-22980.25 16876.97,-22980.28 16875.62,-22980.35 16874.27,-22980.44 16872.92,-22980.57 16871.57,-22980.73 16870.23,-22980.92 16868.9,-22981.14 16867.57,-22981.39 16866.24,-22981.68 16864.93,-22981.99 16863.62,-22982.34 16862.32,-22982.71 16861.03,-22983.12 16859.74,-22983.55 16858.47,-22984.02 16857.21,-22984.52 16855.96,-22985.04 16854.73,-22985.6 16853.51,-22986.18 16852.3,-22986.79 16851.11,-22987.43 16849.93,-22988.1 16848.77,-22988.79 16847.62,-22989.52 16846.49,-22990.27 16845.38,-22991.04 16844.29,-22991.84 16843.22,-22992.67 16842.17,-22993.52 16841.13,-22994.39 16840.12,-22995.29 16839.13,-22996.22 16838.16,-22997.16 16837.22,-22998.13 16836.29,-22999.12 16835.39,-23000.13 16834.52,-23001.17 16833.67,-23002.22 16832.84,-23003.29 16832.04,-23004.38 16831.27,-23005.49 16830.52,-23006.62 16829.79,-23007.77 16829.1,-23008.93 16828.43,-23010.11 16827.79,-23011.3 16827.18,-23012.51 16826.6,-23013.73 16826.04,-23014.96 16825.52,-23016.21 16825.02,-23017.47 16824.55,-23018.74 16824.12,-23020.03 16823.71,-23021.32 16823.34,-23022.62 16822.99,-23023.93 16822.68,-23025.24 16822.39,-23026.57 16822.14,-23027.9 16821.92,-23029.23 16821.73,-23030.57 16821.57,-23031.92 16821.44,-23033.27 16821.35,-23034.62 16821.28,-23035.97 16821.25,-23037.32 16821.25,-23038 16736.75,-23038 16736.76,-23036.33 16736.83,-23033 16736.99,-23029.67 16737.23,-23026.34 16737.54,-23023.02 16737.93,-23019.71 16738.4,-23016.41 16738.94,-23013.12 16739.56,-23009.84 16740.26,-23006.58 16741.04,-23003.33 16741.89,-23000.11 16742.81,-22996.91 16743.81,-22993.72 16744.89,-22990.57 16746.04,-22987.44 16747.26,-22984.33 16748.55,-22981.26 16749.92,-22978.22 16751.36,-22975.21 16752.86,-22972.23 16754.44,-22969.29 16756.08,-22966.39 16757.8,-22963.53 16759.58,-22960.71 16761.42,-22957.93 16763.33,-22955.2 16765.3,-22952.51 16767.34,-22949.87 16769.43,-22947.27 16771.59,-22944.73 16773.81,-22942.24 16776.08,-22939.8 16778.41,-22937.41 16780.8,-22935.08 16783.24,-22932.81 16785.73,-22930.59 16788.27,-22928.43 16790.87,-22926.34 16793.51,-22924.3 16796.2,-22922.33 16798.93,-22920.42 16801.71,-22918.58 16804.53,-22916.8 16807.39,-22915.08 16810.29,-22913.44 16813.23,-22911.86 16816.21,-22910.36 16819.22,-22908.92 16822.26,-22907.55 16825.33,-22906.26 16828.44,-22905.04 16831.57,-22903.89 16834.72,-22902.81 16837.91,-22901.81 16841.11,-22900.89 16844.33,-22900.04 16847.58,-22899.26 16850.84,-22898.56 16854.12,-22897.94 16857.41,-22897.4 16860.71,-22896.93 16864.02,-22896.54 16867.34,-22896.23 16870.67,-22895.99 16874,-22895.83 16877.33,-22895.76"/>
<use transform="translate(15115 -25315)" xlink:href="#1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2"/>
<use transform="translate(17164 -22938)" xlink:href="#1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2_405"/>
=======
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18291.35" height="7473" viewBox="12581.075 16793.425 18291.35 7473"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="18291.35" height="7473" viewBox="12581.075 16793.425 18291.35 7473">
<defs> <defs>
<style type="text/css"> <style type="text/css">
@@ -153,6 +67,5 @@
<use transform="translate(13416 -21038)" xlink:href="#MZI_SiN400_Si220_PIN_mod_1310_L1300_QY_202603"/> <use transform="translate(13416 -21038)" xlink:href="#MZI_SiN400_Si220_PIN_mod_1310_L1300_QY_202603"/>
<use transform="translate(26950 -22750) rotate(90)" xlink:href="#HT_150R_SiPPP_L500_100OHM_DUMMY_QY_202604"/> <use transform="translate(26950 -22750) rotate(90)" xlink:href="#HT_150R_SiPPP_L500_100OHM_DUMMY_QY_202604"/>
<use transform="translate(28490 -19886)" xlink:href="#1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2"/> <use transform="translate(28490 -19886)" xlink:href="#1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2"/>
>>>>>>> jingwen_main
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@@ -13,16 +13,6 @@ type: project
version: "1.0.0" version: "1.0.0"
# 1. External Ports (How this cell connects to the outside world) # 1. External Ports (How this cell connects to the outside world)
<<<<<<< HEAD
ports: []
# 2. Instances (The sub-components dropped onto this canvas)
instances:
MMI_1:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 1511.5
y: -2531.5
=======
pins: pins:
- name: port_1_io1 - name: port_1_io1
layer: WG_CORE layer: WG_CORE
@@ -96,7 +86,6 @@ instances:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 2849.0 x: 2849.0
y: -1988.6 y: -1988.6
>>>>>>> jingwen_main
rotation: 0.0 rotation: 0.0
flip: 0 flip: 0
flop: 0 flop: 0
@@ -104,17 +93,10 @@ instances:
settings: settings:
length: length:
<<<<<<< HEAD
MMI_2:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 1716.4
y: -2293.8
=======
DC_2: DC_2:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/directional_couplers/DC_SiN400_99_1_1310_jyh_quantex_202603 component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/directional_couplers/DC_SiN400_99_1_1310_jyh_quantex_202603
x: 2656.9 x: 2656.9
y: -1992.8 y: -1992.8
>>>>>>> jingwen_main
rotation: 0.0 rotation: 0.0
flip: 0 flip: 0
flop: 0 flop: 0
@@ -122,9 +104,6 @@ instances:
settings: settings:
length: length:
<<<<<<< HEAD
elements: {}
=======
PD_1: PD_1:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/photodetectors/PD_1310_Monitor_Si220_Ge500_NPN_XHN_202604 component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/photodetectors/PD_1310_Monitor_Si220_Ge500_NPN_XHN_202604
x: 3151.7 x: 3151.7
@@ -150,20 +129,9 @@ elements:
pins: pins:
- name: port_1_io1 - name: port_1_io1
role: io1 role: io1
>>>>>>> jingwen_main
# 3. Bundles (Grouped links for multi-bus/parallel routing) # 3. Bundles (Grouped links for multi-bus/parallel routing)
bundles: bundles:
output_bus: output_bus:
routing_type: euler_bend routing_type: euler_bend
links: links:
<<<<<<< HEAD
- from: MMI_2:a1
to: MMI_1:b2
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
=======
>>>>>>> jingwen_main
Binary file not shown.
+17
View File
@@ -1237,6 +1237,22 @@ ${linksYaml}`;
return null; return null;
}; };
const getRotatableNodeHandleDirection = (node, handleId) => {
if (!node || !handleId) return null;
if (node.type !== 'rotatableNode' && !(!node.data?.elementType && node.data?.componentName)) return null;
const ports = node.data && node.data.ports;
if (!ports || !ports[handleId]) return null;
const boxSize = normalizeBoxSize({ box_size: node.data && node.data.boxSize }, DEFAULT_COMPONENT_BOX_SIZE);
const handles = buildPortHandles(ports, {
rotation: Number((node.data && node.data.rotation) || 0),
flip: Boolean(node.data && node.data.flip),
flop: Boolean(node.data && node.data.flop),
boxSize
});
const found = handles.find(handle => handle.name === handleId);
return found ? found.position : null;
};
// Backward-compatible alias for same-type route crossing validation. // Backward-compatible alias for same-type route crossing validation.
const findSameFamilyRouteCrossing = findSameTypeRouteCrossing; const findSameFamilyRouteCrossing = findSameTypeRouteCrossing;
@@ -1276,6 +1292,7 @@ ${linksYaml}`;
createComponentSymbolMetrics, createComponentSymbolMetrics,
transformPortInfo, transformPortInfo,
getNodePortCanvasPoint, getNodePortCanvasPoint,
getRotatableNodeHandleDirection,
buildPortHandles, buildPortHandles,
buildElementPorts, buildElementPorts,
buildElementPinEntries, buildElementPinEntries,
+77 -25
View File
@@ -1566,6 +1566,7 @@ Organization : OptiHK Limited
calculateLayoutBounds, calculateLayoutBounds,
calculateCompositeBoxSize, calculateCompositeBoxSize,
buildPortHandles, buildPortHandles,
getRotatableNodeHandleDirection,
buildElementPorts, buildElementPorts,
getElementPinName, getElementPinName,
buildElementBoxSize, buildElementBoxSize,
@@ -1698,8 +1699,10 @@ Organization : OptiHK Limited
useEffect(() => { useEffect(() => {
const transformKey = `${data.rotation || 0}:${data.flip ? 1 : 0}:${data.flop ? 1 : 0}`; const transformKey = `${data.rotation || 0}:${data.flip ? 1 : 0}:${data.flop ? 1 : 0}`;
if (prevTransformRef.current !== transformKey) { if (prevTransformRef.current !== transformKey) {
updateNodeInternalsRef.current(id);
prevTransformRef.current = transformKey; prevTransformRef.current = transformKey;
requestAnimationFrame(() => {
updateNodeInternalsRef.current(id);
});
} }
}, [data.rotation, data.flip, data.flop, id]); }, [data.rotation, data.flip, data.flop, id]);
@@ -1719,9 +1722,51 @@ Organization : OptiHK Limited
top: Position.Top, top: Position.Top,
bottom: Position.Bottom bottom: Position.Bottom
}; };
const rotateHandleDirection = (dir, rot) => {
const norm = ((rot % 360) + 360) % 360;
const map = {
0: { right: 'right', left: 'left', top: 'top', bottom: 'bottom' },
90: { right: 'bottom', left: 'top', top: 'left', bottom: 'right' },
180: { right: 'left', left: 'right', top: 'bottom', bottom: 'top' },
270: { right: 'top', left: 'bottom', top: 'right', bottom: 'left' }
};
return (map[norm] || map[0])[dir] || dir;
};
const componentSize = normalizeBoxSize({ box_size: data.boxSize }, DEFAULT_COMPONENT_BOX_SIZE); const componentSize = normalizeBoxSize({ box_size: data.boxSize }, DEFAULT_COMPONENT_BOX_SIZE);
const flippedPorts = useMemo(
() => {
const result = {};
const ports = Object.entries(data.ports || {}).filter(([name]) => name !== 'a0' && name !== 'b0');
if (ports.length === 0) return result;
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
ports.forEach(([, info]) => {
const x = Number(info.x || 0);
const y = Number(info.y || 0);
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
});
ports.forEach(([name, info]) => {
let x = Number(info.x || 0);
let y = Number(info.y || 0);
let a = Number(info.a || 0);
if (data.flip) {
y = minY + maxY - y;
a = -a;
}
if (data.flop) {
x = minX + maxX - x;
a = normalizeAngle(180 - a);
}
result[name] = { ...info, x, y, a: normalizeAngle(a) };
});
return result;
},
[data.ports, data.flip, data.flop]
);
const portHandles = useMemo( const portHandles = useMemo(
() => buildPortHandles(data.ports, { rotation: data.rotation || 0, flip: Boolean(data.flip), flop: Boolean(data.flop), boxSize: componentSize }), () => buildPortHandles(flippedPorts, { rotation: 0, boxSize: componentSize }),
[data.ports, data.rotation, data.flip, data.flop, componentSize] [data.ports, data.rotation, data.flip, data.flop, componentSize]
); );
const portDirectionMap = useMemo( const portDirectionMap = useMemo(
@@ -1731,20 +1776,22 @@ Organization : OptiHK Limited
const isAnchorElement = data.elementType === 'anchor'; const isAnchorElement = data.elementType === 'anchor';
const isBasicCompactComponent = isBasicComponent(data.componentName) && ['waveguide', 'taper', '90 bend'].includes(data.componentName); const isBasicCompactComponent = isBasicComponent(data.componentName) && ['waveguide', 'taper', '90 bend'].includes(data.componentName);
const visualSize = isAnchorElement ? { width: PORT_NODE_SIZE, height: PORT_NODE_SIZE } : componentSize; const visualSize = isAnchorElement ? { width: PORT_NODE_SIZE, height: PORT_NODE_SIZE } : componentSize;
const componentVisualTransform = `rotate(${data.rotation || 0}deg) scaleX(${data.flop ? -1 : 1}) scaleY(${data.flip ? -1 : 1})`; const componentVisualTransform = `rotate(${data.rotation || 0}deg)`;
const componentBodyTransform = `rotate(${data.rotation || 0}deg) scaleX(${data.flop ? -1 : 1}) scaleY(${data.flip ? -1 : 1})`;
const iconSize = createComponentSymbolMetrics(componentSize); const iconSize = createComponentSymbolMetrics(componentSize);
const portLabelStyle = (portHandle) => { const portLabelStyle = (portHandle) => {
const base = { ...portHandle.style }; const base = { ...portHandle.style };
const unrotate = `rotate(${-(data.rotation || 0)}deg)`;
if (portHandle.position === 'left') { if (portHandle.position === 'left') {
return { ...base, left: 'auto', right: 'calc(100% + 8px)', transform: 'translateY(-50%)', textAlign: 'right' }; return { ...base, left: 'auto', right: 'calc(100% + 8px)', transform: `${unrotate} translateY(-50%)`, textAlign: 'right' };
} }
if (portHandle.position === 'right') { if (portHandle.position === 'right') {
return { ...base, left: 'calc(100% + 8px)', right: 'auto', transform: 'translateY(-50%)', textAlign: 'left' }; return { ...base, left: 'calc(100% + 8px)', right: 'auto', transform: `${unrotate} translateY(-50%)`, textAlign: 'left' };
} }
if (portHandle.position === 'top') { if (portHandle.position === 'top') {
return { ...base, top: 'auto', bottom: 'calc(100% + 8px)', transform: 'translateX(-50%)', textAlign: 'center' }; return { ...base, top: 'auto', bottom: 'calc(100% + 8px)', transform: `${unrotate} translateX(-50%)`, textAlign: 'center' };
} }
return { ...base, top: 'calc(100% + 8px)', bottom: 'auto', transform: 'translateX(-50%)', textAlign: 'center' }; return { ...base, top: 'calc(100% + 8px)', bottom: 'auto', transform: `${unrotate} translateX(-50%)`, textAlign: 'center' };
}; };
return ( return (
@@ -1768,7 +1815,7 @@ Organization : OptiHK Limited
...(visualSize.height < 50 && !isAnchorElement ? { padding: '2px 4px' } : {}), ...(visualSize.height < 50 && !isAnchorElement ? { padding: '2px 4px' } : {}),
border: selected ? '2px solid var(--accent)' : '1px solid var(--border)', border: selected ? '2px solid var(--accent)' : '1px solid var(--border)',
boxShadow: selected ? '0 0 15px rgba(56, 189, 248, 0.2)' : '0 4px 6px rgba(0,0,0,0.3)', boxShadow: selected ? '0 0 15px rgba(56, 189, 248, 0.2)' : '0 4px 6px rgba(0,0,0,0.3)',
transform: componentVisualTransform, transform: componentBodyTransform,
transformOrigin: 'center center', transformOrigin: 'center center',
...(isBasicCompactComponent ? { ...(isBasicCompactComponent ? {
padding: 0, padding: 0,
@@ -1806,35 +1853,38 @@ Organization : OptiHK Limited
top: 0, left: 0, top: 0, left: 0,
width: componentSize.width, width: componentSize.width,
height: visualSize.height, height: visualSize.height,
transform: componentVisualTransform,
transformOrigin: 'center center',
pointerEvents: 'none' pointerEvents: 'none'
}}> }}>
{portHandles.map((portHandle) => ( {portHandles.map((portHandle) => {
const originalDir = portDirectionMap.get(portHandle.name) || portHandle.position;
const effectiveDir = rotateHandleDirection(originalDir, data.rotation || 0);
return (
<React.Fragment key={portHandle.name}> <React.Fragment key={portHandle.name}>
<Handle <Handle
type="source" type="source"
position={handlePositionMap[portDirectionMap.get(portHandle.name) || portHandle.position]} position={handlePositionMap[effectiveDir]}
id={portHandle.name} id={portHandle.name}
title={portHandle.name} title={portHandle.name}
style={{ ...baseHandleStyle, ...portHandle.style, zIndex: 10, pointerEvents: 'all' }} style={{ ...baseHandleStyle, ...portHandle.style, zIndex: 10, pointerEvents: 'all' }}
/> />
<Handle <Handle
type="target" type="target"
position={handlePositionMap[portDirectionMap.get(portHandle.name) || portHandle.position]} position={handlePositionMap[effectiveDir]}
id={portHandle.name} id={portHandle.name}
title={portHandle.name} title={portHandle.name}
style={{ ...baseHandleStyle, ...portHandle.style, zIndex: 5, pointerEvents: 'all' }} style={{ ...baseHandleStyle, ...portHandle.style, zIndex: 5, pointerEvents: 'all' }}
/> />
</React.Fragment> </React.Fragment>
))} );
</div> })}
{portHandles.map((portHandle) => (
{portHandles.map((portHandle) => ( <span key={`label-${portHandle.name}`} className="port-name-label" style={portLabelStyle(portHandle)} title={portHandle.name}>
<React.Fragment key={`label-${portHandle.name}`}>
<span className="port-name-label" style={portLabelStyle(portHandle)} title={portHandle.name}>
{portHandle.name} {portHandle.name}
</span> </span>
</React.Fragment> ))}
))} </div>
</div> </div>
); );
}, (prevProps, nextProps) => { }, (prevProps, nextProps) => {
@@ -3974,8 +4024,10 @@ Organization : OptiHK Limited
const targetEndpoint = `${edge.target}:${edge.targetHandle || ''}`; const targetEndpoint = `${edge.target}:${edge.targetHandle || ''}`;
const key = [sourceEndpoint, targetEndpoint].sort().join('<>'); const key = [sourceEndpoint, targetEndpoint].sort().join('<>');
const group = groups.get(key) || []; const group = groups.get(key) || [];
const sourceDirection = getAnchorHandleRouteDirection(nodeMap[edge.source], edge.sourceHandle); const sourceDirection = getAnchorHandleRouteDirection(nodeMap[edge.source], edge.sourceHandle)
const targetDirection = getAnchorHandleRouteDirection(nodeMap[edge.target], edge.targetHandle); || getRotatableNodeHandleDirection(nodeMap[edge.source], edge.sourceHandle);
const targetDirection = getAnchorHandleRouteDirection(nodeMap[edge.target], edge.targetHandle)
|| getRotatableNodeHandleDirection(nodeMap[edge.target], edge.targetHandle);
const usesAnchorDirection = Boolean(sourceDirection || targetDirection); const usesAnchorDirection = Boolean(sourceDirection || targetDirection);
const hasRoutePoints = edge.data && Array.isArray(edge.data.points) && edge.data.points.length >= 2; const hasRoutePoints = edge.data && Array.isArray(edge.data.points) && edge.data.points.length >= 2;
const directionalEdge = usesAnchorDirection const directionalEdge = usesAnchorDirection
@@ -3998,7 +4050,7 @@ Organization : OptiHK Limited
}; };
}); });
return [...separatedEdges, ...rulerEdges]; return [...separatedEdges, ...rulerEdges];
}, [currentEdges, currentNodes, getAnchorHandleRouteDirection, rulerEdges]); }, [currentEdges, currentNodes, getAnchorHandleRouteDirection, getRotatableNodeHandleDirection, rulerEdges]);
const [projectCompositeMap, setProjectCompositeMap] = useState({}); const [projectCompositeMap, setProjectCompositeMap] = useState({});
const [standaloneComposites, setStandaloneComposites] = useState([]); const [standaloneComposites, setStandaloneComposites] = useState([]);
@@ -5985,6 +6037,7 @@ Organization : OptiHK Limited
const route = currentLinkRoute; const route = currentLinkRoute;
const view = routeStyleForSettings(route, false); const view = routeStyleForSettings(route, false);
const edgeId = `edge-${connection.source}-${connection.sourceHandle}-${connection.target}-${connection.targetHandle}-${Date.now()}`; const edgeId = `edge-${connection.source}-${connection.sourceHandle}-${connection.target}-${connection.targetHandle}-${Date.now()}`;
const nodeMap = Object.fromEntries(activePage.nodes.map(node => [node.id, node]));
const candidate = { const candidate = {
id: edgeId, id: edgeId,
source: connection.source, source: connection.source,
@@ -5994,9 +6047,8 @@ Organization : OptiHK Limited
type: view.type, type: view.type,
selectable: true, selectable: true,
style: view.style, style: view.style,
data: { route } data: { route },
}; };
const nodeMap = Object.fromEntries(activePage.nodes.map(node => [node.id, node]));
const conflict = findSameTypeRouteCrossing(candidate, activePage.edges, nodeMap, technologyManifest); const conflict = findSameTypeRouteCrossing(candidate, activePage.edges, nodeMap, technologyManifest);
if (conflict) { if (conflict) {
const source = nodeMap[conflict.conflictEdge.source]?.data?.componentDisplayName || conflict.conflictEdge.source; const source = nodeMap[conflict.conflictEdge.source]?.data?.componentDisplayName || conflict.conflictEdge.source;
@@ -6010,7 +6062,7 @@ Organization : OptiHK Limited
: p : p
))); )));
addLog(`Connected ${connection.sourceHandle} to ${connection.targetHandle}.`); addLog(`Connected ${connection.sourceHandle} to ${connection.targetHandle}.`);
}, [activePageId, activePage, rulerMode, currentLinkRoute, technologyManifest, addLog]); }, [activePageId, activePage, rulerMode, currentLinkRoute, technologyManifest, addLog, getAnchorHandleRouteDirection]);
// Select custom route edges from their SVG hit target. // Select custom route edges from their SVG hit target.
const handleRouteEdgeMouseDown = useCallback((event) => { const handleRouteEdgeMouseDown = useCallback((event) => {
+829
View File
@@ -0,0 +1,829 @@
<!DOCTYPE html>
<html lang="en">
{% raw %}
<head>
<meta charset="UTF-8">
<title>mxPIC Core - Canvas</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/reactflow@11/dist/umd/index.js" crossorigin></script>
<link rel="stylesheet" href="https://unpkg.com/reactflow@11/dist/style.css" />
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
/* optihk Shared Dark Theme Variables */
:root {
--bg-main: #0f172a;
--bg-card: #1e293b;
--text-main: #f8fafc;
--text-muted: #94a3b8;
--accent: #38bdf8;
--accent-hover: #0284c7;
--border: #334155;
--input-bg: #0b1120;
}
body,
html,
#root {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-family: 'Inter', sans-serif;
background-color: var(--bg-main);
color: var(--text-main);
overflow: hidden;
}
/* Custom Dark Scrollbars */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-main);
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
/* Tree View Styling */
details {
margin-left: 8px;
}
summary {
cursor: pointer;
padding: 4px 0;
color: var(--text-main);
}
.tree-folder summary {
font-weight: 500;
color: var(--accent);
}
.component-leaf {
cursor: grab;
padding: 4px 6px;
margin-left: 15px;
margin-top: 2px;
word-break: break-all;
white-space: normal;
border-radius: 4px;
color: var(--text-muted);
transition: background 0.2s ease, color 0.2s ease;
}
.component-leaf:hover {
background: var(--border);
color: var(--text-main);
}
/* Side Panel Blocks */
.left-block, .right-block {
background: var(--bg-main);
border: 1px solid var(--border);
border-radius: 6px;
margin-bottom: 12px;
overflow: hidden;
}
.left-block-header, .right-block-header {
background: var(--bg-card);
padding: 8px 12px;
font-weight: 600;
font-size: 0.85em;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
}
.left-block-body, .right-block-body {
padding: 12px;
font-size: 0.85em;
}
.placeholder-block {
border: 1px dashed var(--border);
padding: 12px;
color: var(--text-muted);
text-align: center;
background: var(--bg-main);
}
.toggle-btn {
background: none;
border: none;
font-size: 1.2em;
color: var(--text-muted);
cursor: pointer;
padding: 2px 6px;
border-radius: 4px;
transition: background 0.2s ease, color 0.2s ease;
}
.toggle-btn:hover {
background: var(--border);
color: var(--text-main);
}
/* Standard Form Inputs inside panels */
input[type="number"], input[type="text"] {
background-color: var(--input-bg);
border: 1px solid var(--border);
color: var(--text-main);
font-family: inherit;
font-size: 0.9em;
padding: 6px 10px;
border-radius: 4px;
outline: none;
width: 100%;
box-sizing: border-box;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
input[type="number"]:focus, input[type="text"]:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(56, 189, 248, 0.2);
}
label {
font-weight: 500;
color: var(--text-muted);
margin-bottom: 4px;
display: block;
}
/* ReactFlow Dark Mode Overrides */
.react-flow__controls button {
background-color: var(--bg-card) !important;
border-bottom: 1px solid var(--border) !important;
fill: var(--text-main) !important;
}
.react-flow__controls button:hover {
background-color: var(--border) !important;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect, useRef, useCallback, useMemo, memo } = React;
const {
ReactFlow,
ReactFlowProvider,
useNodesState,
useEdgesState,
Controls,
Background,
useReactFlow,
addEdge,
Handle,
Position,
useUpdateNodeInternals,
} = window.ReactFlow;
// --- NODE DESIGN (Dark CAD Style) ---
const RotatableNode = ({ id, data, selected }) => {
const updateNodeInternals = useUpdateNodeInternals();
useEffect(() => {
updateNodeInternals(id);
}, [data.rotation, updateNodeInternals, id]);
const baseHandleStyle = {
width: 10, height: 10,
background: 'var(--bg-main)',
border: '2px solid var(--accent)',
borderRadius: '50%',
};
const leftTopPort = { ...baseHandleStyle, top: '24%', transform: 'translate(-50%, -50%)' };
const leftBottomPort = { ...baseHandleStyle, top: '76%', transform: 'translate(-50%, -50%)' };
const rightTopPort = { ...baseHandleStyle, top: '24%', transform: 'translate(50%, -50%)' };
const rightBottomPort = { ...baseHandleStyle, top: '76%', transform: 'translate(50%, -50%)' };
return (
<div style={{
padding: '10px 20px',
border: selected ? '2px solid var(--accent)' : '1px solid var(--border)',
borderRadius: 6,
background: 'var(--bg-card)',
color: 'var(--text-main)',
minWidth: 100, textAlign: 'center',
position: 'relative', transform: `rotate(${data.rotation || 0}deg)`,
transition: selected ? 'none' : 'transform 0.1s ease',
boxSizing: 'border-box',
boxShadow: selected ? '0 0 15px rgba(56, 189, 248, 0.2)' : '0 4px 6px rgba(0,0,0,0.3)',
fontFamily: "'Inter', sans-serif",
fontSize: '0.85rem'
}}>
<div>{data.componentDisplayName}</div>
<Handle type="source" position={Position.Left} id="port-lt-source" style={{ ...leftTopPort, zIndex: 10 }} />
<Handle type="target" position={Position.Left} id="port-lt-target" style={{ ...leftTopPort, zIndex: 5 }} />
<Handle type="source" position={Position.Left} id="port-lb-source" style={{ ...leftBottomPort, zIndex: 10 }} />
<Handle type="target" position={Position.Left} id="port-lb-target" style={{ ...leftBottomPort, zIndex: 5 }} />
<Handle type="source" position={Position.Right} id="port-rt-source" style={{ ...rightTopPort, zIndex: 10 }} />
<Handle type="target" position={Position.Right} id="port-rt-target" style={{ ...rightTopPort, zIndex: 5 }} />
<Handle type="source" position={Position.Right} id="port-rb-source" style={{ ...rightBottomPort, zIndex: 10 }} />
<Handle type="target" position={Position.Right} id="port-rb-target" style={{ ...rightBottomPort, zIndex: 5 }} />
</div>
);
};
const TreeNode = ({ name, children }) => {
if (children && children.__type__ === 'component') {
const componentName = children.__name__;
const handleDragStart = (event) => {
event.dataTransfer.setData('application/reactflow', componentName);
event.dataTransfer.effectAllowed = 'move';
};
return (
<div className="component-leaf" draggable onDragStart={handleDragStart}>
<span style={{color: 'var(--accent)', marginRight: '4px'}}></span> {name}
</div>
);
}
const hasChildren = children && Object.keys(children).length > 0;
return (
<details>
<summary className="tree-folder">
<span style={{ wordBreak: 'break-all', whiteSpace: 'normal' }}>📂 {name}</span>
</summary>
{hasChildren &&
Object.entries(children).map(([childName, childData]) => (
<TreeNode key={childName} name={childName} children={childData} />
))
}
</details>
);
};
const LeftPanel = ({ library, treeKey, expanded, onToggle, treeRef, width }) => (
<aside style={{
width: width, background: 'var(--bg-card)', borderRight: '1px solid var(--border)',
padding: 12, display: 'flex', flexDirection: 'column', height: '100%',
boxSizing: 'border-box', overflowY: 'auto'
}}>
<div className="left-block">
<div className="left-block-header">
<span>PDK Libraries</span>
<button className="toggle-btn" onClick={onToggle} title={expanded ? 'Collapse all' : 'Expand all'}>
{expanded ? '▾' : '▸'}
</button>
</div>
<div className="left-block-body" style={{ maxHeight: '45vh', overflowY: 'auto' }} key={treeKey} ref={treeRef}>
{library && Object.keys(library).length > 0 ? (
Object.entries(library).map(([key, value]) => (
<TreeNode key={key} name={key} children={value} />
))
) : (
<p style={{ color: 'var(--text-muted)', fontStyle: 'italic' }}>Loading library...</p>
)}
</div>
</div>
<div className="left-block">
<div className="left-block-header">Routing modes</div>
<div className="left-block-body">
<ul style={{ paddingLeft: 20, margin: 0, color: 'var(--text-muted)', lineHeight: '1.8' }}>
<li>Single mode wires</li>
<li>Multi-mode wires</li>
<li>DC electrical wires</li>
<li>RF electrical wires</li>
</ul>
</div>
</div>
<div className="left-block" style={{ marginTop: 'auto' }}>
<div className="left-block-header">Session</div>
<div className="left-block-body" style={{color: 'var(--text-muted)'}}>
<div style={{marginBottom: '4px'}}>Name: XXXXXX</div>
<div style={{marginBottom: '10px'}}>ID: 12345678</div>
<button disabled style={{
background: 'var(--border)', color: 'var(--text-muted)',
border: 'none', padding: '6px 12px', borderRadius: '4px', width: '100%'
}}>Log out</button>
</div>
</div>
</aside>
);
const RightPanel = memo(({ selectedNode, width, onRenameComponent }) => {
const [componentData, setComponentData] = useState(null);
const [loading, setLoading] = useState(false);
const [enlarged, setEnlarged] = useState(null);
const { setNodes } = useReactFlow();
const [editingComponentName, setEditingComponentName] = useState(false);
const [tempComponentName, setTempComponentName] = useState('');
const [localX, setLocalX] = useState('');
const [localY, setLocalY] = useState('');
const [localRotation, setLocalRotation] = useState('');
useEffect(() => {
const nodeId = selectedNode?.id;
if (!nodeId) {
setComponentData(null);
setLoading(false);
return;
}
const compName = selectedNode?.data?.componentName;
if (!compName) {
setComponentData(null);
setLoading(false);
return;
}
if (componentData && componentData.name === compName && componentData.nodeId === nodeId) return;
setLoading(true);
fetch(`/api/component/${encodeURIComponent(compName)}`)
.then(r => r.json())
.then(data => {
setComponentData({ ...data, nodeId: nodeId, componentDisplayName: selectedNode.data.componentDisplayName || data.name });
setLoading(false);
})
.catch(() => setLoading(false));
}, [selectedNode?.id, selectedNode?.data?.componentName, selectedNode?.data?.componentDisplayName]);
useEffect(() => {
if (selectedNode) {
setLocalX(selectedNode.position.x.toFixed(3));
setLocalY(selectedNode.position.y.toFixed(3));
setLocalRotation(((selectedNode.data?.rotation || 0)).toFixed(3));
}
}, [selectedNode?.position.x, selectedNode?.position.y, selectedNode?.data?.rotation, selectedNode?.id]);
const updatePosition = useCallback((id, axis, value) => {
const val = parseFloat(value);
if (isNaN(val)) return;
setNodes(nds => nds.map(n => n.id === id ? { ...n, position: { ...n.position, [axis]: val } } : n));
}, [setNodes]);
const updateRotation = useCallback((id, value) => {
const val = parseFloat(value);
if (isNaN(val)) return;
const clamped = Math.min(180, Math.max(-180, val));
setNodes(nds => nds.map(n => n.id === id ? { ...n, data: { ...n.data, rotation: clamped } } : n));
}, [setNodes]);
const formatPort = (port) => {
if (!port) return '—';
return `x:${port.x ?? '?'}, y:${port.y ?? '?'}, a:${port.a ?? '?'}, w:${port.width ?? '?'}`;
};
const currentRotation = selectedNode?.data?.rotation ?? 0;
const currentComponentDisplayName = selectedNode?.data?.componentDisplayName || '';
const handleStartEditName = () => {
setTempComponentName(currentComponentDisplayName);
setEditingComponentName(true);
};
const handleSaveName = () => {
const newName = tempComponentName.trim();
if (newName && selectedNode) {
onRenameComponent(selectedNode.id, newName);
}
setEditingComponentName(false);
};
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
handleSaveName();
} else if (e.key === 'Escape') {
setEditingComponentName(false);
}
};
return (
<aside style={{
width: width, background: 'var(--bg-card)', borderLeft: '1px solid var(--border)',
padding: 12, display: 'flex', flexDirection: 'column', height: '100%',
boxSizing: 'border-box', overflowY: 'auto'
}}>
<div className="right-block">
<div className="right-block-header">Transforms</div>
<div className="right-block-body">
{selectedNode ? (
<div>
<label>X Coordinate</label>
<input
type="number"
step="1"
value={localX}
onChange={(e) => setLocalX(e.target.value)}
onBlur={() => {
const val = parseFloat(localX);
if (!isNaN(val) && selectedNode) {
updatePosition(selectedNode.id, 'x', val);
setLocalX(val.toFixed(3));
} else if (selectedNode) {
setLocalX(selectedNode.position.x.toFixed(3));
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter') e.currentTarget.blur();
}}
/>
<br /><br />
<label>Y Coordinate</label>
<input
type="number"
step="1"
value={localY}
onChange={(e) => setLocalY(e.target.value)}
onBlur={() => {
const val = parseFloat(localY);
if (!isNaN(val) && selectedNode) {
updatePosition(selectedNode.id, 'y', val);
setLocalY(val.toFixed(3));
} else if (selectedNode) {
setLocalY(selectedNode.position.y.toFixed(3));
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter') e.currentTarget.blur();
}}
/>
<br /><br />
<label>Angle (deg)</label>
<input
type="number"
step="1"
value={localRotation}
onChange={(e) => setLocalRotation(e.target.value)}
onBlur={() => {
const val = parseFloat(localRotation);
if (!isNaN(val) && selectedNode) {
updateRotation(selectedNode.id, val);
setLocalRotation(val.toFixed(3));
} else if (selectedNode) {
setLocalRotation(((selectedNode.data?.rotation || 0)).toFixed(3));
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter') e.currentTarget.blur();
}}
/>
</div>
) : (
<p style={{ color: 'var(--text-muted)', fontStyle: 'italic', textAlign: 'center' }}>Select a node to inspect</p>
)}
</div>
</div>
{selectedNode?.data?.componentName && (
<div className="right-block">
<div className="right-block-header">Parameters</div>
<div className="right-block-body">
{loading ? (
<p style={{color: 'var(--text-muted)'}}>Loading data...</p>
) : componentData ? (
<>
<div style={{ marginBottom: '15px' }}>
<label>Instance Name</label>
{editingComponentName ? (
<input
type="text"
value={tempComponentName}
onChange={(e) => setTempComponentName(e.target.value)}
onBlur={handleSaveName}
onKeyDown={handleKeyDown}
autoFocus
/>
) : (
<div
style={{
cursor: 'pointer',
padding: '6px 8px',
backgroundColor: 'var(--input-bg)',
border: '1px solid var(--border)',
borderRadius: '4px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
wordBreak: 'break-all',
color: 'var(--accent)'
}}
onClick={handleStartEditName}
title="Click to edit"
>
<span>{currentComponentDisplayName || componentData.name}</span>
<span style={{fontSize: '12px', color: 'var(--text-muted)'}}></span>
</div>
)}
</div>
<div style={{color: 'var(--text-muted)', lineHeight: '1.6'}}>
<p style={{ margin: '0 0 8px 0', wordBreak: 'break-all' }}>
<strong style={{color: 'var(--text-main)'}}>Cell:</strong> {componentData.name}
</p>
<p style={{ margin: '0 0 8px 0' }}>
<strong style={{color: 'var(--text-main)'}}>Foundry:</strong> {componentData.foundry}<br/>
<strong style={{color: 'var(--text-main)'}}>Process:</strong> {componentData.process}
</p>
</div>
<p style={{color: 'var(--text-main)', fontWeight: '500', marginBottom: '4px'}}>Ports:</p>
<ul style={{ paddingLeft: 15, margin: '0 0 15px 0', color: 'var(--text-muted)' }}>
{componentData.ports && Object.entries(componentData.ports).map(([portName, portInfo]) => (
<li key={portName} style={{ letterSpacing: '0.5px' }}>
<span style={{color: 'var(--accent)'}}>{portName}</span>: {formatPort(portInfo)}
</li>
))}
</ul>
<p style={{color: 'var(--text-main)', fontWeight: '500', marginBottom: '4px'}}>Preview:</p>
<div style={{
border: '1px solid var(--border)', width: '100%', height: 100,
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: 'var(--input-bg)', borderRadius: '4px',
overflow: 'hidden',
}}>
<img
src={`/api/component/${encodeURIComponent(componentData.name)}/image`}
alt="Component layout"
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', cursor: 'pointer' }}
onClick={() => setEnlarged(`/api/component/${encodeURIComponent(componentData.name)}/image`)}
onError={(e) => {
e.currentTarget.style.display = 'none';
e.currentTarget.parentElement.innerHTML = '<span style="color:var(--text-muted)">No preview</span>';
}}
/>
</div>
</>
) : (
<p style={{ color: 'var(--text-muted)' }}>No data available</p>
)}
</div>
</div>
)}
<div className="right-block" style={{ marginTop: 'auto' }}>
<div className="right-block-header">Inverse Design</div>
<div className="right-block-body placeholder-block">Requires AI Upgrade</div>
</div>
{enlarged && (
<div
style={{
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: 'rgba(15, 23, 42, 0.9)', zIndex: 1000,
display: 'flex', alignItems: 'center', justifyContent: 'center',
cursor: 'zoom-out',
backdropFilter: 'blur(4px)'
}}
onClick={() => setEnlarged(null)}
>
<img
src={enlarged}
alt="Enlarged layout"
style={{ maxWidth: '90%', maxHeight: '90%', objectFit: 'contain', border: '1px solid var(--border)', background: 'var(--bg-main)' }}
/>
</div>
)}
</aside>
);
}, (prevProps, nextProps) => {
const prev = prevProps.selectedNode;
const next = nextProps.selectedNode;
if (prev?.id !== next?.id) return false;
if (prev?.position?.x !== next?.position?.x) return false;
if (prev?.position?.y !== next?.position?.y) return false;
if (prev?.data?.rotation !== next?.data?.rotation) return false;
if (prev?.data?.componentName !== next?.data?.componentName) return false;
if (prev?.data?.componentDisplayName !== next?.data?.componentDisplayName) return false;
if (prevProps.width !== nextProps.width) return false;
return true;
});
const ResizeHandle = ({ onMouseDown }) => (
<div
onMouseDown={onMouseDown}
style={{
width: 6, cursor: 'col-resize', background: 'transparent',
transition: 'background 0.2s', zIndex: 5, flexShrink: 0,
}}
onMouseEnter={(e) => e.currentTarget.style.background = 'var(--accent)'}
onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
/>
);
function App() {
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const reactFlowInstance = useReactFlow();
const [library, setLibrary] = useState(null);
const [treeKey, setTreeKey] = useState(0);
const [expanded, setExpanded] = useState(false);
const treeContainerRef = useRef(null);
const [leftWidth, setLeftWidth] = useState(260);
const [rightWidth, setRightWidth] = useState(260);
const [dragging, setDragging] = useState(null);
const [gridSnap, setGridSnap] = useState(false);
const componentCounterRef = useRef(1);
const generateComponentDisplayName = useCallback(() => {
const name = `component_${componentCounterRef.current}`;
componentCounterRef.current += 1;
return name;
}, []);
const renameComponent = useCallback((nodeId, newComponentDisplayName) => {
setNodes(nds => nds.map(n => {
if (n.id === nodeId) {
return {
...n,
data: {
...n.data,
componentDisplayName: newComponentDisplayName
}
};
}
return n;
}));
}, [setNodes]);
const fetchLibrary = useCallback(async () => {
try {
const res = await fetch('/api/library');
const data = await res.json();
setLibrary(data);
} catch (err) {
console.error('Failed to fetch library', err);
}
}, []);
useEffect(() => { fetchLibrary(); }, [fetchLibrary]);
const selectedNode = useMemo(() => nodes.find(n => n.selected), [nodes]);
const onDragOver = useCallback((event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, []);
const onDrop = useCallback((event) => {
event.preventDefault();
const type = event.dataTransfer.getData('application/reactflow');
if (!type) return;
const position = reactFlowInstance.screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
const componentDisplayName = generateComponentDisplayName();
const newNode = {
id: Date.now().toString(),
type: 'rotatableNode',
position,
data: {
label: type,
componentName: type,
rotation: 0,
componentDisplayName: componentDisplayName
},
};
setNodes((nds) => nds.concat(newNode));
}, [setNodes, reactFlowInstance, generateComponentDisplayName]);
const onConnect = useCallback((connection) => {
setEdges((eds) => addEdge({ ...connection, type: 'smoothstep', style: { stroke: 'var(--accent)', strokeWidth: 2 } }, eds));
}, [setEdges]);
const expandAll = useCallback(() => {
if (treeContainerRef.current) {
treeContainerRef.current.querySelectorAll('details').forEach(d => d.open = true);
}
}, []);
const collapseAll = useCallback(() => setTreeKey(k => k + 1), []);
const handleToggle = useCallback(() => {
if (expanded) { collapseAll(); setExpanded(false); }
else { expandAll(); setExpanded(true); }
}, [expanded, expandAll, collapseAll]);
const handleResizeStart = useCallback((side) => (e) => {
e.preventDefault();
setDragging(side);
}, []);
useEffect(() => {
if (!dragging) return;
const onMouseMove = (e) => {
if (dragging === 'left') {
setLeftWidth(Math.min(500, Math.max(150, e.clientX)));
} else if (dragging === 'right') {
const newWidth = window.innerWidth - e.clientX;
setRightWidth(Math.min(500, Math.max(150, newWidth)));
}
};
const onMouseUp = () => setDragging(null);
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
return () => {
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
};
}, [dragging]);
const toggleGridSnap = useCallback(() => {
setGridSnap(prev => !prev);
}, []);
return (
<div style={{ display: 'flex', width: '100%', height: '100%', userSelect: dragging ? 'none' : 'auto' }}>
<LeftPanel
library={library} treeKey={treeKey} expanded={expanded}
onToggle={handleToggle} treeRef={treeContainerRef} width={leftWidth}
/>
<ResizeHandle onMouseDown={handleResizeStart('left')} />
<div style={{ flex: 1, position: 'relative' }}>
{/* Grid Snap Toggle Switch */}
<div style={{
position: 'absolute', top: 15, right: 15, zIndex: 10,
display: 'flex', alignItems: 'center', gap: 10,
background: 'var(--bg-card)', padding: '6px 12px', borderRadius: '8px',
border: '1px solid var(--border)', boxShadow: '0 4px 6px rgba(0,0,0,0.3)'
}}>
<span style={{
fontSize: '0.85em', fontWeight: '500', color: 'var(--text-main)', userSelect: 'none'
}}>Snap to Grid</span>
<div
onClick={toggleGridSnap}
style={{
width: 40, height: 20, borderRadius: 10,
background: gridSnap ? 'var(--accent)' : 'var(--input-bg)',
border: '1px solid ' + (gridSnap ? 'var(--accent)' : 'var(--border)'),
cursor: 'pointer', display: 'flex', alignItems: 'center',
padding: '0 2px', transition: 'background 0.3s, border-color 0.3s',
}}
>
<div style={{
width: 16, height: 16, borderRadius: '50%',
background: '#fff',
transform: gridSnap ? 'translateX(20px)' : 'translateX(0)',
transition: 'transform 0.2s',
}} />
</div>
</div>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onDragOver={onDragOver}
onDrop={onDrop}
onConnect={onConnect}
nodeTypes={{ rotatableNode: RotatableNode }}
fitView
snapToGrid={gridSnap}
snapGrid={[10, 10]}
nodesDraggable={true}
nodesConnectable={true}
elementsSelectable={true}
connectionRadius={50}
>
<Controls style={{ bottom: 15, left: 15 }} />
{/* Dark mode background for the canvas */}
<Background color="#334155" gap={20} size={1} />
</ReactFlow>
</div>
<ResizeHandle onMouseDown={handleResizeStart('right')} />
<RightPanel
selectedNode={selectedNode}
width={rightWidth}
onRenameComponent={renameComponent}
/>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ReactFlowProvider>
<App />
</ReactFlowProvider>
);
</script>
</body>
</html>
{% endraw %}
+750
View File
@@ -0,0 +1,750 @@
<!DOCTYPE html>
<html lang="en">
{% raw %}
<head>
<meta charset="UTF-8">
<title>Canvas with PDK Library Component Name & Rotation</title>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/reactflow@11/dist/umd/index.js" crossorigin></script>
<link rel="stylesheet" href="https://unpkg.com/reactflow@11/dist/style.css" />
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
body,
html,
#root {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
details {
margin-left: 8px;
}
summary {
cursor: pointer;
}
.left-block {
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 8px;
}
.left-block-header {
background: #e0e0e0;
padding: 6px 10px;
font-weight: bold;
font-size: 0.9em;
border-bottom: 1px solid #ccc;
display: flex;
align-items: center;
justify-content: space-between;
}
.left-block-body {
padding: 8px 10px;
font-size: 0.85em;
}
.tree-folder summary {
font-weight: bold;
}
.right-block {
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 8px;
}
.right-block-header {
background: #e0e0e0;
padding: 6px 10px;
font-weight: bold;
font-size: 0.9em;
border-bottom: 1px solid #ccc;
}
.right-block-body {
padding: 8px 10px;
font-size: 0.85em;
}
.placeholder-block {
border: 1px dashed #bbb;
padding: 8px;
color: #888;
font-size: 0.85em;
background: #f9f9f9;
}
.toggle-btn {
background: none;
border: none;
font-size: 1.8em;
cursor: pointer;
padding: 2px 4px;
line-height: 1;
border-radius: 3px;
}
.toggle-btn:hover {
background: #d0d0d0;
}
.component-leaf {
cursor: grab;
padding: 2px 4px;
margin-left: 20px;
word-break: break-all;
white-space: normal;
}
.component-leaf:hover {
background: #e6f7ff;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect, useRef, useCallback, useMemo, memo } = React;
const {
ReactFlow,
ReactFlowProvider,
useNodesState,
useEdgesState,
Controls,
Background,
useReactFlow,
addEdge,
Handle,
Position,
useUpdateNodeInternals,
} = window.ReactFlow;
const RotatableNode = ({ id, data, selected }) => {
const updateNodeInternals = useUpdateNodeInternals();
useEffect(() => {
updateNodeInternals(id);
}, [data.rotation, updateNodeInternals, id]);
const baseHandleStyle = {
width: 14, height: 14,
background: '#555',
border: 'none',
borderRadius: '50%',
};
const leftTopPort = { ...baseHandleStyle, top: '24%', transform: 'translate(-50%, -50%)' };
const leftBottomPort = { ...baseHandleStyle, top: '76%', transform: 'translate(-50%, -50%)' };
const rightTopPort = { ...baseHandleStyle, top: '24%', transform: 'translate(50%, -50%)' };
const rightBottomPort = { ...baseHandleStyle, top: '76%', transform: 'translate(50%, -50%)' };
return (
<div style={{
padding: '10px 20px', border: '1px solid #333', borderRadius: 6,
background: '#fff', minWidth: 100, textAlign: 'center',
position: 'relative', transform: `rotate(${data.rotation || 0}deg)`,
transition: selected ? 'none' : 'transform 0.1s ease',
boxSizing: 'border-box',
}}>
<div>{data.componentDisplayName}</div>
<Handle type="source" position={Position.Left} id="port-lt-source" style={{ ...leftTopPort, zIndex: 10 }} />
<Handle type="target" position={Position.Left} id="port-lt-target" style={{ ...leftTopPort, zIndex: 5 }} />
<Handle type="source" position={Position.Left} id="port-lb-source" style={{ ...leftBottomPort, zIndex: 10 }} />
<Handle type="target" position={Position.Left} id="port-lb-target" style={{ ...leftBottomPort, zIndex: 5 }} />
<Handle type="source" position={Position.Right} id="port-rt-source" style={{ ...rightTopPort, zIndex: 10 }} />
<Handle type="target" position={Position.Right} id="port-rt-target" style={{ ...rightTopPort, zIndex: 5 }} />
<Handle type="source" position={Position.Right} id="port-rb-source" style={{ ...rightBottomPort, zIndex: 10 }} />
<Handle type="target" position={Position.Right} id="port-rb-target" style={{ ...rightBottomPort, zIndex: 5 }} />
</div>
);
};
const TreeNode = ({ name, children }) => {
if (children && children.__type__ === 'component') {
const componentName = children.__name__;
const handleDragStart = (event) => {
event.dataTransfer.setData('application/reactflow', componentName);
event.dataTransfer.effectAllowed = 'move';
};
return (
<div className="component-leaf" draggable onDragStart={handleDragStart}>
🔷 {name}
</div>
);
}
const hasChildren = children && Object.keys(children).length > 0;
return (
<details>
<summary className="tree-folder">
<span style={{ wordBreak: 'break-all', whiteSpace: 'normal' }}>📂 {name}</span>
</summary>
{hasChildren &&
Object.entries(children).map(([childName, childData]) => (
<TreeNode key={childName} name={childName} children={childData} />
))
}
</details>
);
};
const LeftPanel = ({ library, treeKey, expanded, onToggle, treeRef, width }) => (
<aside style={{
width: width, background: '#f4f4f4', borderRight: '1px solid #ccc',
padding: 10, display: 'flex', flexDirection: 'column', height: '100%',
boxSizing: 'border-box', overflowY: 'auto'
}}>
<div className="left-block">
<div className="left-block-header">
<span>PDK Libraries</span>
<button className="toggle-btn" onClick={onToggle} title={expanded ? 'Collapse all' : 'Expand all'}>
{expanded ? '▾' : '▸'}
</button>
</div>
<div className="left-block-body" style={{ maxHeight: '45vh', overflowY: 'auto' }} key={treeKey} ref={treeRef}>
{library && Object.keys(library).length > 0 ? (
Object.entries(library).map(([key, value]) => (
<TreeNode key={key} name={key} children={value} />
))
) : (
<p style={{ color: '#999' }}>Loading library...</p>
)}
</div>
</div>
<div className="left-block">
<div className="left-block-header">Routing selections</div>
<div className="left-block-body">
<ul style={{ paddingLeft: 20, margin: 0 }}>
<li>Single mode wires</li>
<li>Multi-mode wires</li>
<li>DC electrical wires</li>
<li>RF electrical wires</li>
</ul>
</div>
</div>
<div className="left-block" style={{ marginTop: 'auto' }}>
<div className="left-block-header">User info</div>
<div className="left-block-body">
<div>Name: XXXXXX</div>
<div>ID: 12345678</div>
<button disabled>Log out</button>
</div>
</div>
</aside>
);
const RightPanel = memo(({ selectedNode, width, onRenameComponent }) => {
const [componentData, setComponentData] = useState(null);
const [loading, setLoading] = useState(false);
const [enlarged, setEnlarged] = useState(null);
const { setNodes } = useReactFlow();
const [editingComponentName, setEditingComponentName] = useState(false);
const [tempComponentName, setTempComponentName] = useState('');
const [localX, setLocalX] = useState('');
const [localY, setLocalY] = useState('');
const [localRotation, setLocalRotation] = useState('');
useEffect(() => {
const nodeId = selectedNode?.id;
if (!nodeId) {
setComponentData(null);
setLoading(false);
return;
}
const compName = selectedNode?.data?.componentName;
if (!compName) {
setComponentData(null);
setLoading(false);
return;
}
if (componentData && componentData.name === compName && componentData.nodeId === nodeId) return;
setLoading(true);
fetch(`/api/component/${encodeURIComponent(compName)}`)
.then(r => r.json())
.then(data => {
setComponentData({ ...data, nodeId: nodeId, componentDisplayName: selectedNode.data.componentDisplayName || data.name });
setLoading(false);
})
.catch(() => setLoading(false));
}, [selectedNode?.id, selectedNode?.data?.componentName, selectedNode?.data?.componentDisplayName]);
useEffect(() => {
if (selectedNode) {
setLocalX(selectedNode.position.x.toFixed(3));
setLocalY(selectedNode.position.y.toFixed(3));
setLocalRotation(((selectedNode.data?.rotation || 0)).toFixed(3));
}
}, [selectedNode?.position.x, selectedNode?.position.y, selectedNode?.data?.rotation, selectedNode?.id]);
const updatePosition = useCallback((id, axis, value) => {
const val = parseFloat(value);
if (isNaN(val)) return;
setNodes(nds => nds.map(n => n.id === id ? { ...n, position: { ...n.position, [axis]: val } } : n));
}, [setNodes]);
const updateRotation = useCallback((id, value) => {
const val = parseFloat(value);
if (isNaN(val)) return;
const clamped = Math.min(180, Math.max(-180, val));
setNodes(nds => nds.map(n => n.id === id ? { ...n, data: { ...n.data, rotation: clamped } } : n));
}, [setNodes]);
const formatPort = (port) => {
if (!port) return '—';
return `x:${port.x ?? '?'}, y:${port.y ?? '?'}, a:${port.a ?? '?'}, w:${port.width ?? '?'}`;
};
const currentRotation = selectedNode?.data?.rotation ?? 0;
const currentComponentDisplayName = selectedNode?.data?.componentDisplayName || '';
const handleStartEditName = () => {
setTempComponentName(currentComponentDisplayName);
setEditingComponentName(true);
};
const handleSaveName = () => {
const newName = tempComponentName.trim();
if (newName && selectedNode) {
onRenameComponent(selectedNode.id, newName);
}
setEditingComponentName(false);
};
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
handleSaveName();
} else if (e.key === 'Escape') {
setEditingComponentName(false);
}
};
return (
<aside style={{
width: width, background: '#fafafa', borderLeft: '1px solid #ccc',
padding: 10, display: 'flex', flexDirection: 'column', height: '100%',
boxSizing: 'border-box', overflowY: 'auto'
}}>
<div className="right-block">
<div className="right-block-header">Properties</div>
<div className="right-block-body">
{selectedNode ? (
<div>
<label>X:</label>
<input
type="number"
step="1"
value={localX}
onChange={(e) => setLocalX(e.target.value)}
onBlur={() => {
const val = parseFloat(localX);
if (!isNaN(val) && selectedNode) {
updatePosition(selectedNode.id, 'x', val);
setLocalX(val.toFixed(3));
} else if (selectedNode) {
setLocalX(selectedNode.position.x.toFixed(3));
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.currentTarget.blur();
}
}}
style={{ width: '100%' }}
/>
<br /><br />
<label>Y:</label>
<input
type="number"
step="1"
value={localY}
onChange={(e) => setLocalY(e.target.value)}
onBlur={() => {
const val = parseFloat(localY);
if (!isNaN(val) && selectedNode) {
updatePosition(selectedNode.id, 'y', val);
setLocalY(val.toFixed(3));
} else if (selectedNode) {
setLocalY(selectedNode.position.y.toFixed(3));
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.currentTarget.blur();
}
}}
style={{ width: '100%' }}
/>
<br /><br />
<label>A (deg):</label>
<div style={{ marginTop: 4 }}>
<input
type="number"
step="1"
value={localRotation}
onChange={(e) => setLocalRotation(e.target.value)}
onBlur={() => {
const val = parseFloat(localRotation);
if (!isNaN(val) && selectedNode) {
updateRotation(selectedNode.id, val);
setLocalRotation(val.toFixed(3));
} else if (selectedNode) {
setLocalRotation(((selectedNode.data?.rotation || 0)).toFixed(3));
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.currentTarget.blur();
}
}}
style={{ width: '100%', marginTop: 4 }}
/>
</div>
</div>
) : (
<p style={{ color: '#999' }}>Click a node to edit</p>
)}
</div>
</div>
{selectedNode?.data?.componentName && (
<div className="right-block">
<div className="right-block-header">Item details</div>
<div className="right-block-body">
{loading ? (
<p>Loading...</p>
) : componentData ? (
<>
<div style={{ marginBottom: '10px' }}>
<strong style={{ display: 'block', marginBottom: '4px' }}>Component name:</strong>
{editingComponentName ? (
<input
type="text"
value={tempComponentName}
onChange={(e) => setTempComponentName(e.target.value)}
onBlur={handleSaveName}
onKeyDown={handleKeyDown}
autoFocus
style={{ width: '100%', padding: '4px', fontSize: '0.85em', boxSizing: 'border-box' }}
/>
) : (
<div
style={{
cursor: 'pointer',
padding: '4px 6px',
backgroundColor: '#f0f0f0',
borderRadius: '3px',
display: 'inline-block',
wordBreak: 'break-all'
}}
onClick={handleStartEditName}
title="Click to edit"
>
{currentComponentDisplayName || componentData.name}
</div>
)}
</div>
<p style={{ wordBreak: 'break-all', whiteSpace: 'normal', overflowWrap: 'break-word', marginTop: '8px' }}>
<strong>PDK name:</strong><br />{componentData.name}
</p>
<p><strong>Description:</strong><br />
Foundry: {componentData.foundry}<br />
Process: {componentData.process}<br />
Year: {componentData.year}<br />
Designer: {componentData.designer}
</p>
<p><strong>PDK:</strong> (string)</p>
<p><strong>Ports:</strong></p>
<ul style={{ paddingLeft: 20, margin: 0 }}>
{componentData.ports && Object.entries(componentData.ports).map(([portName, portInfo]) => (
<li key={portName} style={{ letterSpacing: '0.5px' }}>{portName}: {formatPort(portInfo)}</li>
))}
</ul>
<p><strong>Image:</strong></p>
<div style={{
border: '1px dashed #ccc', width: '100%', height: 80,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: '#999', background: '#fcfcfc', marginTop: 4,
overflow: 'hidden',
}}>
<img
src={`/api/component/${encodeURIComponent(componentData.name)}/image`}
alt="Component layout"
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', cursor: 'pointer' }}
onClick={() => setEnlarged(`/api/component/${encodeURIComponent(componentData.name)}/image`)}
onError={(e) => {
e.currentTarget.style.display = 'none';
e.currentTarget.parentElement.innerHTML = 'No image available';
}}
/>
</div>
</>
) : (
<p style={{ color: '#999' }}>No data</p>
)}
</div>
</div>
)}
<div className="right-block" style={{ marginTop: 'auto' }}>
<div className="right-block-header">Function block to be explored</div>
<div className="right-block-body placeholder-block">Reserved for future functionality</div>
</div>
{enlarged && (
<div
style={{
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: 'rgba(0,0,0,0.8)', zIndex: 1000,
display: 'flex', alignItems: 'center', justifyContent: 'center',
cursor: 'zoom-out',
}}
onClick={() => setEnlarged(null)}
>
<img
src={enlarged}
alt="Enlarged layout"
style={{ maxWidth: '90%', maxHeight: '90%', objectFit: 'contain' }}
/>
</div>
)}
</aside>
);
}, (prevProps, nextProps) => {
const prev = prevProps.selectedNode;
const next = nextProps.selectedNode;
if (prev?.id !== next?.id) return false;
if (prev?.position?.x !== next?.position?.x) return false;
if (prev?.position?.y !== next?.position?.y) return false;
if (prev?.data?.rotation !== next?.data?.rotation) return false;
if (prev?.data?.componentName !== next?.data?.componentName) return false;
if (prev?.data?.componentDisplayName !== next?.data?.componentDisplayName) return false;
if (prevProps.width !== nextProps.width) return false;
return true;
});
const ResizeHandle = ({ onMouseDown }) => (
<div
onMouseDown={onMouseDown}
style={{
width: 6, cursor: 'col-resize', background: 'transparent',
transition: 'background 0.2s', zIndex: 5, flexShrink: 0,
}}
onMouseEnter={(e) => e.currentTarget.style.background = '#ccc'}
onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
/>
);
function App() {
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const reactFlowInstance = useReactFlow();
const [library, setLibrary] = useState(null);
const [treeKey, setTreeKey] = useState(0);
const [expanded, setExpanded] = useState(false);
const treeContainerRef = useRef(null);
const [leftWidth, setLeftWidth] = useState(240);
const [rightWidth, setRightWidth] = useState(220);
const [dragging, setDragging] = useState(null);
const [gridSnap, setGridSnap] = useState(false);
const componentCounterRef = useRef(1);
const generateComponentDisplayName = useCallback(() => {
const name = `component_${componentCounterRef.current}`;
componentCounterRef.current += 1;
return name;
}, []);
const renameComponent = useCallback((nodeId, newComponentDisplayName) => {
setNodes(nds => nds.map(n => {
if (n.id === nodeId) {
return {
...n,
data: {
...n.data,
componentDisplayName: newComponentDisplayName
}
};
}
return n;
}));
}, [setNodes]);
const fetchLibrary = useCallback(async () => {
try {
const res = await fetch('/api/library');
const data = await res.json();
setLibrary(data);
} catch (err) {
console.error('Failed to fetch library', err);
}
}, []);
useEffect(() => { fetchLibrary(); }, [fetchLibrary]);
const selectedNode = useMemo(() => nodes.find(n => n.selected), [nodes]);
const onDragOver = useCallback((event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, []);
const onDrop = useCallback((event) => {
event.preventDefault();
const type = event.dataTransfer.getData('application/reactflow');
if (!type) return;
const position = reactFlowInstance.screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
const componentDisplayName = generateComponentDisplayName();
const newNode = {
id: Date.now().toString(),
type: 'rotatableNode',
position,
data: {
label: type,
componentName: type,
rotation: 0,
componentDisplayName: componentDisplayName
},
};
setNodes((nds) => nds.concat(newNode));
}, [setNodes, reactFlowInstance, generateComponentDisplayName]);
const onConnect = useCallback((connection) => {
setEdges((eds) => addEdge({ ...connection, type: 'straight' }, eds));
}, [setEdges]);
const expandAll = useCallback(() => {
if (treeContainerRef.current) {
treeContainerRef.current.querySelectorAll('details').forEach(d => d.open = true);
}
}, []);
const collapseAll = useCallback(() => setTreeKey(k => k + 1), []);
const handleToggle = useCallback(() => {
if (expanded) { collapseAll(); setExpanded(false); }
else { expandAll(); setExpanded(true); }
}, [expanded, expandAll, collapseAll]);
const handleResizeStart = useCallback((side) => (e) => {
e.preventDefault();
setDragging(side);
}, []);
useEffect(() => {
if (!dragging) return;
const onMouseMove = (e) => {
if (dragging === 'left') {
setLeftWidth(Math.min(500, Math.max(150, e.clientX)));
} else if (dragging === 'right') {
const newWidth = window.innerWidth - e.clientX;
setRightWidth(Math.min(500, Math.max(150, newWidth)));
}
};
const onMouseUp = () => setDragging(null);
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
return () => {
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
};
}, [dragging]);
const toggleGridSnap = useCallback(() => {
setGridSnap(prev => !prev);
}, []);
return (
<div style={{ display: 'flex', width: '100%', height: '100%', userSelect: dragging ? 'none' : 'auto' }}>
<LeftPanel
library={library} treeKey={treeKey} expanded={expanded}
onToggle={handleToggle} treeRef={treeContainerRef} width={leftWidth}
/>
<ResizeHandle onMouseDown={handleResizeStart('left')} />
<div style={{ flex: 1, position: 'relative' }}>
<div style={{
position: 'absolute', top: 10, right: 10, zIndex: 10,
display: 'flex', alignItems: 'center', gap: 8,
}}>
<span style={{
fontSize: '0.85em', fontWeight: 'bold', fontFamily: "'Sofia Pro', sans-serif",
color: '#333', userSelect: 'none'
}}>Grid lock</span>
<div
onClick={toggleGridSnap}
style={{
width: 48, height: 24, borderRadius: 12,
background: gridSnap ? '#28a745' : '#ccc',
cursor: 'pointer', display: 'flex', alignItems: 'center',
padding: '0 2px', transition: 'background 0.3s',
boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.2)',
}}
>
<div style={{
width: 20, height: 20, borderRadius: '50%',
background: '#fff', boxShadow: '0 1px 3px rgba(0,0,0,0.3)',
transform: gridSnap ? 'translateX(24px)' : 'translateX(0)',
transition: 'transform 0.2s',
}} />
</div>
</div>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onDragOver={onDragOver}
onDrop={onDrop}
onConnect={onConnect}
nodeTypes={{ rotatableNode: RotatableNode }}
fitView
snapToGrid={gridSnap}
snapGrid={[10, 10]}
nodesDraggable={true}
nodesConnectable={true}
elementsSelectable={true}
connectionRadius={50}
>
<Controls />
<Background />
</ReactFlow>
</div>
<ResizeHandle onMouseDown={handleResizeStart('right')} />
<RightPanel
selectedNode={selectedNode}
width={rightWidth}
onRenameComponent={renameComponent}
/>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ReactFlowProvider>
<App />
</ReactFlowProvider>
);
</script>
</body>
</html>
{% endraw %}
-15
View File
@@ -61,21 +61,6 @@ assert(
'save-layout response should include an svg_url for the new layout tab' 'save-layout response should include an svg_url for the new layout tab'
); );
assert( assert(
<<<<<<< HEAD
serverPy.includes('svg_ready') &&
serverPy.includes('svg_version') &&
serverPy.includes('file_version(svg_path)') &&
serverPy.includes("url_for('get_layout_svg', project_name=project, cell_name=cell, v=svg_version)"),
'save-layout response should only expose a versioned SVG URL after the preview file is ready'
);
assert(
serverPy.includes('temp_svg_path') &&
serverPy.includes('os.replace(temp_svg_path, svg_path)'),
'save-layout should publish generated SVG previews atomically instead of serving partially written files'
);
assert(
=======
>>>>>>> jingwen_main
serverPy.includes('RouterStackUnavailable') && serverPy.includes('RouterStackUnavailable') &&
serverPy.includes('except RouterStackUnavailable as e') && serverPy.includes('except RouterStackUnavailable as e') &&
serverPy.includes('"preview_status": preview_status') && serverPy.includes('"preview_status": preview_status') &&
-10
View File
@@ -33,16 +33,6 @@ assert(
'Build Layout should use the backend svg_url response' 'Build Layout should use the backend svg_url response'
); );
assert( assert(
<<<<<<< HEAD
canvasHtml.includes('result.svg_ready && result.svg_url') &&
canvasHtml.includes('buildLayoutRequestRef') &&
canvasHtml.includes('buildLayoutBusyRef') &&
canvasHtml.includes("cache: 'no-store'"),
'Build Layout should wait for a ready, versioned SVG response and prevent stale duplicate preview updates'
);
assert(
=======
>>>>>>> jingwen_main
canvasHtml.includes('result.preview_error') && canvasHtml.includes('result.preview_error') &&
canvasHtml.includes('Preview skipped: '), canvasHtml.includes('Preview skipped: '),
'Build Layout should log when the backend saves YAML but skips SVG preview because the router stack is unavailable' 'Build Layout should log when the backend saves YAML but skips SVG preview because the router stack is unavailable'