diff --git a/ComfyUI-Easy-Use.json b/ComfyUI-Easy-Use.json
index 6711810..97c6bae 100644
--- a/ComfyUI-Easy-Use.json
+++ b/ComfyUI-Easy-Use.json
@@ -42,6 +42,15 @@
"text": "提示词"
}
},
+ "easy promptList": {
+ "title": "提示词列表",
+ "inputs": {
+ "optional_prompt_list": "列表(可选)"
+ },
+ "outputs": {
+ "prompt_list": "列表"
+ }
+ },
"easy stylesSelector": {
"title": "风格提示词选择器",
"inputs": {
@@ -330,10 +339,12 @@
"pipe": "节点束",
"source_latent": "潜空间(源)",
"source_mask": "遮罩(源)",
- "new_mask": "遮罩(新)"
+ "destination_mask": "遮罩(目标)"
},
"widgets": {
- "text_combine": "文字拼接"
+ "text_combine": "文字拼接",
+ "text_combine_mode": "文字拼接模式",
+ "replace_text": "替换文字"
},
"outputs": {
"pipe": "节点束",
@@ -756,6 +767,18 @@
"positive_4": "反向条件_4"
}
},
+ "easy XYInputs: PositiveCondList": {
+ "title": "XY输入: 正向条件列表",
+ "inputs": {
+ "positive": "正向条件列表"
+ }
+ },
+ "easy XYInputs: NegativeCondList": {
+ "title": "XY输入: 反向条件列表",
+ "inputs": {
+ "negative": "反向条件列表"
+ }
+ },
"easy XYInputs: PromptSR": {
"title": "XY输入: 替换提示词",
"widgets": {
@@ -1178,7 +1201,8 @@
"images": "图像"
},
"widgets": {
- "filename_prefix": "图片名前缀"
+ "filename_prefix": "图片名前缀",
+ "only_preview": "仅预览"
}
},
"easy joinImageBatch": {
diff --git a/README.en.md b/README.en.md
index a106cc3..1199ff7 100644
--- a/README.en.md
+++ b/README.en.md
@@ -29,6 +29,13 @@ After installing the node package, the UI interface will be automatically switch
## Changelog
+**2024-01-29**
+
+- Changing the first-time install node package no longer automatically replaces the theme, you need to manually adjust and refresh the page
+- `easy imageSave` added **only_preivew**
+- Added `easy promptList`
+- Adjust the `easy latentCompositeMaskedWithCond` node
+
**v1.0.4(2024-01-28)**
- `easy kSamplerInpainting` Added the **patch** input value to be used with the FooocusInpaintLoader node
@@ -39,19 +46,23 @@ After installing the node package, the UI interface will be automatically switch
- Fixed `easy XYInputs: ControlNet` Error
- Fixed `easy loraStack` error when **toggle** is disabled
-**2024-01-22**
+
+2024-01-22
- Fixed `easy XYInputs: Sampler/Scheduler` Error
- Fixed the right menu has a problem when clicking the button
- Adjust the UI theme, divided into two sets of styles: the official default background and the dark black background, which can be switched in the color palette in the settings
+
-**2024-01-21**
+
+22024-01-21
- Modify the styles path to be compatible with other environments
- Fixed `easy comfyLoader` error
- Fixed xyPlot error when connecting to zero123
+
-**v1.0.3(2024-01-19)**
+v1.0.3(2024-01-19)**
- Fixed the error message in the loader when the prompt word was component
- Added `easy stylesSelector`
@@ -60,25 +71,33 @@ After installing the node package, the UI interface will be automatically switch
- Fixed `easy getNode` and `easy setNode` the title does not change when loading
- Fixed all samplers using subdirectories to store images
-**2024-01-12**
+
+2024-01-12
- Fixed UI loading failure in the new version of ComfyUI
+
-**2024-01-11**
+
+2024-01-11
- Replaced the font file used in the generation of XY diagrams
+
-**2024-01-09**
+
+2024-01-09
- Fixed XYPlot is not working when `a1111_prompt_style` is True
- An `autocomplete` folder has been added, If you have [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) installed, the txt files in that folder will be merged and overwritten to the autocomplete .txt file of the pyssss package at startup.
- Added a `📜Groups Map(EasyUse)` to the context menu.
+
-**2024-01-08**
+
+2024-01-08
- `easy XYInputs ModelMergeBlocks` Values can be imported from CSV files
- Fixed `easy pipeToBasicPipe` Bug
- Removed `easy imageRemBg`
+
**v1.0.2(2024-01-05)**
diff --git a/README.md b/README.md
index 1362266..224126c 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@
### UI界面美化
-安装节点包后会自动切换UI界面,如需更换其他主题请在 Settings -> Color Palette 中自行切换并刷新页面即可。
+首次安装的用户,如需使用本节点包UI主题,请在安装节点包在 Settings -> Color Palette 中自行切换并**刷新页面**即可。
### 简体中文节点
@@ -37,6 +37,13 @@
## 更新日志
+**2024-01-29**
+
+- 更改首次安装节点包不再自动替换主题,需手动调整并刷新页面
+- `easy imageSave` 增加 **only_preivew**
+- 增加 `easy promptList` - 提示词列表
+- 修改 `easy latentCompositeMaskedWithCond`
+
**v1.0.4(2024-01-28)**
- `easy kSamplerInpainting` 增加 **patch** 传入值,配合Fooocus内补节点使用
diff --git a/__init__.py b/__init__.py
index 085ba97..de407f9 100644
--- a/__init__.py
+++ b/__init__.py
@@ -87,4 +87,4 @@ WEB_DIRECTORY = "./web"
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', "WEB_DIRECTORY"]
-print('\033[34mComfy-Easy-Use (v1.0.3): \033[92mLoaded\033[0m')
\ No newline at end of file
+print('\033[34mComfy-Easy-Use (v1.0.4): \033[92mLoaded\033[0m')
\ No newline at end of file
diff --git a/py/easyNodes.py b/py/easyNodes.py
index 0737e43..da72a1e 100644
--- a/py/easyNodes.py
+++ b/py/easyNodes.py
@@ -1394,6 +1394,44 @@ class stylesPromptSelector:
return (positive_prompt, negative_prompt)
+#promptList
+class promptList:
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {"required": {
+ "prompt_1": ("STRING", {"multiline": True, "default": ""}),
+ "prompt_2": ("STRING", {"multiline": True, "default": ""}),
+ "prompt_3": ("STRING", {"multiline": True, "default": ""}),
+ "prompt_4": ("STRING", {"multiline": True, "default": ""}),
+ "prompt_5": ("STRING", {"multiline": True, "default": ""}),
+ },
+ "optional": {
+ "optional_prompt_list": ("LIST",)
+ }
+ }
+
+ RETURN_TYPES = ("LIST",)
+ RETURN_NAMES = ("prompt_list",)
+ FUNCTION = "run"
+ CATEGORY = "EasyUse/Prompt"
+
+ def run(self, **kwargs):
+ prompts = []
+
+ if "optional_prompt_list" in kwargs:
+ for l in kwargs["optional_prompt_list"]:
+ prompts.append(l)
+
+ # Iterate over the received inputs in sorted order.
+ for k in sorted(kwargs.keys()):
+ v = kwargs[k]
+
+ # Only process string input ports.
+ if isinstance(v, str) and v != '':
+ prompts.append(v)
+
+ return (prompts,)
+
# 肖像大师
# Created by AI Wiz Art (Stefano Flore)
# Version: 2.2
@@ -1669,14 +1707,17 @@ class latentCompositeMaskedWithCond:
return {
"required": {
"pipe": ("PIPE_LINE",),
- "text_combine": ("STRING", {"default": ""}),
+ "text_combine": ("LIST",),
"source_latent": ("LATENT",),
"source_mask": ("MASK",),
- "new_mask": ("MASK",),
+ "destination_mask": ("MASK",),
+ "text_combine_mode": (["add", "replace", "cover"], {"default": "add"}),
+ "replace_text": ("STRING", {"default": ""})
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
+ OUTPUT_IS_LIST = (False, False, True)
RETURN_TYPES = ("PIPE_LINE", "LATENT", "CONDITIONING")
RETURN_NAMES = ("pipe", "latent", "conditioning",)
FUNCTION = "run"
@@ -1684,43 +1725,53 @@ class latentCompositeMaskedWithCond:
CATEGORY = "EasyUse/Latent"
- def run(self, pipe, text_combine, source_latent, source_mask, new_mask, prompt=None, extra_pnginfo=None, my_unique_id=None):
+ def run(self, pipe, text_combine, source_latent, source_mask, destination_mask, text_combine_mode, replace_text, prompt=None, extra_pnginfo=None, my_unique_id=None):
+
clip = pipe["clip"]
destination_latent = pipe["samples"]
- positive = pipe["loader_settings"]["positive"] + ',' + text_combine
- positive_token_normalization = pipe["loader_settings"]["positive_token_normalization"]
- positive_weight_interpretation = pipe["loader_settings"]["positive_weight_interpretation"]
- a1111_prompt_style = pipe["loader_settings"]["a1111_prompt_style"]
- positive_cond = pipe["positive"]
- log_node_warn("正在处理提示词编码...")
- # Use new clip text encode by smzNodes like same as webui, when if you installed the smzNodes
- if a1111_prompt_style:
- if "smZ CLIPTextEncode" in ALL_NODE_CLASS_MAPPINGS:
- cls = ALL_NODE_CLASS_MAPPINGS['smZ CLIPTextEncode']
- steps = pipe["loader_settings"]["steps"] if "steps" in pipe["loader_settings"] else 5
- positive_embeddings_final, = cls().encode(clip, positive, "A1111", True, True, False, False, 6, 1024,
- 1024, 0, 0, 1024, 1024, '', '', steps)
+ conds = []
+
+ for text in text_combine:
+ if text_combine_mode == 'cover':
+ positive = text
+ elif text_combine_mode == 'replace' and replace_text != '':
+ positive = pipe["loader_settings"]["positive"].replace(replace_text, text)
else:
- raise Exception(f"[ERROR] To use clip text encode same as webui, you need to install 'smzNodes'")
- else:
- positive_embeddings_final, positive_pooled = advanced_encode(clip, positive,
- positive_token_normalization,
- positive_weight_interpretation, w_max=1.0,
- apply_to_pooled='enable')
- positive_embeddings_final = [[positive_embeddings_final, {"pooled_output": positive_pooled}]]
+ positive = pipe["loader_settings"]["positive"] + ',' + text
+ positive_token_normalization = pipe["loader_settings"]["positive_token_normalization"]
+ positive_weight_interpretation = pipe["loader_settings"]["positive_weight_interpretation"]
+ a1111_prompt_style = pipe["loader_settings"]["a1111_prompt_style"]
+ positive_cond = pipe["positive"]
- # source cond
- (cond_1,) = ConditioningSetMask().append(positive_cond, source_mask, "default", 1)
- (cond_2,) = ConditioningSetMask().append(positive_embeddings_final, new_mask, "default", 1)
- positive_cond = cond_1 + cond_2
+ log_node_warn("正在处理提示词编码...")
+ # Use new clip text encode by smzNodes like same as webui, when if you installed the smzNodes
+ if a1111_prompt_style:
+ if "smZ CLIPTextEncode" in ALL_NODE_CLASS_MAPPINGS:
+ cls = ALL_NODE_CLASS_MAPPINGS['smZ CLIPTextEncode']
+ steps = pipe["loader_settings"]["steps"] if "steps" in pipe["loader_settings"] else 5
+ positive_embeddings_final, = cls().encode(clip, positive, "A1111", True, True, False, False, 6, 1024,
+ 1024, 0, 0, 1024, 1024, '', '', steps)
+ else:
+ raise Exception(f"[ERROR] To use clip text encode same as webui, you need to install 'smzNodes'")
+ else:
+ positive_embeddings_final, positive_pooled = advanced_encode(clip, positive,
+ positive_token_normalization,
+ positive_weight_interpretation, w_max=1.0,
+ apply_to_pooled='enable')
+ positive_embeddings_final = [[positive_embeddings_final, {"pooled_output": positive_pooled}]]
+ # source cond
+ (cond_1,) = ConditioningSetMask().append(positive_cond, source_mask, "default", 1)
+ (cond_2,) = ConditioningSetMask().append(positive_embeddings_final, destination_mask, "default", 1)
+ positive_cond = cond_1 + cond_2
+
+ conds.append(positive_cond)
# latent composite masked
(samples,) = LatentCompositeMasked().composite(destination_latent, source_latent, 0, 0, False)
new_pipe = {
**pipe,
- "positive": positive_cond,
"samples": samples,
"loader_settings": {
**pipe["loader_settings"],
@@ -1730,7 +1781,7 @@ class latentCompositeMaskedWithCond:
del pipe
- return (new_pipe, samples, positive_cond)
+ return (new_pipe, samples, conds)
# 随机种
class easySeed:
@@ -4979,6 +5030,58 @@ class XYplot_Negative_Cond:
return ({"axis": axis, "values": values, "cond": cond},) if values is not None else (None,)
+# XYPlot Pos Condition List
+class XYplot_Positive_Cond_List:
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "positive": ("CONDITIONING",),
+ }
+ }
+
+ INPUT_IS_LIST = True
+ RETURN_TYPES = ("X_Y",)
+ RETURN_NAMES = ("X or Y",)
+ FUNCTION = "xy_value"
+ CATEGORY = "EasyUse/XY Inputs"
+
+ def xy_value(self, positive):
+ axis = "advanced: Pos Condition"
+ values = []
+ cond = []
+ for index, c in enumerate(positive):
+ values.append(str(index))
+ cond.append(c)
+
+ return ({"axis": axis, "values": values, "cond": cond},) if values is not None else (None,)
+
+# XYPlot Neg Condition List
+class XYplot_Negative_Cond_List:
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "negative": ("CONDITIONING",),
+ }
+ }
+
+ INPUT_IS_LIST = True
+ RETURN_TYPES = ("X_Y",)
+ RETURN_NAMES = ("X or Y",)
+ FUNCTION = "xy_value"
+ CATEGORY = "EasyUse/XY Inputs"
+
+ def xy_value(self, negative):
+ axis = "advanced: Neg Condition"
+ values = []
+ cond = []
+ for index, c in enumerate(negative):
+ values.append(index)
+ cond.append(c)
+
+ return ({"axis": axis, "values": values, "cond": cond},) if values is not None else (None,)
+
# XY Plot: ControlNet
class XYplot_Control_Net:
parameters = ["strength", "start_percent", "end_percent"]
@@ -5137,6 +5240,7 @@ NODE_CLASS_MAPPINGS = {
"easy positive": positivePrompt,
"easy negative": negativePrompt,
"easy wildcards": wildcardsPrompt,
+ "easy promptList": promptList,
"easy stylesSelector": stylesPromptSelector,
"easy portraitMaster": portraitMaster,
# loaders 加载器
@@ -5189,7 +5293,9 @@ NODE_CLASS_MAPPINGS = {
"easy XYInputs: PromptSR": XYplot_PromptSR,
"easy XYInputs: ControlNet": XYplot_Control_Net,
"easy XYInputs: PositiveCond": XYplot_Positive_Cond,
+ "easy XYInputs: PositiveCondList": XYplot_Positive_Cond_List,
"easy XYInputs: NegativeCond": XYplot_Negative_Cond,
+ "easy XYInputs: NegativeCondList": XYplot_Negative_Cond_List,
# others 其他
"easy showSpentTime": showSpentTime,
# "easy imageRemoveBG": imageREMBG,
@@ -5202,6 +5308,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy positive": "Positive",
"easy negative": "Negative",
"easy wildcards": "Wildcards",
+ "easy promptList": "PromptList",
"easy stylesSelector": "Styles Selector",
"easy portraitMaster": "Portrait Master",
# loaders 加载器
@@ -5254,8 +5361,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy XYInputs: ModelMergeBlocks": "XY Inputs: ModelMergeBlocks //EasyUse",
"easy XYInputs: PromptSR": "XY Inputs: PromptSR //EasyUse",
"easy XYInputs: ControlNet": "XY Inputs: Controlnet //EasyUse",
- "easy XYInputs: PositiveCond": "XY Inputs: PositiveCond //EasyUse",
- "easy XYInputs: NegativeCond": "XY Inputs: NegativeCond //EasyUse",
+ "easy XYInputs: PositiveCond": "XY Inputs: PosCond //EasyUse",
+ "easy XYInputs: PositiveCondList": "XY Inputs: PosCondList //EasyUse",
+ "easy XYInputs: NegativeCond": "XY Inputs: NegCond //EasyUse",
+ "easy XYInputs: NegativeCondList": "XY Inputs: NegCondList //EasyUse",
# others 其他
"easy showSpentTime": "ShowSpentTime",
"easy imageRemoveBG": "ImageRemoveBG",
diff --git a/py/image.py b/py/image.py
index d07a60c..b7e8854 100644
--- a/py/image.py
+++ b/py/image.py
@@ -2,6 +2,7 @@ from PIL import Image
from PIL.PngImagePlugin import PngInfo
from enum import Enum
import datetime
+import random
import re
import json
import os
@@ -410,12 +411,17 @@ class imageSaveSimple:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
+ self.prefix_append = ""
+ self.compress_level = 4
@classmethod
def INPUT_TYPES(s):
return {"required":
- {"images": ("IMAGE",),
- "filename_prefix": ("STRING", {"default": "ComfyUI"}),},
+ {
+ "images": ("IMAGE",),
+ "filename_prefix": ("STRING", {"default": "ComfyUI"}),
+ "only_preview": ("BOOLEAN", {"default": False}),
+ },
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
@@ -450,10 +456,23 @@ class imageSaveSimple:
text = text.replace(format_str, str(date_formats[format_str](date)))
return text
- def save(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
+ def save(self, images, filename_prefix="ComfyUI", only_preview=False, prompt=None, extra_pnginfo=None):
+
+ if only_preview:
+ self.output_dir = folder_paths.get_temp_directory()
+ self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5))
+ self.type = 'temp'
+ self.compress_level = 1
+ else:
+ self.output_dir = folder_paths.get_output_directory()
+ self.type = "output"
+ self.prefix_append = ""
+ self.compress_level = 4
filename_prefix = re.sub(r'%date:(.*?)%', lambda m: self._format_date(m.group(1), datetime.datetime.now()),
filename_prefix)
+
+ filename_prefix += self.prefix_append
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
results = list()
@@ -470,7 +489,7 @@ class imageSaveSimple:
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
file = f"{filename}_{counter:05}_.png"
- img.save(os.path.join(full_output_folder, file), pnginfo=metadata)
+ img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=self.compress_level)
results.append({
"filename": file,
"subfolder": subfolder,
diff --git a/py/logic.py b/py/logic.py
index 8e4bbd5..cf8f0da 100644
--- a/py/logic.py
+++ b/py/logic.py
@@ -248,6 +248,33 @@ class Boolean:
def execute(self, value):
return (value,)
+# ---------------------------------------------------------------开关 开始----------------------------------------------------------------------#
+class imageSwitch:
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(cls):
+ return {
+ "required": {
+ "image_a": ("IMAGE",),
+ "image_b": ("IMAGE",),
+ "boolean": ("BOOLEAN", {"default": False}),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE",)
+ FUNCTION = "image_switch"
+
+ CATEGORY = "EasyUse/Logic/Switch"
+
+ def image_switch(self, image_a, image_b, boolean):
+
+ if boolean:
+ return (image_a, )
+ else:
+ return (image_b, )
+
# ---------------------------------------------------------------运算 开始----------------------------------------------------------------------#
COMPARE_FUNCTIONS = {
@@ -404,6 +431,7 @@ NODE_CLASS_MAPPINGS = {
"easy rangeFloat": RangeFloat,
"easy boolean": Boolean,
"easy compare": Compare,
+ "easy imageSwitch": imageSwitch,
"easy if": If,
"easy xyAny": xyAny,
"easy convertAnything": ConvertAnything,
@@ -417,6 +445,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"easy rangeFloat": "Range(Float)",
"easy boolean": "Boolean",
"easy compare": "Compare",
+ "easy imageSwitch": "Image Switch",
"easy if": "If",
"easy xyAny": "XYAny",
"easy convertAnything": "ConvertAnything",
diff --git a/web/js/easy/easyDynamicWidgets.js b/web/js/easy/easyDynamicWidgets.js
index 68d6e19..5343fd5 100644
--- a/web/js/easy/easyDynamicWidgets.js
+++ b/web/js/easy/easyDynamicWidgets.js
@@ -167,6 +167,14 @@ function widgetLogic(node, widget) {
widget.options = {on: 'Enabled', off: 'Disabled'}
}
+ if(widget.name == 'text_combine_mode'){
+ if(widget.value == 'replace'){
+ toggleWidget(node, findWidgetByName(node, 'replace_text'), true)
+ }else{
+ toggleWidget(node, findWidgetByName(node, 'replace_text'))
+ }
+ updateNodeHeight(node)
+ }
}
function widgetLogic2(node, widget) {
@@ -344,6 +352,7 @@ app.registerExtension({
case "easy XYInputs: ControlNet":
case "easy rangeInt":
case "easy rangeFloat":
+ case 'easy latentCompositeMaskedWithCond':
getSetters(node)
break
case "easy wildcards":
@@ -731,7 +740,7 @@ const getSetWidgets = ['rescale_after_model', 'rescale', 'image_output',
'refiner_lora1_name', 'refiner_lora2_name', 'upscale_method',
'image_output', 'add_noise', 'info', 'sampler_name',
'ckpt_B_name', 'ckpt_C_name', 'save_model', 'refiner_ckpt_name',
- 'num_loras', 'mode', 'toggle', 'resolution', 'target_parameter', 'input_count', 'replace_count', 'downscale_mode', 'range_mode']
+ 'num_loras', 'mode', 'toggle', 'resolution', 'target_parameter', 'input_count', 'replace_count', 'downscale_mode', 'range_mode','text_combine_mode']
function getSetters(node) {
if (node.widgets)
diff --git a/web/js/easy/easyInterface.js b/web/js/easy/easyInterface.js
index 54667eb..d0d8dba 100644
--- a/web/js/easy/easyInterface.js
+++ b/web/js/easy/easyInterface.js
@@ -106,7 +106,7 @@ try{
ui2.colors.litegraph_base.CLEAR_BACKGROUND_COLOR = '#000'
custom_theme[ui2.id] = ui2
localStorage.setItem('Comfy.Settings.Comfy.CustomColorPalettes', JSON.stringify(custom_theme));
- localStorage.setItem('Comfy.Settings.Comfy.ColorPalette',`"${custom_theme_name}"`)
+ // localStorage.setItem('Comfy.Settings.Comfy.ColorPalette',`"${custom_theme_name}"`)
}
const theme_name = localStorage.getItem('Comfy.Settings.Comfy.ColorPalette')
// 兼容 ComfyUI Revision: 1887 [235727fe] 以上版本
@@ -834,6 +834,7 @@ const COLOR_THEMES = LGraphCanvas.node_colors
const NODE_COLORS = {
"easy positive":"green",
"easy negative":"red",
+ "easy promptList":"cyan",
"easy XYInputs: Seeds++ Batch": customXYLink,
"easy XYInputs: ModelMergeBlocks": customXYLink,
}