fb1ffd4d2aea1b996230b8c8528d58e19308ce05ffb2f5c4d89d42fa73455684
Browse files- sd-webui-3d-open-pose-editor/public/icons/icon-144x144.png +0 -0
- sd-webui-3d-open-pose-editor/public/icons/icon-152x152.png +0 -0
- sd-webui-3d-open-pose-editor/public/icons/icon-192x192.png +0 -0
- sd-webui-3d-open-pose-editor/public/icons/icon-384x384.png +0 -0
- sd-webui-3d-open-pose-editor/public/icons/icon-48x48.png +0 -0
- sd-webui-3d-open-pose-editor/public/icons/icon-512x512.png +0 -0
- sd-webui-3d-open-pose-editor/public/icons/icon-72x72.png +0 -0
- sd-webui-3d-open-pose-editor/public/icons/icon-96x96.png +0 -0
- sd-webui-3d-open-pose-editor/public/mstile-150x150.png +0 -0
- sd-webui-3d-open-pose-editor/public/safari-pinned-tab.svg +61 -0
- sd-webui-3d-open-pose-editor/scripts/__pycache__/openpose_editor.cpython-310.pyc +0 -0
- sd-webui-3d-open-pose-editor/scripts/openpose_editor.py +276 -0
- sd-webui-3d-open-pose-editor/src/assets.ts +9 -0
- sd-webui-3d-open-pose-editor/src/body.ts +1293 -0
- sd-webui-3d-open-pose-editor/src/components/ContextMenu/index.tsx +145 -0
- sd-webui-3d-open-pose-editor/src/components/ContextMenu/styles.module.css +132 -0
- sd-webui-3d-open-pose-editor/src/components/Dialog/index.tsx +103 -0
- sd-webui-3d-open-pose-editor/src/components/Dialog/styles.module.css +124 -0
- sd-webui-3d-open-pose-editor/src/components/Loading/index.tsx +83 -0
- sd-webui-3d-open-pose-editor/src/components/Loading/styles.module.css +147 -0
- sd-webui-3d-open-pose-editor/src/components/Menu/index.tsx +416 -0
- sd-webui-3d-open-pose-editor/src/components/Menu/styles.module.css +131 -0
- sd-webui-3d-open-pose-editor/src/components/Oops.tsx +19 -0
- sd-webui-3d-open-pose-editor/src/components/PopupOver/index.tsx +348 -0
- sd-webui-3d-open-pose-editor/src/components/PopupOver/styles.module.css +115 -0
- sd-webui-3d-open-pose-editor/src/components/Slider/index.tsx +38 -0
- sd-webui-3d-open-pose-editor/src/components/Slider/styles.module.css +26 -0
- sd-webui-3d-open-pose-editor/src/components/Toast/index.tsx +91 -0
- sd-webui-3d-open-pose-editor/src/components/Toast/styles.module.css +142 -0
- sd-webui-3d-open-pose-editor/src/defines.ts +209 -0
- sd-webui-3d-open-pose-editor/src/editor.ts +1875 -0
- sd-webui-3d-open-pose-editor/src/entry.ts +3 -0
- sd-webui-3d-open-pose-editor/src/env.d.ts +3 -0
- sd-webui-3d-open-pose-editor/src/environments/extension/@types/webui.d.ts +6 -0
- sd-webui-3d-open-pose-editor/src/environments/extension/@types/window.d.ts +25 -0
- sd-webui-3d-open-pose-editor/src/environments/extension/assets.ts +19 -0
- sd-webui-3d-open-pose-editor/src/environments/extension/entry.ts +217 -0
- sd-webui-3d-open-pose-editor/src/environments/extension/internal/gradio.ts +78 -0
- sd-webui-3d-open-pose-editor/src/environments/extension/internal/message.ts +62 -0
- sd-webui-3d-open-pose-editor/src/environments/online/App.module.css +42 -0
- sd-webui-3d-open-pose-editor/src/environments/online/App.tsx +284 -0
- sd-webui-3d-open-pose-editor/src/environments/online/helper.ts +208 -0
- sd-webui-3d-open-pose-editor/src/environments/online/index.css +21 -0
- sd-webui-3d-open-pose-editor/src/environments/online/init.ts +8 -0
- sd-webui-3d-open-pose-editor/src/environments/online/main.tsx +18 -0
- sd-webui-3d-open-pose-editor/src/environments/online/update.ts +31 -0
- sd-webui-3d-open-pose-editor/src/hooks/index.ts +90 -0
- sd-webui-3d-open-pose-editor/src/hooks/useEventCall.ts +22 -0
- sd-webui-3d-open-pose-editor/src/hooks/useFoceUpdate.ts +11 -0
- sd-webui-3d-open-pose-editor/src/hooks/useMessageDispatch.ts +124 -0
sd-webui-3d-open-pose-editor/public/icons/icon-144x144.png
ADDED
|
|
sd-webui-3d-open-pose-editor/public/icons/icon-152x152.png
ADDED
|
|
sd-webui-3d-open-pose-editor/public/icons/icon-192x192.png
ADDED
|
|
sd-webui-3d-open-pose-editor/public/icons/icon-384x384.png
ADDED
|
|
sd-webui-3d-open-pose-editor/public/icons/icon-48x48.png
ADDED
|
|
sd-webui-3d-open-pose-editor/public/icons/icon-512x512.png
ADDED
|
|
sd-webui-3d-open-pose-editor/public/icons/icon-72x72.png
ADDED
|
|
sd-webui-3d-open-pose-editor/public/icons/icon-96x96.png
ADDED
|
|
sd-webui-3d-open-pose-editor/public/mstile-150x150.png
ADDED
|
sd-webui-3d-open-pose-editor/public/safari-pinned-tab.svg
ADDED
|
|
sd-webui-3d-open-pose-editor/scripts/__pycache__/openpose_editor.cpython-310.pyc
ADDED
|
Binary file (7.69 kB). View file
|
|
|
sd-webui-3d-open-pose-editor/scripts/openpose_editor.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import html
|
| 2 |
+
import json
|
| 3 |
+
import os.path
|
| 4 |
+
import pathlib
|
| 5 |
+
import typing
|
| 6 |
+
import urllib.parse
|
| 7 |
+
|
| 8 |
+
import gradio as gr
|
| 9 |
+
|
| 10 |
+
try:
|
| 11 |
+
root_path = pathlib.Path(__file__).resolve().parents[1]
|
| 12 |
+
except NameError:
|
| 13 |
+
import inspect
|
| 14 |
+
|
| 15 |
+
root_path = pathlib.Path(inspect.getfile(lambda: None)).resolve().parents[1]
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def get_asset_url(
|
| 19 |
+
file_path: pathlib.Path, append: typing.Optional[dict[str, str]] = None
|
| 20 |
+
) -> str:
|
| 21 |
+
if append is None:
|
| 22 |
+
append = {"v": str(os.path.getmtime(file_path))}
|
| 23 |
+
else:
|
| 24 |
+
append = append.copy()
|
| 25 |
+
append["v"] = str(os.path.getmtime(file_path))
|
| 26 |
+
return f"/file={file_path.absolute()}?{urllib.parse.urlencode(append)}"
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def write_config_file() -> pathlib.Path:
|
| 30 |
+
assets = {
|
| 31 |
+
"models/hand.fbx": get_asset_url(root_path / "models" / "hand.fbx"),
|
| 32 |
+
"models/foot.fbx": get_asset_url(root_path / "models" / "foot.fbx"),
|
| 33 |
+
"src/poses/data.bin": get_asset_url(root_path / "src" / "poses" / "data.bin"),
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
MEDIAPIPE_POSE_VERSION = "0.5.1675469404"
|
| 37 |
+
mediapipe_dir = root_path / "downloads" / "pose" / MEDIAPIPE_POSE_VERSION
|
| 38 |
+
for file_name in [
|
| 39 |
+
"pose_landmark_full.tflite",
|
| 40 |
+
"pose_web.binarypb",
|
| 41 |
+
"pose_solution_packed_assets.data",
|
| 42 |
+
"pose_solution_simd_wasm_bin.wasm",
|
| 43 |
+
"pose_solution_packed_assets_loader.js",
|
| 44 |
+
"pose_solution_simd_wasm_bin.js",
|
| 45 |
+
]:
|
| 46 |
+
file_path = mediapipe_dir / file_name
|
| 47 |
+
if not file_path.exists():
|
| 48 |
+
continue
|
| 49 |
+
assets[file_name] = get_asset_url(file_path.absolute())
|
| 50 |
+
|
| 51 |
+
consts = {"assets": assets}
|
| 52 |
+
|
| 53 |
+
config_dir = root_path / "downloads"
|
| 54 |
+
config_dir.mkdir(mode=0o755, parents=True, exist_ok=True)
|
| 55 |
+
config_path = config_dir / "config.json"
|
| 56 |
+
config_path.write_text(json.dumps(consts))
|
| 57 |
+
return config_path
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def on_ui_tabs():
|
| 61 |
+
with gr.Blocks(analytics_enabled=False) as blocks:
|
| 62 |
+
create_ui()
|
| 63 |
+
return [(blocks, "3D Openpose", "threedopenpose")]
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def create_ui():
|
| 67 |
+
try:
|
| 68 |
+
from modules.shared import opts
|
| 69 |
+
|
| 70 |
+
cn_max: int = opts.control_net_max_models_num
|
| 71 |
+
use_online: bool = opts.openpose3d_use_online_version
|
| 72 |
+
except (ImportError, AttributeError):
|
| 73 |
+
cn_max = 0
|
| 74 |
+
use_online = False
|
| 75 |
+
|
| 76 |
+
if use_online:
|
| 77 |
+
html_url = "https://zhuyu1997.github.io/open-pose-editor/"
|
| 78 |
+
else:
|
| 79 |
+
config = {"config": get_asset_url(write_config_file()) or ""}
|
| 80 |
+
html_url = get_asset_url(root_path / "pages" / "index.html", config)
|
| 81 |
+
|
| 82 |
+
with gr.Tabs(elem_id="openpose3d_main"):
|
| 83 |
+
with gr.Tab(label="Edit Openpose"):
|
| 84 |
+
gr.HTML(
|
| 85 |
+
f"""
|
| 86 |
+
<iframe id="openpose3d_iframe" src="{html.escape(html_url)}"></iframe>
|
| 87 |
+
"""
|
| 88 |
+
)
|
| 89 |
+
gr.Markdown(
|
| 90 |
+
"Original: [Online 3D Openpose Editor](https://zhuyu1997.github.io/open-pose-editor/)"
|
| 91 |
+
)
|
| 92 |
+
with gr.Tab(label="Send to ControlNet"):
|
| 93 |
+
with gr.Row():
|
| 94 |
+
send_t2i = gr.Button(value="Send to txt2img", variant="primary")
|
| 95 |
+
send_i2i = gr.Button(value="Send to img2img", variant="primary")
|
| 96 |
+
with gr.Row():
|
| 97 |
+
cn_dropdown_list = [str(i) for i in range(cn_max)]
|
| 98 |
+
cn_dropdown_list.insert(0, "-")
|
| 99 |
+
with gr.Column(variant="panel"):
|
| 100 |
+
pose_image = gr.Image(
|
| 101 |
+
label="Pose",
|
| 102 |
+
elem_id="openpose3d_pose_image",
|
| 103 |
+
)
|
| 104 |
+
with gr.Row():
|
| 105 |
+
pose_target = gr.Dropdown(
|
| 106 |
+
label="Control Model number",
|
| 107 |
+
choices=cn_dropdown_list,
|
| 108 |
+
value="0" if cn_max >= 1 else "-",
|
| 109 |
+
)
|
| 110 |
+
pose_download = gr.Button(value="Download")
|
| 111 |
+
with gr.Column(variant="panel"):
|
| 112 |
+
depth_image = gr.Image(
|
| 113 |
+
label="Depth",
|
| 114 |
+
elem_id="openpose3d_depth_image",
|
| 115 |
+
)
|
| 116 |
+
with gr.Row():
|
| 117 |
+
depth_target = gr.Dropdown(
|
| 118 |
+
label="Control Model number",
|
| 119 |
+
choices=cn_dropdown_list,
|
| 120 |
+
value="1" if cn_max >= 2 else "-",
|
| 121 |
+
)
|
| 122 |
+
depth_download = gr.Button(value="Download")
|
| 123 |
+
with gr.Column(variant="panel"):
|
| 124 |
+
normal_image = gr.Image(
|
| 125 |
+
label="Normal",
|
| 126 |
+
elem_id="openpose3d_normal_image",
|
| 127 |
+
)
|
| 128 |
+
with gr.Row():
|
| 129 |
+
normal_target = gr.Dropdown(
|
| 130 |
+
label="Control Model number",
|
| 131 |
+
choices=cn_dropdown_list,
|
| 132 |
+
value="2" if cn_max >= 3 else "-",
|
| 133 |
+
)
|
| 134 |
+
normal_download = gr.Button(value="Download")
|
| 135 |
+
with gr.Column(variant="panel"):
|
| 136 |
+
canny_image = gr.Image(
|
| 137 |
+
label="Canny",
|
| 138 |
+
elem_id="openpose3d_canny_image",
|
| 139 |
+
)
|
| 140 |
+
with gr.Row():
|
| 141 |
+
canny_target = gr.Dropdown(
|
| 142 |
+
label="Control Model number",
|
| 143 |
+
choices=cn_dropdown_list,
|
| 144 |
+
value="3" if cn_max >= 4 else "-",
|
| 145 |
+
)
|
| 146 |
+
canny_download = gr.Button(value="Download")
|
| 147 |
+
|
| 148 |
+
send_cn_inputs = [
|
| 149 |
+
pose_image,
|
| 150 |
+
pose_target,
|
| 151 |
+
depth_image,
|
| 152 |
+
depth_target,
|
| 153 |
+
normal_image,
|
| 154 |
+
normal_target,
|
| 155 |
+
canny_image,
|
| 156 |
+
canny_target,
|
| 157 |
+
]
|
| 158 |
+
send_t2i.click(
|
| 159 |
+
None,
|
| 160 |
+
send_cn_inputs,
|
| 161 |
+
None,
|
| 162 |
+
_js="window.openpose3d.sendTxt2img",
|
| 163 |
+
)
|
| 164 |
+
send_i2i.click(
|
| 165 |
+
None,
|
| 166 |
+
send_cn_inputs,
|
| 167 |
+
None,
|
| 168 |
+
_js="window.openpose3d.sendImg2img",
|
| 169 |
+
)
|
| 170 |
+
pose_download.click(
|
| 171 |
+
None,
|
| 172 |
+
pose_image,
|
| 173 |
+
None,
|
| 174 |
+
_js="(v) => window.openpose3d.downloadImage(v, 'pose')",
|
| 175 |
+
)
|
| 176 |
+
depth_download.click(
|
| 177 |
+
None,
|
| 178 |
+
depth_image,
|
| 179 |
+
None,
|
| 180 |
+
_js="(v) => window.openpose3d.downloadImage(v, 'depth')",
|
| 181 |
+
)
|
| 182 |
+
normal_download.click(
|
| 183 |
+
None,
|
| 184 |
+
normal_image,
|
| 185 |
+
None,
|
| 186 |
+
_js="(v) => window.openpose3d.downloadImage(v, 'normal')",
|
| 187 |
+
)
|
| 188 |
+
canny_download.click(
|
| 189 |
+
None,
|
| 190 |
+
canny_image,
|
| 191 |
+
None,
|
| 192 |
+
_js="(v) => window.openpose3d.downloadImage(v, 'canny')",
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def on_ui_settings():
|
| 197 |
+
from modules.shared import OptionInfo, opts
|
| 198 |
+
|
| 199 |
+
section = ("openpose3d", "3D Openpose Editor")
|
| 200 |
+
|
| 201 |
+
opts.add_option(
|
| 202 |
+
"openpose3d_use_online_version",
|
| 203 |
+
OptionInfo(False, "Use online version", section=section),
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def main():
|
| 208 |
+
js_path = root_path / "javascript" / "index.js"
|
| 209 |
+
css_path = root_path / "style.css"
|
| 210 |
+
|
| 211 |
+
original_template_response = gr.routes.templates.TemplateResponse
|
| 212 |
+
head = """
|
| 213 |
+
<script>
|
| 214 |
+
function waitForElement(parent, selector) {
|
| 215 |
+
return new Promise((resolve) => {
|
| 216 |
+
const observer = new MutationObserver(() => {
|
| 217 |
+
if (!parent.querySelector(selector)) {
|
| 218 |
+
return
|
| 219 |
+
}
|
| 220 |
+
observer.disconnect()
|
| 221 |
+
resolve(undefined)
|
| 222 |
+
})
|
| 223 |
+
|
| 224 |
+
observer.observe(parent, {
|
| 225 |
+
childList: true,
|
| 226 |
+
subtree: true,
|
| 227 |
+
})
|
| 228 |
+
|
| 229 |
+
if (parent.querySelector(selector)) {
|
| 230 |
+
resolve(undefined)
|
| 231 |
+
}
|
| 232 |
+
})
|
| 233 |
+
}
|
| 234 |
+
let onTabChangedCallback
|
| 235 |
+
function gradioApp() {
|
| 236 |
+
const elems = document.getElementsByTagName('gradio-app')
|
| 237 |
+
const gradioShadowRoot = elems.length == 0 ? null : elems[0].shadowRoot
|
| 238 |
+
return gradioShadowRoot ? gradioShadowRoot : document
|
| 239 |
+
}
|
| 240 |
+
async function onUiLoaded(callback){
|
| 241 |
+
await waitForElement(gradioApp(), '#openpose3d_main')
|
| 242 |
+
await callback()
|
| 243 |
+
await onTabChangedCallback?.()
|
| 244 |
+
}
|
| 245 |
+
function onUiUpdate(callback){
|
| 246 |
+
onTabChangedCallback = callback
|
| 247 |
+
}
|
| 248 |
+
</script>
|
| 249 |
+
"""
|
| 250 |
+
head += f"""
|
| 251 |
+
<script type="module">
|
| 252 |
+
document.addEventListener("DOMContentLoaded", function() {{import("{get_asset_url(js_path)}")}})
|
| 253 |
+
</script>
|
| 254 |
+
"""
|
| 255 |
+
|
| 256 |
+
def template_response(*args, **kwargs):
|
| 257 |
+
res = original_template_response(*args, **kwargs)
|
| 258 |
+
res.body = res.body.replace(b"</head>", f"{head}</head>".encode("utf8"))
|
| 259 |
+
res.init_headers()
|
| 260 |
+
return res
|
| 261 |
+
|
| 262 |
+
gr.routes.templates.TemplateResponse = template_response
|
| 263 |
+
|
| 264 |
+
with gr.Blocks(analytics_enabled=False, css=css_path.read_text()) as blocks:
|
| 265 |
+
with gr.Tab(label="3D Openpose", elem_id="tab_threedopenpose"):
|
| 266 |
+
create_ui()
|
| 267 |
+
blocks.launch()
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
try:
|
| 271 |
+
from modules import script_callbacks
|
| 272 |
+
|
| 273 |
+
script_callbacks.on_ui_tabs(on_ui_tabs)
|
| 274 |
+
script_callbacks.on_ui_settings(on_ui_settings)
|
| 275 |
+
except ImportError:
|
| 276 |
+
main()
|
sd-webui-3d-open-pose-editor/src/assets.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import handFBXFileUrl from '../models/hand.fbx?url'
|
| 2 |
+
import footFBXFileUrl from '../models/foot.fbx?url'
|
| 3 |
+
import posesLibraryUrl from './poses/data.bin?url'
|
| 4 |
+
|
| 5 |
+
export default {
|
| 6 |
+
'models/hand.fbx': handFBXFileUrl,
|
| 7 |
+
'models/foot.fbx': footFBXFileUrl,
|
| 8 |
+
'src/poses/data.bin': posesLibraryUrl,
|
| 9 |
+
}
|
sd-webui-3d-open-pose-editor/src/body.ts
ADDED
|
@@ -0,0 +1,1293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as THREE from 'three'
|
| 2 |
+
import { Bone, Object3D } from 'three'
|
| 3 |
+
import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils'
|
| 4 |
+
import type { TupleToUnion } from 'type-fest'
|
| 5 |
+
import {
|
| 6 |
+
FindObjectItem,
|
| 7 |
+
GetLocalPosition,
|
| 8 |
+
GetWorldPosition,
|
| 9 |
+
} from './utils/three-utils'
|
| 10 |
+
import { CCDIKSolver } from './utils/CCDIKSolver'
|
| 11 |
+
import {
|
| 12 |
+
BoneThickness,
|
| 13 |
+
ConnectColor,
|
| 14 |
+
ToHexColor,
|
| 15 |
+
GetColorOfLinkByName,
|
| 16 |
+
OpenposeKeypoints,
|
| 17 |
+
OpenposeyKeypointsConst,
|
| 18 |
+
PartIndexMappingOfBlazePoseModel,
|
| 19 |
+
PartIndexMappingOfPoseModel,
|
| 20 |
+
} from './defines'
|
| 21 |
+
import {
|
| 22 |
+
ExtremitiesMapping,
|
| 23 |
+
FootObject,
|
| 24 |
+
HandObject,
|
| 25 |
+
IsMatchBonePrefix,
|
| 26 |
+
} from './models'
|
| 27 |
+
|
| 28 |
+
function GetPresetColorOfJoint(name: string) {
|
| 29 |
+
const index = OpenposeKeypoints.indexOf(name)
|
| 30 |
+
return index !== -1 ? ToHexColor(ConnectColor[index]) : 0x0
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
function CreateLink(
|
| 34 |
+
startObject: THREE.Object3D,
|
| 35 |
+
startName: string,
|
| 36 |
+
endName: string
|
| 37 |
+
) {
|
| 38 |
+
const presetColor = GetColorOfLinkByName(startName, endName)
|
| 39 |
+
const material = new THREE.MeshBasicMaterial({
|
| 40 |
+
color: presetColor ?? 0x0,
|
| 41 |
+
opacity: 0.6,
|
| 42 |
+
transparent: true,
|
| 43 |
+
})
|
| 44 |
+
const mesh = new THREE.Mesh(
|
| 45 |
+
new THREE.SphereGeometry(BoneThickness),
|
| 46 |
+
material
|
| 47 |
+
)
|
| 48 |
+
mesh.name = startName + '_link_' + endName
|
| 49 |
+
startObject.add(mesh)
|
| 50 |
+
return mesh
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
function UpdateJointSphere(obj: Object3D, thickness = BoneThickness) {
|
| 54 |
+
const name = obj.name + '_joint_sphere'
|
| 55 |
+
obj.getObjectByName(name)?.scale.setScalar(thickness)
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
function UpdateLink4(
|
| 59 |
+
startObject: Object3D,
|
| 60 |
+
endObject: Object3D,
|
| 61 |
+
startName: string,
|
| 62 |
+
endName: string,
|
| 63 |
+
thickness = BoneThickness,
|
| 64 |
+
create = false
|
| 65 |
+
) {
|
| 66 |
+
const startPosition = new THREE.Vector3(0, 0, 0)
|
| 67 |
+
const endPostion = endObject.position
|
| 68 |
+
const distance = startPosition.distanceTo(endPostion)
|
| 69 |
+
// 将拉伸后的球体放在中点,并计算旋转轴和角度
|
| 70 |
+
const origin = startPosition.clone().add(endPostion).multiplyScalar(0.5)
|
| 71 |
+
|
| 72 |
+
// Another method
|
| 73 |
+
// new THREE.Quaternion().setFromUnitVectors(...)
|
| 74 |
+
const v = endPostion.clone().sub(startPosition)
|
| 75 |
+
const unit = new THREE.Vector3(1, 0, 0)
|
| 76 |
+
const axis = unit.clone().cross(v)
|
| 77 |
+
const angle = unit.clone().angleTo(v)
|
| 78 |
+
const mesh = create
|
| 79 |
+
? CreateLink(startObject, startName, endName)
|
| 80 |
+
: startObject.getObjectByName(startName + '_link_' + endName)!
|
| 81 |
+
mesh.scale.copy(
|
| 82 |
+
new THREE.Vector3(
|
| 83 |
+
distance / 2,
|
| 84 |
+
thickness / BoneThickness,
|
| 85 |
+
thickness / BoneThickness
|
| 86 |
+
)
|
| 87 |
+
)
|
| 88 |
+
mesh.position.copy(origin)
|
| 89 |
+
mesh.setRotationFromAxisAngle(axis.normalize(), angle)
|
| 90 |
+
|
| 91 |
+
UpdateJointSphere(startObject, thickness)
|
| 92 |
+
UpdateJointSphere(endObject, thickness)
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
function UpdateLink2(
|
| 96 |
+
startObject: Object3D,
|
| 97 |
+
endObject: Object3D,
|
| 98 |
+
thickness = BoneThickness,
|
| 99 |
+
create = false
|
| 100 |
+
) {
|
| 101 |
+
UpdateLink4(
|
| 102 |
+
startObject,
|
| 103 |
+
endObject,
|
| 104 |
+
startObject.name,
|
| 105 |
+
endObject.name,
|
| 106 |
+
thickness,
|
| 107 |
+
create
|
| 108 |
+
)
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
function CreateSphere(name: string, thickness: number, color: number) {
|
| 112 |
+
const sphere = new THREE.Mesh(
|
| 113 |
+
new THREE.SphereGeometry(thickness),
|
| 114 |
+
new THREE.MeshBasicMaterial({ color: color })
|
| 115 |
+
)
|
| 116 |
+
sphere.name = name
|
| 117 |
+
return sphere
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
function CreateGroup(name: string, x = 0, y = 0, z = 0) {
|
| 121 |
+
const object = new THREE.Group()
|
| 122 |
+
object.name = name
|
| 123 |
+
object.translateX(x)
|
| 124 |
+
object.translateY(y)
|
| 125 |
+
object.translateZ(z)
|
| 126 |
+
return object
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
function CreateJoint(
|
| 130 |
+
name: string,
|
| 131 |
+
x = 0,
|
| 132 |
+
y = 0,
|
| 133 |
+
z = 0,
|
| 134 |
+
thickness: number = BoneThickness
|
| 135 |
+
) {
|
| 136 |
+
return CreateGroup(name, x, y, z).add(
|
| 137 |
+
CreateSphere(
|
| 138 |
+
name + '_joint_sphere',
|
| 139 |
+
thickness,
|
| 140 |
+
GetPresetColorOfJoint(name)
|
| 141 |
+
)
|
| 142 |
+
)
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
let templateBody: THREE.Group | null = null
|
| 146 |
+
|
| 147 |
+
export function CloneBody() {
|
| 148 |
+
if (templateBody) {
|
| 149 |
+
return SkeletonUtils.clone(templateBody)
|
| 150 |
+
}
|
| 151 |
+
return null
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
const HandScale = 2.2
|
| 155 |
+
const FootScale = 0.25
|
| 156 |
+
|
| 157 |
+
function CreateHand(name: 'right_hand' | 'left_hand') {
|
| 158 |
+
if (!HandObject || !FootObject) {
|
| 159 |
+
throw new Error('Failed to create body')
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
if (name === 'right_hand') {
|
| 163 |
+
const right_hand = SkeletonUtils.clone(HandObject)
|
| 164 |
+
|
| 165 |
+
right_hand.name = 'right_hand'
|
| 166 |
+
right_hand.translateX(-0.4)
|
| 167 |
+
right_hand.translateY(3)
|
| 168 |
+
right_hand.rotateY(Math.PI)
|
| 169 |
+
right_hand.rotateZ(-Math.PI / 2)
|
| 170 |
+
|
| 171 |
+
right_hand.scale.multiplyScalar(HandScale)
|
| 172 |
+
|
| 173 |
+
right_hand.traverse((o) => {
|
| 174 |
+
if (IsBone(o.name)) {
|
| 175 |
+
o.name = o.name + '_R'
|
| 176 |
+
}
|
| 177 |
+
})
|
| 178 |
+
return right_hand
|
| 179 |
+
} else {
|
| 180 |
+
const left_hand = SkeletonUtils.clone(HandObject)
|
| 181 |
+
|
| 182 |
+
left_hand.name = 'left_hand'
|
| 183 |
+
left_hand.scale.x = -1
|
| 184 |
+
left_hand.translateX(0.4)
|
| 185 |
+
left_hand.translateY(3)
|
| 186 |
+
left_hand.rotateY(Math.PI)
|
| 187 |
+
left_hand.rotateZ(Math.PI / 2)
|
| 188 |
+
left_hand.scale.multiplyScalar(HandScale)
|
| 189 |
+
|
| 190 |
+
left_hand.traverse((o) => {
|
| 191 |
+
if (IsBone(o.name)) {
|
| 192 |
+
o.name = o.name + '_L'
|
| 193 |
+
}
|
| 194 |
+
})
|
| 195 |
+
return left_hand
|
| 196 |
+
}
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
function CreateFoot(name: 'right_foot' | 'left_foot') {
|
| 200 |
+
if (!HandObject || !FootObject) {
|
| 201 |
+
throw new Error('Failed to create body')
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
if (name === 'right_foot') {
|
| 205 |
+
const right_foot = SkeletonUtils.clone(FootObject)
|
| 206 |
+
|
| 207 |
+
right_foot.name = 'right_foot'
|
| 208 |
+
right_foot.scale.setX(-1)
|
| 209 |
+
right_foot.scale.multiplyScalar(FootScale)
|
| 210 |
+
|
| 211 |
+
right_foot.traverse((o) => {
|
| 212 |
+
if (IsBone(o.name)) {
|
| 213 |
+
o.name = o.name + '_R'
|
| 214 |
+
}
|
| 215 |
+
})
|
| 216 |
+
|
| 217 |
+
return right_foot
|
| 218 |
+
} else {
|
| 219 |
+
const left_foot = SkeletonUtils.clone(FootObject)
|
| 220 |
+
left_foot.name = 'left_foot'
|
| 221 |
+
left_foot.scale.multiplyScalar(FootScale)
|
| 222 |
+
|
| 223 |
+
left_foot.traverse((o) => {
|
| 224 |
+
if (IsBone(o.name)) {
|
| 225 |
+
o.name = o.name + '_L'
|
| 226 |
+
}
|
| 227 |
+
})
|
| 228 |
+
return left_foot
|
| 229 |
+
}
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
export function CreateTemplateBody() {
|
| 233 |
+
if (!HandObject || !FootObject) {
|
| 234 |
+
throw new Error('Failed to create body')
|
| 235 |
+
}
|
| 236 |
+
const width = 34
|
| 237 |
+
const height = 46
|
| 238 |
+
|
| 239 |
+
const torso = CreateGroup('torso', 0, 115, 0).add(
|
| 240 |
+
CreateSphere('center', BoneThickness, 0x888888),
|
| 241 |
+
CreateGroup('five', 0, height / 2, 0).add(
|
| 242 |
+
CreateJoint('neck').add(
|
| 243 |
+
CreateJoint('nose', 0, 20, 14).add(
|
| 244 |
+
CreateJoint('right_eye', -3, 3, -3).add(
|
| 245 |
+
CreateJoint('right_ear', -4, -3, -8)
|
| 246 |
+
),
|
| 247 |
+
CreateJoint('left_eye', 3, 3, -3).add(
|
| 248 |
+
CreateJoint('left_ear', 4, -3, -8)
|
| 249 |
+
)
|
| 250 |
+
)
|
| 251 |
+
),
|
| 252 |
+
CreateGroup('left_shoulder_inner').add(
|
| 253 |
+
CreateJoint('right_shoulder', -width / 2, 0, 0).add(
|
| 254 |
+
CreateJoint('right_elbow', 0, -25, 0).add(
|
| 255 |
+
CreateJoint('right_wrist', 0, -25, 0).add(
|
| 256 |
+
CreateHand('right_hand')
|
| 257 |
+
)
|
| 258 |
+
)
|
| 259 |
+
)
|
| 260 |
+
),
|
| 261 |
+
CreateGroup('right_shoulder_inner').add(
|
| 262 |
+
CreateJoint('left_shoulder', width / 2, 0, 0).add(
|
| 263 |
+
CreateJoint('left_elbow', 0, -25, 0).add(
|
| 264 |
+
CreateJoint('left_wrist', 0, -25, 0).add(
|
| 265 |
+
CreateHand('left_hand')
|
| 266 |
+
)
|
| 267 |
+
)
|
| 268 |
+
)
|
| 269 |
+
),
|
| 270 |
+
CreateGroup('right_hip_inner').add(
|
| 271 |
+
CreateJoint('right_hip', -width / 2 + 7, -height, 0).add(
|
| 272 |
+
CreateJoint('right_knee', 0, -40, 0).add(
|
| 273 |
+
CreateJoint('right_ankle', 0, -36, 0).add(
|
| 274 |
+
CreateFoot('right_foot')
|
| 275 |
+
)
|
| 276 |
+
)
|
| 277 |
+
)
|
| 278 |
+
),
|
| 279 |
+
CreateGroup('left_hip_inner').add(
|
| 280 |
+
CreateJoint('left_hip', width / 2 - 7, -height, 0).add(
|
| 281 |
+
CreateJoint('left_knee', 0, -40, 0).add(
|
| 282 |
+
CreateJoint('left_ankle', 0, -36, 0).add(
|
| 283 |
+
CreateFoot('left_foot')
|
| 284 |
+
)
|
| 285 |
+
)
|
| 286 |
+
)
|
| 287 |
+
)
|
| 288 |
+
)
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
new BodyControlor(torso).Create()
|
| 292 |
+
templateBody = torso
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
function CreateIKTarget(body: Object3D, effector: Object3D, name: string) {
|
| 296 |
+
const target = new THREE.Mesh(
|
| 297 |
+
new THREE.BoxGeometry(
|
| 298 |
+
BoneThickness * 5,
|
| 299 |
+
BoneThickness * 5,
|
| 300 |
+
BoneThickness * 5
|
| 301 |
+
),
|
| 302 |
+
new THREE.MeshBasicMaterial({
|
| 303 |
+
color: 0x0088ff,
|
| 304 |
+
transparent: true,
|
| 305 |
+
opacity: 0.5,
|
| 306 |
+
})
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
const effector_pos = GetWorldPosition(effector)
|
| 310 |
+
target.position.copy(GetLocalPosition(body, effector_pos))
|
| 311 |
+
target.name = name
|
| 312 |
+
|
| 313 |
+
return target
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
export function GetExtremityMesh(o: Object3D) {
|
| 317 |
+
if (!(o.name in ExtremitiesMapping)) {
|
| 318 |
+
return null
|
| 319 |
+
}
|
| 320 |
+
return FindObjectItem<THREE.SkinnedMesh>(
|
| 321 |
+
o,
|
| 322 |
+
ExtremitiesMapping[o.name].meshName
|
| 323 |
+
)
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
export function IsVirtualPoint(name: string) {
|
| 327 |
+
return [
|
| 328 |
+
'right_shoulder_inner',
|
| 329 |
+
'left_shoulder_inner',
|
| 330 |
+
'right_hip_inner',
|
| 331 |
+
'left_hip_inner',
|
| 332 |
+
'five',
|
| 333 |
+
].includes(name)
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
export function IsNeedSaveObject(name: string) {
|
| 337 |
+
if (OpenposeKeypoints.includes(name)) return true
|
| 338 |
+
if (name === 'right_hand' || name === 'left_hand') return true
|
| 339 |
+
if (name === 'right_foot' || name === 'left_foot') return true
|
| 340 |
+
if (IsVirtualPoint(name)) return true // virtual point
|
| 341 |
+
if (IsBone(name)) return true
|
| 342 |
+
if (name.includes('_joint_sphere')) return true
|
| 343 |
+
if (name.includes('_link_')) return true
|
| 344 |
+
return false
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
export function IsBone(name: string) {
|
| 348 |
+
return IsMatchBonePrefix(name)
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
const pickableObjectNames: string[] = [
|
| 352 |
+
'torso',
|
| 353 |
+
'nose',
|
| 354 |
+
'neck',
|
| 355 |
+
'right_shoulder',
|
| 356 |
+
'left_shoulder',
|
| 357 |
+
'right_elbow',
|
| 358 |
+
'left_elbow',
|
| 359 |
+
'right_hip',
|
| 360 |
+
'left_hip',
|
| 361 |
+
'right_knee',
|
| 362 |
+
'left_knee',
|
| 363 |
+
// virtual point for better control
|
| 364 |
+
'right_shoulder_inner',
|
| 365 |
+
'left_shoulder_inner',
|
| 366 |
+
'right_hip_inner',
|
| 367 |
+
'left_hip_inner',
|
| 368 |
+
'left_wrist_target',
|
| 369 |
+
'right_wrist_target',
|
| 370 |
+
'left_ankle_target',
|
| 371 |
+
'right_ankle_target',
|
| 372 |
+
]
|
| 373 |
+
|
| 374 |
+
export function IsPickable(name: string, isFreeMode = false) {
|
| 375 |
+
if (isFreeMode && OpenposeKeypoints.includes(name)) return true
|
| 376 |
+
if (pickableObjectNames.includes(name)) return true
|
| 377 |
+
if (IsBone(name)) return true
|
| 378 |
+
return false
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
export function IsTranslate(name: string, isFreeMode = false) {
|
| 382 |
+
if (isFreeMode)
|
| 383 |
+
return (
|
| 384 |
+
[
|
| 385 |
+
'right_shoulder_inner',
|
| 386 |
+
'left_shoulder_inner',
|
| 387 |
+
'right_hip_inner',
|
| 388 |
+
'left_hip_inner',
|
| 389 |
+
'neck',
|
| 390 |
+
].includes(name) == false
|
| 391 |
+
)
|
| 392 |
+
|
| 393 |
+
if (name.endsWith('_target')) return true
|
| 394 |
+
return false
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
export function IsTarget(name: string) {
|
| 398 |
+
if (name.endsWith('_target')) return true
|
| 399 |
+
return false
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
export function IsHand(name: string) {
|
| 403 |
+
return ['left_hand', 'right_hand'].includes(name)
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
export function IsFoot(name: string) {
|
| 407 |
+
return ['left_foot', 'right_foot'].includes(name)
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
export function IsMask(name: string) {
|
| 411 |
+
return ['foot_mask', 'hand_mask'].includes(name)
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
export function IsSkeleton(name: string) {
|
| 415 |
+
if (name == 'torso') return true
|
| 416 |
+
if (OpenposeKeypoints.includes(name)) return true
|
| 417 |
+
if (IsVirtualPoint(name)) return true // virtual point
|
| 418 |
+
if (name.includes('_joint_sphere')) return true
|
| 419 |
+
if (name.includes('_link_')) return true
|
| 420 |
+
return false
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
export function IsExtremities(name: string) {
|
| 424 |
+
return ['left_hand', 'right_hand', 'left_foot', 'right_foot'].includes(name)
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
const ControlablePart = [
|
| 428 |
+
...OpenposeyKeypointsConst,
|
| 429 |
+
'left_shoulder_inner',
|
| 430 |
+
'right_shoulder_inner',
|
| 431 |
+
'left_hip_inner',
|
| 432 |
+
'right_hip_inner',
|
| 433 |
+
'five',
|
| 434 |
+
'right_hand',
|
| 435 |
+
'left_hand',
|
| 436 |
+
'left_foot',
|
| 437 |
+
'right_foot',
|
| 438 |
+
'torso',
|
| 439 |
+
|
| 440 |
+
'left_wrist_target',
|
| 441 |
+
'right_wrist_target',
|
| 442 |
+
'left_ankle_target',
|
| 443 |
+
'right_ankle_target',
|
| 444 |
+
] as const
|
| 445 |
+
|
| 446 |
+
type ControlPartName = TupleToUnion<typeof ControlablePart>
|
| 447 |
+
|
| 448 |
+
export interface BodyData {
|
| 449 |
+
position?: ReturnType<THREE.Vector3['toArray']>
|
| 450 |
+
rotation?: ReturnType<THREE.Euler['toArray']>
|
| 451 |
+
scale?: ReturnType<THREE.Vector3['toArray']>
|
| 452 |
+
|
| 453 |
+
child: Record<
|
| 454 |
+
string,
|
| 455 |
+
{
|
| 456 |
+
position?: ReturnType<THREE.Vector3['toArray']>
|
| 457 |
+
rotation?: ReturnType<THREE.Euler['toArray']>
|
| 458 |
+
scale?: ReturnType<THREE.Vector3['toArray']>
|
| 459 |
+
}
|
| 460 |
+
>
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
export interface HandData {
|
| 464 |
+
child: Record<
|
| 465 |
+
string,
|
| 466 |
+
{
|
| 467 |
+
position?: ReturnType<THREE.Vector3['toArray']>
|
| 468 |
+
rotation?: ReturnType<THREE.Euler['toArray']>
|
| 469 |
+
scale?: ReturnType<THREE.Vector3['toArray']>
|
| 470 |
+
}
|
| 471 |
+
>
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
export class BodyControlor {
|
| 475 |
+
body: Object3D
|
| 476 |
+
part: Record<ControlPartName, Object3D> = {} as any
|
| 477 |
+
constructor(o: Object3D) {
|
| 478 |
+
this.body = o
|
| 479 |
+
this.body.traverse((o) => {
|
| 480 |
+
if (ControlablePart.includes(o.name as ControlPartName)) {
|
| 481 |
+
this.part[o.name as ControlPartName] = o
|
| 482 |
+
}
|
| 483 |
+
})
|
| 484 |
+
|
| 485 |
+
this.part['left_shoulder_inner'] = this.getObjectByName(
|
| 486 |
+
'left_shoulder_inner'
|
| 487 |
+
)
|
| 488 |
+
this.part['right_shoulder_inner'] = this.getObjectByName(
|
| 489 |
+
'right_shoulder_inner'
|
| 490 |
+
)
|
| 491 |
+
this.part['left_hip_inner'] = this.getObjectByName('left_hip_inner')
|
| 492 |
+
this.part['right_hip_inner'] = this.getObjectByName('right_hip_inner')
|
| 493 |
+
this.part['five'] = this.getObjectByName('five')
|
| 494 |
+
this.part['right_hand'] = this.getObjectByName('right_hand')
|
| 495 |
+
this.part['left_hand'] = this.getObjectByName('left_hand')
|
| 496 |
+
this.part['right_foot'] = this.getObjectByName('right_foot')
|
| 497 |
+
this.part['left_foot'] = this.getObjectByName('left_foot')
|
| 498 |
+
this.part['torso'] = this.body
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
getObjectByName(name: string) {
|
| 502 |
+
const part = this.body.getObjectByName(name)
|
| 503 |
+
|
| 504 |
+
if (!part) throw new Error(`Not found part: ${name}`)
|
| 505 |
+
|
| 506 |
+
return part
|
| 507 |
+
}
|
| 508 |
+
getWorldPosition(o: Object3D) {
|
| 509 |
+
const pos = new THREE.Vector3()
|
| 510 |
+
o.getWorldPosition(pos)
|
| 511 |
+
return pos
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
UpdateLink(
|
| 515 |
+
name: ControlPartName,
|
| 516 |
+
thickness = this.BoneThickness,
|
| 517 |
+
create = false
|
| 518 |
+
) {
|
| 519 |
+
if (
|
| 520 |
+
[
|
| 521 |
+
'left_hip',
|
| 522 |
+
'right_hip',
|
| 523 |
+
'right_shoulder',
|
| 524 |
+
'left_shoulder',
|
| 525 |
+
].includes(name)
|
| 526 |
+
)
|
| 527 |
+
UpdateLink4(
|
| 528 |
+
this.part[`${name}_inner` as ControlPartName],
|
| 529 |
+
this.part[name],
|
| 530 |
+
'neck',
|
| 531 |
+
name,
|
| 532 |
+
thickness,
|
| 533 |
+
create
|
| 534 |
+
)
|
| 535 |
+
else if (name !== 'neck' && OpenposeKeypoints.includes(name)) {
|
| 536 |
+
UpdateLink2(
|
| 537 |
+
this.part[name].parent!,
|
| 538 |
+
this.part[name],
|
| 539 |
+
thickness,
|
| 540 |
+
create
|
| 541 |
+
)
|
| 542 |
+
}
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
get HeadSize() {
|
| 546 |
+
const size = this.getWorldPosition(this.part['right_ear']).distanceTo(
|
| 547 |
+
this.getWorldPosition(this.part['left_ear'])
|
| 548 |
+
)
|
| 549 |
+
return size
|
| 550 |
+
}
|
| 551 |
+
|
| 552 |
+
set HeadSize(value: number) {
|
| 553 |
+
const scale = value / this.HeadSize
|
| 554 |
+
|
| 555 |
+
const earLength = this.part['left_ear'].position.length() * scale
|
| 556 |
+
const eyeLength = this.part['left_eye'].position.length() * scale
|
| 557 |
+
|
| 558 |
+
this.part['left_ear'].position.normalize().multiplyScalar(earLength)
|
| 559 |
+
this.part['right_ear'].position.normalize().multiplyScalar(earLength)
|
| 560 |
+
this.part['left_eye'].position.normalize().multiplyScalar(eyeLength)
|
| 561 |
+
this.part['right_eye'].position.normalize().multiplyScalar(eyeLength)
|
| 562 |
+
|
| 563 |
+
this.UpdateLink('left_eye')
|
| 564 |
+
this.UpdateLink('right_eye')
|
| 565 |
+
this.UpdateLink('left_ear')
|
| 566 |
+
this.UpdateLink('right_ear')
|
| 567 |
+
}
|
| 568 |
+
get NoseToNeck() {
|
| 569 |
+
return this.part['nose'].position.length()
|
| 570 |
+
}
|
| 571 |
+
set NoseToNeck(value: number) {
|
| 572 |
+
this.part['nose'].position.normalize().multiplyScalar(value)
|
| 573 |
+
this.UpdateLink('nose')
|
| 574 |
+
}
|
| 575 |
+
get ShoulderToHip() {
|
| 576 |
+
return this.getDistanceOf(
|
| 577 |
+
this.getWorldPosition(this.part['five']),
|
| 578 |
+
this.getMidpoint(
|
| 579 |
+
this.getWorldPosition(this.part['left_hip']),
|
| 580 |
+
this.getWorldPosition(this.part['right_hip'])
|
| 581 |
+
)
|
| 582 |
+
)
|
| 583 |
+
}
|
| 584 |
+
set ShoulderToHip(value: number) {
|
| 585 |
+
const origin = this.ShoulderToHip
|
| 586 |
+
|
| 587 |
+
this.part['five'].position.normalize().multiplyScalar(value / 2)
|
| 588 |
+
this.part['left_hip'].position.multiplyScalar(value / origin)
|
| 589 |
+
this.part['right_hip'].position.multiplyScalar(value / origin)
|
| 590 |
+
|
| 591 |
+
this.UpdateLink('left_hip')
|
| 592 |
+
this.UpdateLink('right_hip')
|
| 593 |
+
}
|
| 594 |
+
get ShoulderWidth() {
|
| 595 |
+
return this.part['left_shoulder'].position.distanceTo(
|
| 596 |
+
this.part['right_shoulder'].position
|
| 597 |
+
)
|
| 598 |
+
}
|
| 599 |
+
set ShoulderWidth(width: number) {
|
| 600 |
+
const right_shoulder = this.part['right_shoulder']
|
| 601 |
+
right_shoulder.position.x = -width / 2
|
| 602 |
+
const left_shoulder = this.part['left_shoulder']
|
| 603 |
+
left_shoulder.position.x = width / 2
|
| 604 |
+
|
| 605 |
+
this.UpdateLink('right_shoulder')
|
| 606 |
+
this.UpdateLink('left_shoulder')
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
get UpperArm() {
|
| 610 |
+
return this.part['left_elbow'].position.length()
|
| 611 |
+
}
|
| 612 |
+
set UpperArm(length: number) {
|
| 613 |
+
this.part['left_elbow'].position.normalize().multiplyScalar(length)
|
| 614 |
+
this.part['right_elbow'].position.normalize().multiplyScalar(length)
|
| 615 |
+
this.UpdateLink('left_elbow')
|
| 616 |
+
this.UpdateLink('right_elbow')
|
| 617 |
+
}
|
| 618 |
+
get Forearm() {
|
| 619 |
+
return this.part['left_wrist'].position.length()
|
| 620 |
+
}
|
| 621 |
+
set Forearm(length: number) {
|
| 622 |
+
this.part['left_wrist'].position.normalize().multiplyScalar(length)
|
| 623 |
+
this.part['right_wrist'].position.normalize().multiplyScalar(length)
|
| 624 |
+
|
| 625 |
+
this.UpdateLink('left_wrist')
|
| 626 |
+
this.UpdateLink('right_wrist')
|
| 627 |
+
}
|
| 628 |
+
|
| 629 |
+
get ArmLength() {
|
| 630 |
+
return this.UpperArm + this.Forearm
|
| 631 |
+
}
|
| 632 |
+
set ArmLength(length: number) {
|
| 633 |
+
const origin = this.ArmLength
|
| 634 |
+
this.UpperArm = (length * this.UpperArm) / origin
|
| 635 |
+
this.Forearm = (length * this.Forearm) / origin
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
get Thigh() {
|
| 639 |
+
return this.part['left_knee'].position.length()
|
| 640 |
+
}
|
| 641 |
+
set Thigh(length: number) {
|
| 642 |
+
this.part['left_knee'].position.normalize().multiplyScalar(length)
|
| 643 |
+
this.part['right_knee'].position.normalize().multiplyScalar(length)
|
| 644 |
+
|
| 645 |
+
this.UpdateLink('left_knee')
|
| 646 |
+
this.UpdateLink('right_knee')
|
| 647 |
+
}
|
| 648 |
+
|
| 649 |
+
get HandSize() {
|
| 650 |
+
return Math.abs(this.part['left_hand'].scale.x) / HandScale
|
| 651 |
+
}
|
| 652 |
+
set HandSize(size: number) {
|
| 653 |
+
const origin = this.HandSize
|
| 654 |
+
this.part['left_hand'].scale
|
| 655 |
+
.divideScalar(origin * HandScale)
|
| 656 |
+
.multiplyScalar(size * HandScale)
|
| 657 |
+
this.part['right_hand'].scale
|
| 658 |
+
.divideScalar(origin * HandScale)
|
| 659 |
+
.multiplyScalar(size * HandScale)
|
| 660 |
+
}
|
| 661 |
+
|
| 662 |
+
get Hips() {
|
| 663 |
+
return this.getDistanceOf(
|
| 664 |
+
this.getWorldPosition(this.part['left_hip']),
|
| 665 |
+
this.getWorldPosition(this.part['right_hip'])
|
| 666 |
+
)
|
| 667 |
+
}
|
| 668 |
+
set Hips(width: number) {
|
| 669 |
+
const left = this.getWorldPosition(this.part['left_hip'])
|
| 670 |
+
const right = this.getWorldPosition(this.part['right_hip'])
|
| 671 |
+
|
| 672 |
+
const mid = this.getMidpoint(left, right)
|
| 673 |
+
|
| 674 |
+
// newLLeft = normalize(left - mid) * width + mid
|
| 675 |
+
|
| 676 |
+
const newLeft = left
|
| 677 |
+
.sub(mid)
|
| 678 |
+
.normalize()
|
| 679 |
+
.multiplyScalar(width / 2.0)
|
| 680 |
+
.add(mid)
|
| 681 |
+
const newRight = right
|
| 682 |
+
.sub(mid)
|
| 683 |
+
.normalize()
|
| 684 |
+
.multiplyScalar(width / 2.0)
|
| 685 |
+
.add(mid)
|
| 686 |
+
|
| 687 |
+
this.setPositionFromWorld(this.part['left_hip'], newLeft)
|
| 688 |
+
this.setPositionFromWorld(this.part['right_hip'], newRight)
|
| 689 |
+
|
| 690 |
+
this.UpdateLink('left_hip')
|
| 691 |
+
this.UpdateLink('right_hip')
|
| 692 |
+
}
|
| 693 |
+
get LowerLeg() {
|
| 694 |
+
return this.part['left_ankle'].position.length()
|
| 695 |
+
}
|
| 696 |
+
set LowerLeg(length: number) {
|
| 697 |
+
this.part['left_ankle'].position.normalize().multiplyScalar(length)
|
| 698 |
+
this.part['right_ankle'].position.normalize().multiplyScalar(length)
|
| 699 |
+
|
| 700 |
+
this.UpdateLink('left_ankle')
|
| 701 |
+
this.UpdateLink('right_ankle')
|
| 702 |
+
}
|
| 703 |
+
|
| 704 |
+
get LegLength() {
|
| 705 |
+
return this.Thigh + this.LowerLeg
|
| 706 |
+
}
|
| 707 |
+
set LegLength(length: number) {
|
| 708 |
+
const origin = this.LegLength
|
| 709 |
+
this.Thigh = (length * this.Thigh) / origin
|
| 710 |
+
this.LowerLeg = (length * this.LowerLeg) / origin
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
get FootSize() {
|
| 714 |
+
return Math.abs(this.part['left_foot'].scale.x) / FootScale
|
| 715 |
+
}
|
| 716 |
+
set FootSize(size: number) {
|
| 717 |
+
const origin = this.FootSize
|
| 718 |
+
this.part['left_foot'].scale
|
| 719 |
+
.divideScalar(origin * FootScale)
|
| 720 |
+
.multiplyScalar(size * FootScale)
|
| 721 |
+
this.part['right_foot'].scale
|
| 722 |
+
.divideScalar(origin * FootScale)
|
| 723 |
+
.multiplyScalar(size * FootScale)
|
| 724 |
+
}
|
| 725 |
+
getLocalPosition(obj: Object3D, postion: THREE.Vector3) {
|
| 726 |
+
return obj.worldToLocal(postion.clone())
|
| 727 |
+
}
|
| 728 |
+
|
| 729 |
+
setPositionFromWorld(obj: Object3D, postion: THREE.Vector3) {
|
| 730 |
+
return obj.position.copy(this.getLocalPosition(obj.parent!, postion))
|
| 731 |
+
}
|
| 732 |
+
|
| 733 |
+
getDirectionVectorByParentOf(
|
| 734 |
+
name: ControlPartName,
|
| 735 |
+
from: THREE.Vector3,
|
| 736 |
+
to: THREE.Vector3
|
| 737 |
+
) {
|
| 738 |
+
const parent = this.part[name].parent!
|
| 739 |
+
const localFrom = this.getLocalPosition(parent, from)
|
| 740 |
+
const localTo = this.getLocalPosition(parent, to)
|
| 741 |
+
|
| 742 |
+
return localTo.clone().sub(localFrom).normalize()
|
| 743 |
+
}
|
| 744 |
+
|
| 745 |
+
rotateTo(name: ControlPartName, dir: THREE.Vector3) {
|
| 746 |
+
const obj = this.part[name]
|
| 747 |
+
const unit = obj.position.clone().normalize()
|
| 748 |
+
const axis = unit.clone().cross(dir)
|
| 749 |
+
const angle = unit.clone().angleTo(dir)
|
| 750 |
+
obj.parent?.rotateOnAxis(axis.normalize(), angle)
|
| 751 |
+
}
|
| 752 |
+
|
| 753 |
+
rotateTo3(name: ControlPartName, from: THREE.Vector3, to: THREE.Vector3) {
|
| 754 |
+
this.rotateTo(name, this.getDirectionVectorByParentOf(name, from, to))
|
| 755 |
+
}
|
| 756 |
+
|
| 757 |
+
setPositionByDistance(
|
| 758 |
+
name: ControlPartName,
|
| 759 |
+
from: THREE.Vector3,
|
| 760 |
+
to: THREE.Vector3
|
| 761 |
+
) {
|
| 762 |
+
const dis = this.getDistanceOf(from, to)
|
| 763 |
+
this.part[name].position.normalize().multiplyScalar(dis)
|
| 764 |
+
}
|
| 765 |
+
|
| 766 |
+
setDirectionVector(name: ControlPartName, v: THREE.Vector3) {
|
| 767 |
+
const len = this.part[name].position.length()
|
| 768 |
+
this.part[name].position.copy(v).multiplyScalar(len)
|
| 769 |
+
this.UpdateLink(name)
|
| 770 |
+
}
|
| 771 |
+
|
| 772 |
+
getDistanceOf(from: THREE.Vector3, to: THREE.Vector3) {
|
| 773 |
+
return from.distanceTo(to)
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
getMidpoint(from: THREE.Vector3, to: THREE.Vector3) {
|
| 777 |
+
return from.clone().add(to).multiplyScalar(0.5)
|
| 778 |
+
}
|
| 779 |
+
ResetPose() {
|
| 780 |
+
templateBody?.traverse((o) => {
|
| 781 |
+
if (o.name in this.part) {
|
| 782 |
+
const name = o.name as ControlPartName
|
| 783 |
+
|
| 784 |
+
if (name == 'torso') this.part[name].position.setY(o.position.y)
|
| 785 |
+
else this.part[name].position.copy(o.position)
|
| 786 |
+
|
| 787 |
+
this.part[name].rotation.copy(o.rotation)
|
| 788 |
+
this.part[name].scale.copy(o.scale)
|
| 789 |
+
this.UpdateLink(name)
|
| 790 |
+
}
|
| 791 |
+
})
|
| 792 |
+
}
|
| 793 |
+
|
| 794 |
+
SetBlazePose(rawData: [number, number, number][]) {
|
| 795 |
+
this.ResetPose()
|
| 796 |
+
|
| 797 |
+
const data = Object.fromEntries(
|
| 798 |
+
Object.entries(PartIndexMappingOfBlazePoseModel).map(
|
| 799 |
+
([name, index]) => {
|
| 800 |
+
return [
|
| 801 |
+
name,
|
| 802 |
+
new THREE.Vector3().fromArray(
|
| 803 |
+
rawData[index] ?? [0, 0, 0]
|
| 804 |
+
),
|
| 805 |
+
]
|
| 806 |
+
}
|
| 807 |
+
)
|
| 808 |
+
) as Record<
|
| 809 |
+
keyof typeof PartIndexMappingOfBlazePoseModel,
|
| 810 |
+
THREE.Vector3
|
| 811 |
+
>
|
| 812 |
+
|
| 813 |
+
// this.Hips = this.getDistanceOf(data['right_hip'], data['left_hip'])
|
| 814 |
+
// this.Thigh = this.getDistanceOf(data['left_knee'], data['left_hip'])
|
| 815 |
+
// this.LowerLeg = this.getDistanceOf(
|
| 816 |
+
// data['left_ankle'],
|
| 817 |
+
// data['left_knee']
|
| 818 |
+
// )
|
| 819 |
+
// this.UpperArm = this.getDistanceOf(
|
| 820 |
+
// data['left_shoulder'],
|
| 821 |
+
// data['left_elbow']
|
| 822 |
+
// )
|
| 823 |
+
// this.Forearm = this.getDistanceOf(
|
| 824 |
+
// data['left_elbow'],
|
| 825 |
+
// data['left_wrist']
|
| 826 |
+
// )
|
| 827 |
+
// this.ShoulderWidth = this.getDistanceOf(
|
| 828 |
+
// data['left_shoulder'],
|
| 829 |
+
// data['right_shoulder']
|
| 830 |
+
// )
|
| 831 |
+
|
| 832 |
+
// this.NoseToNeck = this.getDistanceOf(
|
| 833 |
+
// data['nose'],
|
| 834 |
+
// this.getMidpoint(data['left_shoulder'], data['right_shoulder'])
|
| 835 |
+
// )
|
| 836 |
+
|
| 837 |
+
const map: [
|
| 838 |
+
ControlPartName,
|
| 839 |
+
[
|
| 840 |
+
THREE.Vector3 | keyof typeof PartIndexMappingOfBlazePoseModel,
|
| 841 |
+
THREE.Vector3 | keyof typeof PartIndexMappingOfBlazePoseModel
|
| 842 |
+
]
|
| 843 |
+
][] = [
|
| 844 |
+
[
|
| 845 |
+
'five',
|
| 846 |
+
[
|
| 847 |
+
this.getMidpoint(
|
| 848 |
+
this.getMidpoint(data['left_hip'], data['right_hip']),
|
| 849 |
+
this.getMidpoint(
|
| 850 |
+
data['left_shoulder'],
|
| 851 |
+
data['right_shoulder']
|
| 852 |
+
)
|
| 853 |
+
),
|
| 854 |
+
this.getMidpoint(
|
| 855 |
+
data['left_shoulder'],
|
| 856 |
+
data['right_shoulder']
|
| 857 |
+
),
|
| 858 |
+
],
|
| 859 |
+
],
|
| 860 |
+
|
| 861 |
+
[
|
| 862 |
+
'left_shoulder',
|
| 863 |
+
[
|
| 864 |
+
this.getMidpoint(
|
| 865 |
+
data['left_shoulder'],
|
| 866 |
+
data['right_shoulder']
|
| 867 |
+
),
|
| 868 |
+
'left_shoulder',
|
| 869 |
+
],
|
| 870 |
+
],
|
| 871 |
+
['left_elbow', ['left_shoulder', 'left_elbow']],
|
| 872 |
+
['left_wrist', ['left_elbow', 'left_wrist']],
|
| 873 |
+
[
|
| 874 |
+
'left_hip',
|
| 875 |
+
[
|
| 876 |
+
this.getMidpoint(
|
| 877 |
+
data['left_shoulder'],
|
| 878 |
+
data['right_shoulder']
|
| 879 |
+
),
|
| 880 |
+
'left_hip',
|
| 881 |
+
],
|
| 882 |
+
],
|
| 883 |
+
['left_knee', ['left_hip', 'left_knee']],
|
| 884 |
+
['left_ankle', ['left_knee', 'left_ankle']],
|
| 885 |
+
|
| 886 |
+
[
|
| 887 |
+
'right_shoulder',
|
| 888 |
+
[
|
| 889 |
+
this.getMidpoint(
|
| 890 |
+
data['left_shoulder'],
|
| 891 |
+
data['right_shoulder']
|
| 892 |
+
),
|
| 893 |
+
'right_shoulder',
|
| 894 |
+
],
|
| 895 |
+
],
|
| 896 |
+
['right_elbow', ['right_shoulder', 'right_elbow']],
|
| 897 |
+
['right_wrist', ['right_elbow', 'right_wrist']],
|
| 898 |
+
|
| 899 |
+
[
|
| 900 |
+
'right_hip',
|
| 901 |
+
[
|
| 902 |
+
this.getMidpoint(
|
| 903 |
+
data['left_shoulder'],
|
| 904 |
+
data['right_shoulder']
|
| 905 |
+
),
|
| 906 |
+
'right_hip',
|
| 907 |
+
],
|
| 908 |
+
],
|
| 909 |
+
['right_knee', ['right_hip', 'right_knee']],
|
| 910 |
+
['right_ankle', ['right_knee', 'right_ankle']],
|
| 911 |
+
|
| 912 |
+
[
|
| 913 |
+
'nose',
|
| 914 |
+
[
|
| 915 |
+
this.getMidpoint(
|
| 916 |
+
data['left_shoulder'],
|
| 917 |
+
data['right_shoulder']
|
| 918 |
+
),
|
| 919 |
+
'nose',
|
| 920 |
+
],
|
| 921 |
+
],
|
| 922 |
+
['left_eye', ['nose', 'left_eye']],
|
| 923 |
+
['right_eye', ['nose', 'right_eye']],
|
| 924 |
+
['left_ear', ['left_eye', 'left_ear']],
|
| 925 |
+
['right_ear', ['right_eye', 'right_ear']],
|
| 926 |
+
]
|
| 927 |
+
|
| 928 |
+
for (const [name, [from, to]] of map) {
|
| 929 |
+
this.rotateTo3(
|
| 930 |
+
name,
|
| 931 |
+
from instanceof THREE.Vector3 ? from : data[from],
|
| 932 |
+
to instanceof THREE.Vector3 ? to : data[to]
|
| 933 |
+
)
|
| 934 |
+
|
| 935 |
+
this.setPositionByDistance(
|
| 936 |
+
name,
|
| 937 |
+
from instanceof THREE.Vector3 ? from : data[from],
|
| 938 |
+
to instanceof THREE.Vector3 ? to : data[to]
|
| 939 |
+
)
|
| 940 |
+
|
| 941 |
+
this.UpdateLink(name)
|
| 942 |
+
}
|
| 943 |
+
this.Update()
|
| 944 |
+
}
|
| 945 |
+
SetPose(rawData: [number, number, number][]) {
|
| 946 |
+
this.ResetPose()
|
| 947 |
+
|
| 948 |
+
const data = Object.fromEntries(
|
| 949 |
+
Object.entries(PartIndexMappingOfPoseModel).map(([name, index]) => {
|
| 950 |
+
return [
|
| 951 |
+
name,
|
| 952 |
+
new THREE.Vector3().fromArray(rawData[index] ?? [0, 0, 0]),
|
| 953 |
+
]
|
| 954 |
+
})
|
| 955 |
+
) as Record<keyof typeof PartIndexMappingOfPoseModel, THREE.Vector3>
|
| 956 |
+
|
| 957 |
+
this.part['torso'].position.setY(
|
| 958 |
+
this.getMidpoint(data['Hips'], data['Chest']).y
|
| 959 |
+
)
|
| 960 |
+
this.Hips = this.getDistanceOf(data['Hips'], data['UpLeg_L']) * 2
|
| 961 |
+
this.Thigh = this.getDistanceOf(data['UpLeg_L'], data['Leg_L'])
|
| 962 |
+
this.LowerLeg = this.getDistanceOf(data['Leg_L'], data['Foot_L'])
|
| 963 |
+
this.UpperArm = this.getDistanceOf(data['Arm_L'], data['ForeArm_L'])
|
| 964 |
+
this.Forearm = this.getDistanceOf(data['ForeArm_L'], data['Hand_L'])
|
| 965 |
+
this.ShoulderWidth =
|
| 966 |
+
2 *
|
| 967 |
+
(this.getDistanceOf(data['Shoulder_L'], data['Arm_L']) +
|
| 968 |
+
this.getDistanceOf(data['Chest'], data['Shoulder_L']) /
|
| 969 |
+
Math.SQRT2)
|
| 970 |
+
|
| 971 |
+
const map: [
|
| 972 |
+
ControlPartName,
|
| 973 |
+
[
|
| 974 |
+
keyof typeof PartIndexMappingOfPoseModel,
|
| 975 |
+
keyof typeof PartIndexMappingOfPoseModel
|
| 976 |
+
]
|
| 977 |
+
][] = [
|
| 978 |
+
['five', ['Hips', 'Chest']],
|
| 979 |
+
['left_elbow', ['Arm_L', 'ForeArm_L']],
|
| 980 |
+
['left_wrist', ['ForeArm_L', 'Hand_L']],
|
| 981 |
+
['left_knee', ['UpLeg_L', 'Leg_L']],
|
| 982 |
+
['left_ankle', ['Leg_L', 'Foot_L']],
|
| 983 |
+
['right_elbow', ['Arm_R', 'ForeArm_R']],
|
| 984 |
+
['right_wrist', ['ForeArm_R', 'Hand_R']],
|
| 985 |
+
['right_knee', ['UpLeg_R', 'Leg_R']],
|
| 986 |
+
['right_ankle', ['Leg_R', 'Foot_R']],
|
| 987 |
+
]
|
| 988 |
+
|
| 989 |
+
for (const [name, [from, to]] of map)
|
| 990 |
+
this.rotateTo(
|
| 991 |
+
name,
|
| 992 |
+
this.getDirectionVectorByParentOf(name, data[from], data[to])
|
| 993 |
+
)
|
| 994 |
+
this.Update()
|
| 995 |
+
}
|
| 996 |
+
|
| 997 |
+
GetHandData(hand: 'left_hand' | 'right_hand'): HandData {
|
| 998 |
+
const o = this.part[hand]
|
| 999 |
+
const result: HandData = {
|
| 1000 |
+
child: {},
|
| 1001 |
+
}
|
| 1002 |
+
o.traverse((child) => {
|
| 1003 |
+
if (child.name && IsBone(child.name)) {
|
| 1004 |
+
if (child.name in result.child)
|
| 1005 |
+
console.log('Duplicate name', child.name, child)
|
| 1006 |
+
const data: Pick<BodyData, 'position' | 'rotation' | 'scale'> =
|
| 1007 |
+
{}
|
| 1008 |
+
|
| 1009 |
+
if (
|
| 1010 |
+
this.getDistanceOf(
|
| 1011 |
+
child.position,
|
| 1012 |
+
new THREE.Vector3(0, 0, 0)
|
| 1013 |
+
) != 0
|
| 1014 |
+
) {
|
| 1015 |
+
data.position = child.position.toArray()
|
| 1016 |
+
}
|
| 1017 |
+
|
| 1018 |
+
if (
|
| 1019 |
+
this.getDistanceOf(
|
| 1020 |
+
child.scale,
|
| 1021 |
+
new THREE.Vector3(1, 1, 1)
|
| 1022 |
+
) != 0
|
| 1023 |
+
) {
|
| 1024 |
+
data.scale = child.scale.toArray()
|
| 1025 |
+
}
|
| 1026 |
+
|
| 1027 |
+
if (
|
| 1028 |
+
child.rotation.x !== 0 ||
|
| 1029 |
+
child.rotation.y !== 0 ||
|
| 1030 |
+
child.rotation.z !== 0
|
| 1031 |
+
) {
|
| 1032 |
+
data.rotation = child.rotation.toArray()
|
| 1033 |
+
}
|
| 1034 |
+
if (data) result.child[child.name] = data
|
| 1035 |
+
}
|
| 1036 |
+
})
|
| 1037 |
+
|
| 1038 |
+
return result
|
| 1039 |
+
}
|
| 1040 |
+
|
| 1041 |
+
RestoreHand(hand: 'left_hand' | 'right_hand', data: HandData) {
|
| 1042 |
+
data.child = Object.fromEntries(
|
| 1043 |
+
Object.entries(data.child).map(([k, v]) => {
|
| 1044 |
+
if (hand == 'left_hand') return [k.replace('_R', '_L'), v]
|
| 1045 |
+
if (hand == 'right_hand') return [k.replace('_L', '_R'), v]
|
| 1046 |
+
return [k, v]
|
| 1047 |
+
})
|
| 1048 |
+
)
|
| 1049 |
+
this.part[hand]?.traverse((o) => {
|
| 1050 |
+
if (o.name && o.name in data.child) {
|
| 1051 |
+
const child = data.child[o.name]
|
| 1052 |
+
if (child.position) o.position.fromArray(child.position)
|
| 1053 |
+
if (child.rotation) o.rotation.fromArray(child.rotation as any)
|
| 1054 |
+
if (child.scale) o.scale.fromArray(child.scale)
|
| 1055 |
+
}
|
| 1056 |
+
})
|
| 1057 |
+
}
|
| 1058 |
+
|
| 1059 |
+
GetBodyData(): BodyData {
|
| 1060 |
+
const o = this.part['torso']
|
| 1061 |
+
const result: BodyData = {
|
| 1062 |
+
position: o.position.toArray(),
|
| 1063 |
+
rotation: o.rotation.toArray(),
|
| 1064 |
+
scale: o.scale.toArray(),
|
| 1065 |
+
child: {},
|
| 1066 |
+
}
|
| 1067 |
+
o.traverse((child) => {
|
| 1068 |
+
if (child.name && IsNeedSaveObject(child.name)) {
|
| 1069 |
+
if (child.name in result.child)
|
| 1070 |
+
console.log('Duplicate name', child.name, child)
|
| 1071 |
+
const data: Pick<BodyData, 'position' | 'rotation' | 'scale'> =
|
| 1072 |
+
{}
|
| 1073 |
+
|
| 1074 |
+
if (
|
| 1075 |
+
this.getDistanceOf(
|
| 1076 |
+
child.position,
|
| 1077 |
+
new THREE.Vector3(0, 0, 0)
|
| 1078 |
+
) != 0
|
| 1079 |
+
) {
|
| 1080 |
+
data.position = child.position.toArray()
|
| 1081 |
+
}
|
| 1082 |
+
|
| 1083 |
+
if (
|
| 1084 |
+
this.getDistanceOf(
|
| 1085 |
+
child.scale,
|
| 1086 |
+
new THREE.Vector3(1, 1, 1)
|
| 1087 |
+
) != 0
|
| 1088 |
+
) {
|
| 1089 |
+
data.scale = child.scale.toArray()
|
| 1090 |
+
}
|
| 1091 |
+
|
| 1092 |
+
if (
|
| 1093 |
+
child.rotation.x !== 0 ||
|
| 1094 |
+
child.rotation.y !== 0 ||
|
| 1095 |
+
child.rotation.z !== 0
|
| 1096 |
+
) {
|
| 1097 |
+
data.rotation = child.rotation.toArray()
|
| 1098 |
+
}
|
| 1099 |
+
if (data) result.child[child.name] = data
|
| 1100 |
+
}
|
| 1101 |
+
})
|
| 1102 |
+
|
| 1103 |
+
return result
|
| 1104 |
+
}
|
| 1105 |
+
|
| 1106 |
+
RestoreBody(data: BodyData) {
|
| 1107 |
+
const body = this.part['torso']
|
| 1108 |
+
|
| 1109 |
+
body?.traverse((o) => {
|
| 1110 |
+
if (o.name && o.name in data.child) {
|
| 1111 |
+
const child = data.child[o.name]
|
| 1112 |
+
if (child.position) o.position.fromArray(child.position)
|
| 1113 |
+
if (child.rotation) o.rotation.fromArray(child.rotation as any)
|
| 1114 |
+
if (child.scale) o.scale.fromArray(child.scale)
|
| 1115 |
+
}
|
| 1116 |
+
})
|
| 1117 |
+
if (data.position) body.position.fromArray(data.position)
|
| 1118 |
+
if (data.rotation) body.rotation.fromArray(data.rotation as any)
|
| 1119 |
+
if (data.scale) body.scale.fromArray(data.scale)
|
| 1120 |
+
}
|
| 1121 |
+
|
| 1122 |
+
UpdateBones(thickness = this.BoneThickness) {
|
| 1123 |
+
this.part['torso'].traverse((o) => {
|
| 1124 |
+
if (o.name in this.part) {
|
| 1125 |
+
const name = o.name as ControlPartName
|
| 1126 |
+
this.UpdateLink(name, thickness)
|
| 1127 |
+
}
|
| 1128 |
+
})
|
| 1129 |
+
}
|
| 1130 |
+
|
| 1131 |
+
CreateBones(thickness = this.BoneThickness) {
|
| 1132 |
+
this.part['torso'].traverse((o) => {
|
| 1133 |
+
if (o.name in this.part) {
|
| 1134 |
+
const name = o.name as ControlPartName
|
| 1135 |
+
this.UpdateLink(name, thickness, true)
|
| 1136 |
+
}
|
| 1137 |
+
})
|
| 1138 |
+
}
|
| 1139 |
+
|
| 1140 |
+
Create() {
|
| 1141 |
+
this.CreateBones()
|
| 1142 |
+
|
| 1143 |
+
this.part['torso'].add(
|
| 1144 |
+
CreateIKTarget(
|
| 1145 |
+
this.part['torso'],
|
| 1146 |
+
this.part['left_wrist'],
|
| 1147 |
+
'left_wrist_target'
|
| 1148 |
+
)
|
| 1149 |
+
)
|
| 1150 |
+
this.part['torso'].add(
|
| 1151 |
+
CreateIKTarget(
|
| 1152 |
+
this.part['torso'],
|
| 1153 |
+
this.part['right_wrist'],
|
| 1154 |
+
'right_wrist_target'
|
| 1155 |
+
)
|
| 1156 |
+
)
|
| 1157 |
+
this.part['torso'].add(
|
| 1158 |
+
CreateIKTarget(
|
| 1159 |
+
this.part['torso'],
|
| 1160 |
+
this.part['left_ankle'],
|
| 1161 |
+
'left_ankle_target'
|
| 1162 |
+
)
|
| 1163 |
+
)
|
| 1164 |
+
this.part['torso'].add(
|
| 1165 |
+
CreateIKTarget(
|
| 1166 |
+
this.part['torso'],
|
| 1167 |
+
this.part['right_ankle'],
|
| 1168 |
+
'right_ankle_target'
|
| 1169 |
+
)
|
| 1170 |
+
)
|
| 1171 |
+
}
|
| 1172 |
+
|
| 1173 |
+
Get18keyPointsData(): Array<[number, number, number]> {
|
| 1174 |
+
return OpenposeKeypoints.map((name) => {
|
| 1175 |
+
if (name in this.part) {
|
| 1176 |
+
return this.getWorldPosition(
|
| 1177 |
+
this.part[name as ControlPartName]
|
| 1178 |
+
).toArray()
|
| 1179 |
+
} else {
|
| 1180 |
+
return [0, 0, 0]
|
| 1181 |
+
}
|
| 1182 |
+
})
|
| 1183 |
+
}
|
| 1184 |
+
|
| 1185 |
+
get BoneThickness() {
|
| 1186 |
+
return Math.abs(
|
| 1187 |
+
this.part['neck'].getObjectByName('neck_joint_sphere')?.scale.x ??
|
| 1188 |
+
BoneThickness
|
| 1189 |
+
)
|
| 1190 |
+
}
|
| 1191 |
+
|
| 1192 |
+
set BoneThickness(thickness: number) {
|
| 1193 |
+
this.UpdateBones(thickness)
|
| 1194 |
+
}
|
| 1195 |
+
|
| 1196 |
+
GetIKSolver() {
|
| 1197 |
+
return new CCDIKSolver([
|
| 1198 |
+
{
|
| 1199 |
+
target: this.part['left_wrist_target'],
|
| 1200 |
+
effector: this.part['left_wrist'],
|
| 1201 |
+
links: [
|
| 1202 |
+
{
|
| 1203 |
+
index: this.part['left_elbow'],
|
| 1204 |
+
enabled: true,
|
| 1205 |
+
},
|
| 1206 |
+
{
|
| 1207 |
+
index: this.part['left_shoulder'],
|
| 1208 |
+
enabled: true,
|
| 1209 |
+
},
|
| 1210 |
+
],
|
| 1211 |
+
iteration: 10,
|
| 1212 |
+
minAngle: 0.0,
|
| 1213 |
+
maxAngle: 1.0,
|
| 1214 |
+
},
|
| 1215 |
+
{
|
| 1216 |
+
target: this.part['right_wrist_target'],
|
| 1217 |
+
effector: this.part['right_wrist'],
|
| 1218 |
+
links: [
|
| 1219 |
+
{
|
| 1220 |
+
index: this.part['right_elbow'],
|
| 1221 |
+
enabled: true,
|
| 1222 |
+
},
|
| 1223 |
+
{
|
| 1224 |
+
index: this.part['right_shoulder'],
|
| 1225 |
+
enabled: true,
|
| 1226 |
+
},
|
| 1227 |
+
],
|
| 1228 |
+
iteration: 10,
|
| 1229 |
+
minAngle: 0.0,
|
| 1230 |
+
maxAngle: 1.0,
|
| 1231 |
+
},
|
| 1232 |
+
{
|
| 1233 |
+
target: this.part['left_ankle_target'],
|
| 1234 |
+
effector: this.part['left_ankle'],
|
| 1235 |
+
links: [
|
| 1236 |
+
{
|
| 1237 |
+
index: this.part['left_knee'],
|
| 1238 |
+
enabled: true,
|
| 1239 |
+
},
|
| 1240 |
+
{
|
| 1241 |
+
index: this.part['left_hip'],
|
| 1242 |
+
enabled: true,
|
| 1243 |
+
},
|
| 1244 |
+
],
|
| 1245 |
+
iteration: 10,
|
| 1246 |
+
minAngle: 0.0,
|
| 1247 |
+
maxAngle: 1.0,
|
| 1248 |
+
},
|
| 1249 |
+
{
|
| 1250 |
+
target: this.part['right_ankle_target'],
|
| 1251 |
+
effector: this.part['right_ankle'],
|
| 1252 |
+
links: [
|
| 1253 |
+
{
|
| 1254 |
+
index: this.part['right_knee'],
|
| 1255 |
+
enabled: true,
|
| 1256 |
+
},
|
| 1257 |
+
{
|
| 1258 |
+
index: this.part['right_hip'],
|
| 1259 |
+
enabled: true,
|
| 1260 |
+
},
|
| 1261 |
+
],
|
| 1262 |
+
iteration: 10,
|
| 1263 |
+
minAngle: 0.0,
|
| 1264 |
+
maxAngle: 1.0,
|
| 1265 |
+
},
|
| 1266 |
+
])
|
| 1267 |
+
}
|
| 1268 |
+
|
| 1269 |
+
ResetTargetPosition(
|
| 1270 |
+
effectorName: ControlPartName,
|
| 1271 |
+
targetName: ControlPartName
|
| 1272 |
+
) {
|
| 1273 |
+
const body = this.part['torso']
|
| 1274 |
+
const effector = this.part[effectorName]
|
| 1275 |
+
const target = this.part[targetName]
|
| 1276 |
+
|
| 1277 |
+
const effector_pos = GetWorldPosition(effector)
|
| 1278 |
+
target.position.copy(this.getLocalPosition(body, effector_pos))
|
| 1279 |
+
}
|
| 1280 |
+
|
| 1281 |
+
ResetAllTargetsPosition() {
|
| 1282 |
+
this.ResetTargetPosition('left_wrist', 'left_wrist_target')
|
| 1283 |
+
this.ResetTargetPosition('right_wrist', 'right_wrist_target')
|
| 1284 |
+
this.ResetTargetPosition('left_ankle', 'left_ankle_target')
|
| 1285 |
+
this.ResetTargetPosition('right_ankle', 'right_ankle_target')
|
| 1286 |
+
}
|
| 1287 |
+
|
| 1288 |
+
Update() {
|
| 1289 |
+
this.ResetAllTargetsPosition()
|
| 1290 |
+
this.UpdateBones()
|
| 1291 |
+
this.part['torso'].updateMatrixWorld(true)
|
| 1292 |
+
}
|
| 1293 |
+
}
|
sd-webui-3d-open-pose-editor/src/components/ContextMenu/index.tsx
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useMemo } from 'react'
|
| 2 |
+
import classes from './styles.module.css'
|
| 3 |
+
import NiceModal, { useModal } from '@ebay/nice-modal-react'
|
| 4 |
+
import { debounce } from 'lodash-es'
|
| 5 |
+
import { BodyEditor } from '../../editor'
|
| 6 |
+
import i18n, { IsChina } from '../../i18n'
|
| 7 |
+
import { Helper } from '../../environments/online/helper'
|
| 8 |
+
import { SetCDNBase } from '../../utils/detect'
|
| 9 |
+
|
| 10 |
+
const { Root, ContextMenuContent, ContextMenuItem, RightSlot } = classes
|
| 11 |
+
|
| 12 |
+
const MyContextMenu = NiceModal.create<{
|
| 13 |
+
editor: BodyEditor
|
| 14 |
+
mouseX: number
|
| 15 |
+
mouseY: number
|
| 16 |
+
onChangeBackground: (url: string) => void
|
| 17 |
+
}>(({ editor, mouseX, mouseY, onChangeBackground }) => {
|
| 18 |
+
const helper = useMemo(() => new Helper(editor), [editor])
|
| 19 |
+
|
| 20 |
+
const modal = useModal()
|
| 21 |
+
return (
|
| 22 |
+
<div
|
| 23 |
+
className={Root}
|
| 24 |
+
style={{
|
| 25 |
+
display: modal.visible ? undefined : 'none',
|
| 26 |
+
}}
|
| 27 |
+
onClick={() => {
|
| 28 |
+
modal.hide()
|
| 29 |
+
}}
|
| 30 |
+
onContextMenu={(e) => {
|
| 31 |
+
e.preventDefault()
|
| 32 |
+
}}
|
| 33 |
+
>
|
| 34 |
+
<div
|
| 35 |
+
className={ContextMenuContent}
|
| 36 |
+
style={{
|
| 37 |
+
top: mouseY,
|
| 38 |
+
left: mouseX,
|
| 39 |
+
}}
|
| 40 |
+
>
|
| 41 |
+
<div
|
| 42 |
+
className={ContextMenuItem}
|
| 43 |
+
onClick={() => {
|
| 44 |
+
editor.Undo()
|
| 45 |
+
}}
|
| 46 |
+
>
|
| 47 |
+
{i18n.t('Undo')}
|
| 48 |
+
<div className={RightSlot}>⌘ Z</div>
|
| 49 |
+
</div>
|
| 50 |
+
<div
|
| 51 |
+
className={ContextMenuItem}
|
| 52 |
+
onClick={() => {
|
| 53 |
+
editor.Redo()
|
| 54 |
+
}}
|
| 55 |
+
>
|
| 56 |
+
{i18n.t('Redo')}
|
| 57 |
+
<div className={RightSlot}>⇧ ⌘ Z</div>
|
| 58 |
+
</div>
|
| 59 |
+
<div
|
| 60 |
+
className={ContextMenuItem}
|
| 61 |
+
onClick={() => {
|
| 62 |
+
helper.CopySkeleton()
|
| 63 |
+
}}
|
| 64 |
+
>
|
| 65 |
+
{i18n.t('Duplicate Skeleton')}
|
| 66 |
+
<div className={RightSlot}>⇧ D</div>
|
| 67 |
+
</div>
|
| 68 |
+
<div
|
| 69 |
+
className={ContextMenuItem}
|
| 70 |
+
onClick={() => {
|
| 71 |
+
helper.RemoveSkeleton()
|
| 72 |
+
}}
|
| 73 |
+
>
|
| 74 |
+
{i18n.t('Delete Skeleton')}
|
| 75 |
+
<div className={RightSlot}>{i18n.t('Del')}</div>
|
| 76 |
+
</div>
|
| 77 |
+
<div
|
| 78 |
+
className={ContextMenuItem}
|
| 79 |
+
onClick={() => {
|
| 80 |
+
helper.CopyKeypointToClipboard()
|
| 81 |
+
}}
|
| 82 |
+
>
|
| 83 |
+
{i18n.t('Copy Keypoint Data')}
|
| 84 |
+
</div>
|
| 85 |
+
<div
|
| 86 |
+
className={ContextMenuItem}
|
| 87 |
+
onClick={() => {
|
| 88 |
+
helper.SetRandomPose()
|
| 89 |
+
}}
|
| 90 |
+
>
|
| 91 |
+
{i18n.t('Set Random Pose')}
|
| 92 |
+
</div>
|
| 93 |
+
<div
|
| 94 |
+
className={ContextMenuItem}
|
| 95 |
+
onClick={() => helper.DetectFromImage(onChangeBackground)}
|
| 96 |
+
>
|
| 97 |
+
{i18n.t('Detect From Image')}
|
| 98 |
+
</div>
|
| 99 |
+
{IsChina() ? (
|
| 100 |
+
<div
|
| 101 |
+
className={ContextMenuItem}
|
| 102 |
+
onClick={() => {
|
| 103 |
+
SetCDNBase(false)
|
| 104 |
+
helper.DetectFromImage(onChangeBackground)
|
| 105 |
+
}}
|
| 106 |
+
>
|
| 107 |
+
{i18n.t('Detect From Image') + ' [中国]'}
|
| 108 |
+
</div>
|
| 109 |
+
) : undefined}
|
| 110 |
+
</div>
|
| 111 |
+
</div>
|
| 112 |
+
)
|
| 113 |
+
})
|
| 114 |
+
|
| 115 |
+
declare type NiceModalArgs<T> = T extends
|
| 116 |
+
| keyof JSX.IntrinsicElements
|
| 117 |
+
| React.JSXElementConstructor<any>
|
| 118 |
+
? Omit<React.ComponentProps<T>, 'id'>
|
| 119 |
+
: Record<string, unknown>
|
| 120 |
+
|
| 121 |
+
export type ShowProps = NiceModalArgs<typeof MyContextMenu>
|
| 122 |
+
|
| 123 |
+
export function GetContextMenu(wait = 0) {
|
| 124 |
+
const show = debounce((props: ShowProps) => {
|
| 125 |
+
NiceModal.show(MyContextMenu, props)
|
| 126 |
+
}, wait)
|
| 127 |
+
|
| 128 |
+
return {
|
| 129 |
+
show: (props: ShowProps) => {
|
| 130 |
+
show(props)
|
| 131 |
+
},
|
| 132 |
+
hide: () => {
|
| 133 |
+
show.cancel()
|
| 134 |
+
HideContextMenu()
|
| 135 |
+
},
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
export async function ShowContextMenu(props: ShowProps): Promise<string> {
|
| 140 |
+
return await NiceModal.show(MyContextMenu, props)
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
export function HideContextMenu() {
|
| 144 |
+
return NiceModal.hide(MyContextMenu)
|
| 145 |
+
}
|
sd-webui-3d-open-pose-editor/src/components/ContextMenu/styles.module.css
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import '@radix-ui/colors/blackA.css';
|
| 2 |
+
@import '@radix-ui/colors/mauve.css';
|
| 3 |
+
@import '@radix-ui/colors/violet.css';
|
| 4 |
+
|
| 5 |
+
.Root {
|
| 6 |
+
position: fixed;
|
| 7 |
+
top: 0px;
|
| 8 |
+
left: 0px;
|
| 9 |
+
width: 100%;
|
| 10 |
+
height: 100%;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
.ContextMenuContent {
|
| 14 |
+
animation-name: slideDownAndFade;
|
| 15 |
+
position: fixed;
|
| 16 |
+
top: 0px;
|
| 17 |
+
left: 0px;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.ContextMenuContent {
|
| 21 |
+
min-width: 220px;
|
| 22 |
+
background-color: white;
|
| 23 |
+
border-radius: 6px;
|
| 24 |
+
padding: 5px;
|
| 25 |
+
box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35),
|
| 26 |
+
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
|
| 27 |
+
animation-duration: 400ms;
|
| 28 |
+
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
| 29 |
+
will-change: transform, opacity;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
.ContextMenuItem {
|
| 33 |
+
font-size: 13px;
|
| 34 |
+
line-height: 1;
|
| 35 |
+
color: var(--violet11);
|
| 36 |
+
border-radius: 3px;
|
| 37 |
+
display: flex;
|
| 38 |
+
align-items: center;
|
| 39 |
+
height: 25px;
|
| 40 |
+
padding: 0 5px;
|
| 41 |
+
position: relative;
|
| 42 |
+
padding-left: 25px;
|
| 43 |
+
user-select: none;
|
| 44 |
+
outline: none;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.ContextMenuItem:hover {
|
| 48 |
+
background-color: var(--violet9);
|
| 49 |
+
color: var(--violet1);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.ContextMenuSeparator {
|
| 53 |
+
height: 1px;
|
| 54 |
+
background-color: var(--violet6);
|
| 55 |
+
margin: 5px;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.IconButton {
|
| 59 |
+
all: unset;
|
| 60 |
+
font-family: inherit;
|
| 61 |
+
border-radius: 100%;
|
| 62 |
+
height: 35px;
|
| 63 |
+
width: 35px;
|
| 64 |
+
display: inline-flex;
|
| 65 |
+
align-items: center;
|
| 66 |
+
justify-content: center;
|
| 67 |
+
color: var(--violet11);
|
| 68 |
+
background-color: white;
|
| 69 |
+
box-shadow: 0 2px 10px var(--blackA7);
|
| 70 |
+
width: 100%;
|
| 71 |
+
height: 100%;
|
| 72 |
+
}
|
| 73 |
+
.IconButton:hover {
|
| 74 |
+
background-color: var(--violet3);
|
| 75 |
+
}
|
| 76 |
+
.IconButton:focus {
|
| 77 |
+
box-shadow: 0 0 0 2px black;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.RightSlot {
|
| 81 |
+
margin-left: auto;
|
| 82 |
+
padding-left: 20px;
|
| 83 |
+
color: var(--mauve11);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.ContextMenuItem:hover > .RightSlot {
|
| 87 |
+
color: white;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
@keyframes slideUpAndFade {
|
| 91 |
+
from {
|
| 92 |
+
opacity: 0;
|
| 93 |
+
transform: translateY(2px);
|
| 94 |
+
}
|
| 95 |
+
to {
|
| 96 |
+
opacity: 1;
|
| 97 |
+
transform: translateY(0);
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
@keyframes slideRightAndFade {
|
| 102 |
+
from {
|
| 103 |
+
opacity: 0;
|
| 104 |
+
transform: translateX(-2px);
|
| 105 |
+
}
|
| 106 |
+
to {
|
| 107 |
+
opacity: 1;
|
| 108 |
+
transform: translateX(0);
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
@keyframes slideDownAndFade {
|
| 113 |
+
from {
|
| 114 |
+
opacity: 0;
|
| 115 |
+
transform: translateY(-2px);
|
| 116 |
+
}
|
| 117 |
+
to {
|
| 118 |
+
opacity: 1;
|
| 119 |
+
transform: translateY(0);
|
| 120 |
+
}
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
@keyframes slideLeftAndFade {
|
| 124 |
+
from {
|
| 125 |
+
opacity: 0;
|
| 126 |
+
transform: translateX(2px);
|
| 127 |
+
}
|
| 128 |
+
to {
|
| 129 |
+
opacity: 1;
|
| 130 |
+
transform: translateX(0);
|
| 131 |
+
}
|
| 132 |
+
}
|
sd-webui-3d-open-pose-editor/src/components/Dialog/index.tsx
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react'
|
| 2 |
+
import * as Dialog from '@radix-ui/react-dialog'
|
| 3 |
+
import { Cross2Icon } from '@radix-ui/react-icons'
|
| 4 |
+
import classes from './styles.module.css'
|
| 5 |
+
import NiceModal, { useModal } from '@ebay/nice-modal-react'
|
| 6 |
+
import { debounce } from 'lodash-es'
|
| 7 |
+
import classNames from 'classnames'
|
| 8 |
+
|
| 9 |
+
const {
|
| 10 |
+
DialogOverlay,
|
| 11 |
+
DialogContent,
|
| 12 |
+
DialogTitle,
|
| 13 |
+
DialogDescription,
|
| 14 |
+
Button,
|
| 15 |
+
IconButton,
|
| 16 |
+
green,
|
| 17 |
+
} = classes
|
| 18 |
+
|
| 19 |
+
const MyDialog = NiceModal.create<{
|
| 20 |
+
title?: string
|
| 21 |
+
description?: string
|
| 22 |
+
children?: React.ReactNode
|
| 23 |
+
button?: string
|
| 24 |
+
}>(({ title, description, children, button }) => {
|
| 25 |
+
const modal = useModal()
|
| 26 |
+
return (
|
| 27 |
+
<Dialog.Root
|
| 28 |
+
defaultOpen={true}
|
| 29 |
+
open={modal.visible}
|
| 30 |
+
onOpenChange={(value) => {
|
| 31 |
+
if (value) modal.show()
|
| 32 |
+
else modal.hide()
|
| 33 |
+
}}
|
| 34 |
+
>
|
| 35 |
+
<Dialog.Portal>
|
| 36 |
+
<Dialog.Overlay className={DialogOverlay} />
|
| 37 |
+
<Dialog.Content className={DialogContent}>
|
| 38 |
+
<Dialog.Title className={DialogTitle}>{title}</Dialog.Title>
|
| 39 |
+
<Dialog.Description className={DialogDescription}>
|
| 40 |
+
{description}
|
| 41 |
+
</Dialog.Description>
|
| 42 |
+
<div> {children}</div>
|
| 43 |
+
<div
|
| 44 |
+
style={{
|
| 45 |
+
display: 'flex',
|
| 46 |
+
marginTop: 25,
|
| 47 |
+
justifyContent: 'flex-end',
|
| 48 |
+
}}
|
| 49 |
+
>
|
| 50 |
+
<Dialog.Close asChild>
|
| 51 |
+
<button
|
| 52 |
+
className={classNames(Button, green)}
|
| 53 |
+
onClick={() => {
|
| 54 |
+
modal.resolve('action')
|
| 55 |
+
}}
|
| 56 |
+
>
|
| 57 |
+
{button}
|
| 58 |
+
</button>
|
| 59 |
+
</Dialog.Close>
|
| 60 |
+
</div>
|
| 61 |
+
<Dialog.Close asChild>
|
| 62 |
+
<button className={IconButton}>
|
| 63 |
+
<Cross2Icon />
|
| 64 |
+
</button>
|
| 65 |
+
</Dialog.Close>
|
| 66 |
+
</Dialog.Content>
|
| 67 |
+
</Dialog.Portal>
|
| 68 |
+
</Dialog.Root>
|
| 69 |
+
)
|
| 70 |
+
})
|
| 71 |
+
|
| 72 |
+
declare type NiceModalArgs<T> = T extends
|
| 73 |
+
| keyof JSX.IntrinsicElements
|
| 74 |
+
| React.JSXElementConstructor<any>
|
| 75 |
+
? Omit<React.ComponentProps<T>, 'id'>
|
| 76 |
+
: Record<string, unknown>
|
| 77 |
+
|
| 78 |
+
export type ShowProps = NiceModalArgs<typeof MyDialog>
|
| 79 |
+
|
| 80 |
+
export function GetDialog(wait = 0) {
|
| 81 |
+
const show = debounce((props: ShowProps) => {
|
| 82 |
+
NiceModal.show(MyDialog, props)
|
| 83 |
+
}, wait)
|
| 84 |
+
|
| 85 |
+
return {
|
| 86 |
+
show: (props: ShowProps) => {
|
| 87 |
+
show(props)
|
| 88 |
+
},
|
| 89 |
+
hide: () => {
|
| 90 |
+
show.cancel()
|
| 91 |
+
HideDialog()
|
| 92 |
+
},
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
export async function ShowDialog(props: ShowProps): Promise<string> {
|
| 97 |
+
console.log(props)
|
| 98 |
+
return await NiceModal.show(MyDialog, props)
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
export function HideDialog() {
|
| 102 |
+
return NiceModal.hide(MyDialog)
|
| 103 |
+
}
|
sd-webui-3d-open-pose-editor/src/components/Dialog/styles.module.css
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import '@radix-ui/colors/blackA.css';
|
| 2 |
+
@import '@radix-ui/colors/green.css';
|
| 3 |
+
@import '@radix-ui/colors/mauve.css';
|
| 4 |
+
@import '@radix-ui/colors/violet.css';
|
| 5 |
+
|
| 6 |
+
.DialogOverlay {
|
| 7 |
+
background-color: var(--blackA9);
|
| 8 |
+
position: fixed;
|
| 9 |
+
inset: 0;
|
| 10 |
+
animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
| 11 |
+
font-family: var(--openpose-editor-font-family);
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
.DialogContent {
|
| 15 |
+
font-family: var(--openpose-editor-font-family);
|
| 16 |
+
background-color: white;
|
| 17 |
+
border-radius: 6px;
|
| 18 |
+
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
|
| 19 |
+
hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
|
| 20 |
+
position: fixed;
|
| 21 |
+
top: 50%;
|
| 22 |
+
left: 50%;
|
| 23 |
+
transform: translate(-50%, -50%);
|
| 24 |
+
width: 90vw;
|
| 25 |
+
max-width: 450px;
|
| 26 |
+
max-height: 85vh;
|
| 27 |
+
padding: 25px;
|
| 28 |
+
animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
| 29 |
+
}
|
| 30 |
+
.DialogContent:focus {
|
| 31 |
+
outline: none;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.DialogTitle {
|
| 35 |
+
margin: 0;
|
| 36 |
+
font-weight: 500;
|
| 37 |
+
color: var(--mauve12);
|
| 38 |
+
font-size: 32px;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.DialogDescription {
|
| 42 |
+
margin: 10px 0 20px;
|
| 43 |
+
color: var(--mauve11);
|
| 44 |
+
font-size: 15px;
|
| 45 |
+
line-height: 1.5;
|
| 46 |
+
overflow-y: auto;
|
| 47 |
+
word-break: break-all;
|
| 48 |
+
max-height: 60vh;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
.Button {
|
| 52 |
+
all: unset;
|
| 53 |
+
display: inline-flex;
|
| 54 |
+
align-items: center;
|
| 55 |
+
justify-content: center;
|
| 56 |
+
border-radius: 4px;
|
| 57 |
+
padding: 0 15px;
|
| 58 |
+
font-size: 15px;
|
| 59 |
+
line-height: 1;
|
| 60 |
+
font-weight: 500;
|
| 61 |
+
height: 35px;
|
| 62 |
+
}
|
| 63 |
+
.Button.violet {
|
| 64 |
+
background-color: white;
|
| 65 |
+
color: var(--violet11);
|
| 66 |
+
box-shadow: 0 2px 10px var(--blackA7);
|
| 67 |
+
}
|
| 68 |
+
.Button.violet:hover {
|
| 69 |
+
background-color: var(--mauve3);
|
| 70 |
+
}
|
| 71 |
+
.Button.violet:focus {
|
| 72 |
+
box-shadow: 0 0 0 2px black;
|
| 73 |
+
}
|
| 74 |
+
.Button.green {
|
| 75 |
+
background-color: var(--green4);
|
| 76 |
+
color: var(--green11);
|
| 77 |
+
}
|
| 78 |
+
.Button.green:hover {
|
| 79 |
+
background-color: var(--green5);
|
| 80 |
+
}
|
| 81 |
+
.Button.green:focus {
|
| 82 |
+
box-shadow: 0 0 0 2px var(--green7);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.IconButton {
|
| 86 |
+
all: unset;
|
| 87 |
+
font-family: inherit;
|
| 88 |
+
border-radius: 100%;
|
| 89 |
+
height: 25px;
|
| 90 |
+
width: 25px;
|
| 91 |
+
display: inline-flex;
|
| 92 |
+
align-items: center;
|
| 93 |
+
justify-content: center;
|
| 94 |
+
color: var(--violet11);
|
| 95 |
+
position: absolute;
|
| 96 |
+
top: 10px;
|
| 97 |
+
right: 10px;
|
| 98 |
+
}
|
| 99 |
+
.IconButton:hover {
|
| 100 |
+
background-color: var(--violet4);
|
| 101 |
+
}
|
| 102 |
+
.IconButton:focus {
|
| 103 |
+
box-shadow: 0 0 0 2px var(--violet7);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
@keyframes overlayShow {
|
| 107 |
+
from {
|
| 108 |
+
opacity: 0;
|
| 109 |
+
}
|
| 110 |
+
to {
|
| 111 |
+
opacity: 1;
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
@keyframes contentShow {
|
| 116 |
+
from {
|
| 117 |
+
opacity: 0;
|
| 118 |
+
transform: translate(-50%, -48%) scale(0.96);
|
| 119 |
+
}
|
| 120 |
+
to {
|
| 121 |
+
opacity: 1;
|
| 122 |
+
transform: translate(-50%, -50%) scale(1);
|
| 123 |
+
}
|
| 124 |
+
}
|
sd-webui-3d-open-pose-editor/src/components/Loading/index.tsx
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react'
|
| 2 |
+
import * as AlertDialog from '@radix-ui/react-alert-dialog'
|
| 3 |
+
import NiceModal, { useModal } from '@ebay/nice-modal-react'
|
| 4 |
+
import { debounce } from 'lodash-es'
|
| 5 |
+
|
| 6 |
+
import classes from './styles.module.css'
|
| 7 |
+
|
| 8 |
+
const {
|
| 9 |
+
AlertDialogOverlay,
|
| 10 |
+
AlertDialogContent,
|
| 11 |
+
AlertDialogTitle,
|
| 12 |
+
'lds-roller': LdsRoller,
|
| 13 |
+
} = classes
|
| 14 |
+
|
| 15 |
+
function CSSLoading() {
|
| 16 |
+
return (
|
| 17 |
+
<div className={LdsRoller}>
|
| 18 |
+
<div></div>
|
| 19 |
+
<div></div>
|
| 20 |
+
<div></div>
|
| 21 |
+
<div></div>
|
| 22 |
+
<div></div>
|
| 23 |
+
<div></div>
|
| 24 |
+
<div></div>
|
| 25 |
+
<div></div>
|
| 26 |
+
</div>
|
| 27 |
+
)
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
const Loading = NiceModal.create<{ title: string }>(
|
| 31 |
+
({ title }: { title: string }) => {
|
| 32 |
+
const modal = useLoading()
|
| 33 |
+
return (
|
| 34 |
+
<AlertDialog.Root defaultOpen={true} open={modal.visible}>
|
| 35 |
+
<AlertDialog.Portal>
|
| 36 |
+
<AlertDialog.Overlay className={AlertDialogOverlay} />
|
| 37 |
+
<AlertDialog.Content className={AlertDialogContent}>
|
| 38 |
+
<AlertDialog.Title className={AlertDialogTitle}>
|
| 39 |
+
<CSSLoading></CSSLoading>
|
| 40 |
+
{title}
|
| 41 |
+
</AlertDialog.Title>
|
| 42 |
+
</AlertDialog.Content>
|
| 43 |
+
</AlertDialog.Portal>
|
| 44 |
+
</AlertDialog.Root>
|
| 45 |
+
)
|
| 46 |
+
}
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
export function useLoading() {
|
| 50 |
+
return useModal(Loading)
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
declare type NiceModalArgs<T> = T extends
|
| 54 |
+
| keyof JSX.IntrinsicElements
|
| 55 |
+
| React.JSXElementConstructor<any>
|
| 56 |
+
? Omit<React.ComponentProps<T>, 'id'>
|
| 57 |
+
: Record<string, unknown>
|
| 58 |
+
|
| 59 |
+
export type ShowProps = NiceModalArgs<typeof Loading>
|
| 60 |
+
|
| 61 |
+
export function GetLoading(wait = 0) {
|
| 62 |
+
const show = debounce((props: ShowProps) => {
|
| 63 |
+
NiceModal.show(Loading, props)
|
| 64 |
+
}, wait)
|
| 65 |
+
|
| 66 |
+
return {
|
| 67 |
+
show: (props: ShowProps) => {
|
| 68 |
+
show(props)
|
| 69 |
+
},
|
| 70 |
+
hide: () => {
|
| 71 |
+
show.cancel()
|
| 72 |
+
HideLoading()
|
| 73 |
+
},
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
export function ShowLoading(props: ShowProps) {
|
| 78 |
+
NiceModal.show(Loading, props)
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
export function HideLoading() {
|
| 82 |
+
NiceModal.hide(Loading)
|
| 83 |
+
}
|
sd-webui-3d-open-pose-editor/src/components/Loading/styles.module.css
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import '@radix-ui/colors/blackA.css';
|
| 2 |
+
@import '@radix-ui/colors/mauve.css';
|
| 3 |
+
@import '@radix-ui/colors/red.css';
|
| 4 |
+
@import '@radix-ui/colors/violet.css';
|
| 5 |
+
|
| 6 |
+
.AlertDialogOverlay {
|
| 7 |
+
font-family: var(--openpose-editor-font-family);
|
| 8 |
+
background-color: var(--blackA9);
|
| 9 |
+
position: fixed;
|
| 10 |
+
inset: 0;
|
| 11 |
+
animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
.AlertDialogContent {
|
| 15 |
+
font-family: var(--openpose-editor-font-family);
|
| 16 |
+
background-color: white;
|
| 17 |
+
border-radius: 6px;
|
| 18 |
+
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
|
| 19 |
+
hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
|
| 20 |
+
position: fixed;
|
| 21 |
+
top: 50%;
|
| 22 |
+
left: 50%;
|
| 23 |
+
transform: translate(-50%, -50%);
|
| 24 |
+
width: 90vw;
|
| 25 |
+
max-width: 500px;
|
| 26 |
+
max-height: 85vh;
|
| 27 |
+
padding: 25px;
|
| 28 |
+
animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
| 29 |
+
}
|
| 30 |
+
.AlertDialogContent:focus {
|
| 31 |
+
outline: none;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.AlertDialogTitle {
|
| 35 |
+
margin: 0;
|
| 36 |
+
color: var(--mauve12);
|
| 37 |
+
font-size: 27px;
|
| 38 |
+
font-weight: 500;
|
| 39 |
+
display: flex;
|
| 40 |
+
justify-content: flex-start;
|
| 41 |
+
align-items: center;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
@keyframes overlayShow {
|
| 45 |
+
from {
|
| 46 |
+
opacity: 0;
|
| 47 |
+
}
|
| 48 |
+
to {
|
| 49 |
+
opacity: 1;
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
@keyframes contentShow {
|
| 54 |
+
from {
|
| 55 |
+
opacity: 0;
|
| 56 |
+
transform: translate(-50%, -48%) scale(0.96);
|
| 57 |
+
}
|
| 58 |
+
to {
|
| 59 |
+
opacity: 1;
|
| 60 |
+
transform: translate(-50%, -50%) scale(1);
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.lds-roller {
|
| 65 |
+
display: inline-block;
|
| 66 |
+
position: relative;
|
| 67 |
+
min-width: 100px;
|
| 68 |
+
min-height: 80px;
|
| 69 |
+
}
|
| 70 |
+
.lds-roller div {
|
| 71 |
+
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
| 72 |
+
transform-origin: 40px 40px;
|
| 73 |
+
}
|
| 74 |
+
.lds-roller div:after {
|
| 75 |
+
content: ' ';
|
| 76 |
+
display: block;
|
| 77 |
+
position: absolute;
|
| 78 |
+
width: 7px;
|
| 79 |
+
height: 7px;
|
| 80 |
+
border-radius: 50%;
|
| 81 |
+
background: gray;
|
| 82 |
+
margin: -4px 0 0 -4px;
|
| 83 |
+
}
|
| 84 |
+
.lds-roller div:nth-child(1) {
|
| 85 |
+
animation-delay: -0.036s;
|
| 86 |
+
}
|
| 87 |
+
.lds-roller div:nth-child(1):after {
|
| 88 |
+
top: 63px;
|
| 89 |
+
left: 63px;
|
| 90 |
+
}
|
| 91 |
+
.lds-roller div:nth-child(2) {
|
| 92 |
+
animation-delay: -0.072s;
|
| 93 |
+
}
|
| 94 |
+
.lds-roller div:nth-child(2):after {
|
| 95 |
+
top: 68px;
|
| 96 |
+
left: 56px;
|
| 97 |
+
}
|
| 98 |
+
.lds-roller div:nth-child(3) {
|
| 99 |
+
animation-delay: -0.108s;
|
| 100 |
+
}
|
| 101 |
+
.lds-roller div:nth-child(3):after {
|
| 102 |
+
top: 71px;
|
| 103 |
+
left: 48px;
|
| 104 |
+
}
|
| 105 |
+
.lds-roller div:nth-child(4) {
|
| 106 |
+
animation-delay: -0.144s;
|
| 107 |
+
}
|
| 108 |
+
.lds-roller div:nth-child(4):after {
|
| 109 |
+
top: 72px;
|
| 110 |
+
left: 40px;
|
| 111 |
+
}
|
| 112 |
+
.lds-roller div:nth-child(5) {
|
| 113 |
+
animation-delay: -0.18s;
|
| 114 |
+
}
|
| 115 |
+
.lds-roller div:nth-child(5):after {
|
| 116 |
+
top: 71px;
|
| 117 |
+
left: 32px;
|
| 118 |
+
}
|
| 119 |
+
.lds-roller div:nth-child(6) {
|
| 120 |
+
animation-delay: -0.216s;
|
| 121 |
+
}
|
| 122 |
+
.lds-roller div:nth-child(6):after {
|
| 123 |
+
top: 68px;
|
| 124 |
+
left: 24px;
|
| 125 |
+
}
|
| 126 |
+
.lds-roller div:nth-child(7) {
|
| 127 |
+
animation-delay: -0.252s;
|
| 128 |
+
}
|
| 129 |
+
.lds-roller div:nth-child(7):after {
|
| 130 |
+
top: 63px;
|
| 131 |
+
left: 17px;
|
| 132 |
+
}
|
| 133 |
+
.lds-roller div:nth-child(8) {
|
| 134 |
+
animation-delay: -0.288s;
|
| 135 |
+
}
|
| 136 |
+
.lds-roller div:nth-child(8):after {
|
| 137 |
+
top: 56px;
|
| 138 |
+
left: 12px;
|
| 139 |
+
}
|
| 140 |
+
@keyframes lds-roller {
|
| 141 |
+
0% {
|
| 142 |
+
transform: rotate(0deg);
|
| 143 |
+
}
|
| 144 |
+
100% {
|
| 145 |
+
transform: rotate(360deg);
|
| 146 |
+
}
|
| 147 |
+
}
|
sd-webui-3d-open-pose-editor/src/components/Menu/index.tsx
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useEffect, useMemo, useState } from 'react'
|
| 2 |
+
import * as Menubar from '@radix-ui/react-menubar'
|
| 3 |
+
import { CheckIcon, DotFilledIcon } from '@radix-ui/react-icons'
|
| 4 |
+
|
| 5 |
+
import classes from './styles.module.css'
|
| 6 |
+
import { BodyEditor } from '../../editor'
|
| 7 |
+
import i18n, { IsChina } from '../../i18n'
|
| 8 |
+
import { Helper } from '../../environments/online/helper'
|
| 9 |
+
import { uploadImage } from '../../utils/transfer'
|
| 10 |
+
import { getCurrentTime } from '../../utils/time'
|
| 11 |
+
import useForceUpdate from '../../hooks/useFoceUpdate'
|
| 12 |
+
import classNames from 'classnames'
|
| 13 |
+
import { useLanguageSelect } from '../../hooks'
|
| 14 |
+
import { ShowContextMenu } from '../ContextMenu'
|
| 15 |
+
import { ShowDialog } from '../Dialog'
|
| 16 |
+
import { SetCDNBase } from '../../utils/detect'
|
| 17 |
+
|
| 18 |
+
const {
|
| 19 |
+
MenubarRoot,
|
| 20 |
+
MenubarTrigger,
|
| 21 |
+
MenubarContent,
|
| 22 |
+
MenubarItem,
|
| 23 |
+
MenubarCheckboxItem,
|
| 24 |
+
MenubarRadioItem,
|
| 25 |
+
MenubarItemIndicator,
|
| 26 |
+
MenubarSeparator,
|
| 27 |
+
RightSlot,
|
| 28 |
+
Blue,
|
| 29 |
+
inset,
|
| 30 |
+
} = classes
|
| 31 |
+
const MenubarDemo: React.FC<{
|
| 32 |
+
editor: BodyEditor
|
| 33 |
+
onChangeBackground: (url: string) => void
|
| 34 |
+
onScreenShot: (data: Record<string, { src: string; title: string }>) => void
|
| 35 |
+
style?: React.CSSProperties
|
| 36 |
+
}> = ({ editor, onChangeBackground, onScreenShot, style }) => {
|
| 37 |
+
const forceUpdate = useForceUpdate()
|
| 38 |
+
const helper = useMemo(() => new Helper(editor), [editor])
|
| 39 |
+
const { current, changeLanguage, languagList } = useLanguageSelect()
|
| 40 |
+
|
| 41 |
+
useEffect(() => {
|
| 42 |
+
const show = (data: { mouseX: number; mouseY: number }) => {
|
| 43 |
+
ShowContextMenu({ ...data, editor, onChangeBackground })
|
| 44 |
+
}
|
| 45 |
+
editor?.ContextMenuEventManager.AddEventListener(show)
|
| 46 |
+
return () => {
|
| 47 |
+
editor?.ContextMenuEventManager.RemoveEventListener(show)
|
| 48 |
+
}
|
| 49 |
+
}, [editor])
|
| 50 |
+
|
| 51 |
+
return (
|
| 52 |
+
<Menubar.Root className={MenubarRoot} style={style}>
|
| 53 |
+
<Menubar.Menu>
|
| 54 |
+
<Menubar.Trigger className={MenubarTrigger}>
|
| 55 |
+
{i18n.t('File')}
|
| 56 |
+
</Menubar.Trigger>
|
| 57 |
+
<Menubar.Portal>
|
| 58 |
+
<Menubar.Content
|
| 59 |
+
className={MenubarContent}
|
| 60 |
+
align="start"
|
| 61 |
+
sideOffset={5}
|
| 62 |
+
alignOffset={-3}
|
| 63 |
+
>
|
| 64 |
+
<Menubar.Item
|
| 65 |
+
className={MenubarItem}
|
| 66 |
+
onSelect={() => editor.ResetScene()}
|
| 67 |
+
>
|
| 68 |
+
{i18n.t('Reset Scene')}
|
| 69 |
+
</Menubar.Item>
|
| 70 |
+
<Menubar.Item
|
| 71 |
+
className={MenubarItem}
|
| 72 |
+
onSelect={() => editor.LoadScene()}
|
| 73 |
+
>
|
| 74 |
+
{i18n.t('Load Scene')}
|
| 75 |
+
</Menubar.Item>
|
| 76 |
+
<Menubar.Item
|
| 77 |
+
className={MenubarItem}
|
| 78 |
+
onSelect={() => editor.SaveScene()}
|
| 79 |
+
>
|
| 80 |
+
{i18n.t('Save Scene')}
|
| 81 |
+
</Menubar.Item>
|
| 82 |
+
<Menubar.Item
|
| 83 |
+
className={MenubarItem}
|
| 84 |
+
onSelect={() => helper.LoadGesture()}
|
| 85 |
+
>
|
| 86 |
+
{i18n.t('Load Gesture')}
|
| 87 |
+
</Menubar.Item>
|
| 88 |
+
<Menubar.Item
|
| 89 |
+
className={MenubarItem}
|
| 90 |
+
onSelect={() => helper.SaveGesture()}
|
| 91 |
+
>
|
| 92 |
+
{i18n.t('Save Gesture')}
|
| 93 |
+
</Menubar.Item>
|
| 94 |
+
<Menubar.Item
|
| 95 |
+
className={MenubarItem}
|
| 96 |
+
onSelect={() => {
|
| 97 |
+
helper.GenerateSceneURL()
|
| 98 |
+
}}
|
| 99 |
+
>
|
| 100 |
+
{i18n.t('Generate Scene URL')}
|
| 101 |
+
</Menubar.Item>
|
| 102 |
+
<Menubar.Separator className={MenubarSeparator} />
|
| 103 |
+
<Menubar.Item
|
| 104 |
+
className={MenubarItem}
|
| 105 |
+
onSelect={() => editor.RestoreLastSavedScene()}
|
| 106 |
+
>
|
| 107 |
+
{i18n.t('Restore Last Scene')}
|
| 108 |
+
</Menubar.Item>
|
| 109 |
+
<Menubar.Separator className={MenubarSeparator} />
|
| 110 |
+
<Menubar.Item
|
| 111 |
+
className={MenubarItem}
|
| 112 |
+
onSelect={() =>
|
| 113 |
+
helper.DetectFromImage(onChangeBackground)
|
| 114 |
+
}
|
| 115 |
+
>
|
| 116 |
+
{i18n.t('Detect From Image')}
|
| 117 |
+
</Menubar.Item>
|
| 118 |
+
{IsChina() ? (
|
| 119 |
+
<Menubar.Item
|
| 120 |
+
className={MenubarItem}
|
| 121 |
+
onSelect={() => {
|
| 122 |
+
SetCDNBase(false)
|
| 123 |
+
helper.DetectFromImage(onChangeBackground)
|
| 124 |
+
}}
|
| 125 |
+
>
|
| 126 |
+
{i18n.t('Detect From Image') + ' [中国]'}
|
| 127 |
+
</Menubar.Item>
|
| 128 |
+
) : undefined}
|
| 129 |
+
<Menubar.Item
|
| 130 |
+
className={MenubarItem}
|
| 131 |
+
onSelect={() => helper.SetRandomPose()}
|
| 132 |
+
>
|
| 133 |
+
{i18n.t('Set Random Pose')}
|
| 134 |
+
</Menubar.Item>
|
| 135 |
+
<Menubar.Item
|
| 136 |
+
className={MenubarItem}
|
| 137 |
+
onSelect={async () => {
|
| 138 |
+
const image = await uploadImage()
|
| 139 |
+
if (image) onChangeBackground(image)
|
| 140 |
+
}}
|
| 141 |
+
>
|
| 142 |
+
{i18n.t('Set Background Image')}
|
| 143 |
+
</Menubar.Item>
|
| 144 |
+
<Menubar.Item className={MenubarItem}>
|
| 145 |
+
v{__APP_VERSION__}
|
| 146 |
+
</Menubar.Item>
|
| 147 |
+
</Menubar.Content>
|
| 148 |
+
</Menubar.Portal>
|
| 149 |
+
</Menubar.Menu>
|
| 150 |
+
|
| 151 |
+
<Menubar.Menu>
|
| 152 |
+
<Menubar.Trigger className={MenubarTrigger}>
|
| 153 |
+
{i18n.t('Edit')}
|
| 154 |
+
</Menubar.Trigger>
|
| 155 |
+
<Menubar.Portal>
|
| 156 |
+
<Menubar.Content
|
| 157 |
+
className={MenubarContent}
|
| 158 |
+
align="start"
|
| 159 |
+
sideOffset={5}
|
| 160 |
+
alignOffset={-3}
|
| 161 |
+
>
|
| 162 |
+
<Menubar.Item
|
| 163 |
+
className={MenubarItem}
|
| 164 |
+
onSelect={() => editor.Undo()}
|
| 165 |
+
>
|
| 166 |
+
{i18n.t('Undo')}
|
| 167 |
+
<div className={RightSlot}>⌘ Z</div>
|
| 168 |
+
</Menubar.Item>
|
| 169 |
+
<Menubar.Item
|
| 170 |
+
className={MenubarItem}
|
| 171 |
+
onSelect={() => editor.Redo()}
|
| 172 |
+
>
|
| 173 |
+
{i18n.t('Redo')}
|
| 174 |
+
<div className={RightSlot}>⇧ ⌘ Z</div>
|
| 175 |
+
</Menubar.Item>
|
| 176 |
+
<Menubar.Separator className={MenubarSeparator} />
|
| 177 |
+
<Menubar.Item
|
| 178 |
+
className={MenubarItem}
|
| 179 |
+
onSelect={() => editor.CopySelectedBody()}
|
| 180 |
+
>
|
| 181 |
+
{i18n.t('Duplicate Skeleton')}
|
| 182 |
+
<div className={RightSlot}>⇧ D</div>
|
| 183 |
+
</Menubar.Item>
|
| 184 |
+
<Menubar.Item
|
| 185 |
+
className={MenubarItem}
|
| 186 |
+
onSelect={() => editor.RemoveBody()}
|
| 187 |
+
>
|
| 188 |
+
{i18n.t('Delete Skeleton')}
|
| 189 |
+
<div className={RightSlot}>{i18n.t('Del')}</div>
|
| 190 |
+
</Menubar.Item>
|
| 191 |
+
</Menubar.Content>
|
| 192 |
+
</Menubar.Portal>
|
| 193 |
+
</Menubar.Menu>
|
| 194 |
+
|
| 195 |
+
<Menubar.Menu>
|
| 196 |
+
<Menubar.Trigger className={MenubarTrigger}>
|
| 197 |
+
{i18n.t('View')}
|
| 198 |
+
</Menubar.Trigger>
|
| 199 |
+
<Menubar.Portal>
|
| 200 |
+
<Menubar.Content
|
| 201 |
+
className={MenubarContent}
|
| 202 |
+
align="start"
|
| 203 |
+
sideOffset={5}
|
| 204 |
+
alignOffset={-14}
|
| 205 |
+
>
|
| 206 |
+
<Menubar.Item
|
| 207 |
+
className={classNames(MenubarItem, inset)}
|
| 208 |
+
onSelect={() => {
|
| 209 |
+
editor.LockView()
|
| 210 |
+
}}
|
| 211 |
+
>
|
| 212 |
+
{i18n.t('Lock View')}
|
| 213 |
+
</Menubar.Item>
|
| 214 |
+
<Menubar.Item
|
| 215 |
+
className={classNames(MenubarItem, inset)}
|
| 216 |
+
onSelect={() => {
|
| 217 |
+
editor.UnlockView()
|
| 218 |
+
}}
|
| 219 |
+
>
|
| 220 |
+
{i18n.t('Unlock View')}
|
| 221 |
+
</Menubar.Item>
|
| 222 |
+
<Menubar.Item
|
| 223 |
+
className={classNames(MenubarItem, inset)}
|
| 224 |
+
onSelect={() => {
|
| 225 |
+
editor.RestoreView()
|
| 226 |
+
}}
|
| 227 |
+
>
|
| 228 |
+
{i18n.t('Restore View')}
|
| 229 |
+
</Menubar.Item>
|
| 230 |
+
</Menubar.Content>
|
| 231 |
+
</Menubar.Portal>
|
| 232 |
+
</Menubar.Menu>
|
| 233 |
+
|
| 234 |
+
<Menubar.Menu>
|
| 235 |
+
<Menubar.Trigger className={MenubarTrigger}>
|
| 236 |
+
{i18n.t('Setting')}
|
| 237 |
+
</Menubar.Trigger>
|
| 238 |
+
<Menubar.Portal>
|
| 239 |
+
<Menubar.Content
|
| 240 |
+
className={MenubarContent}
|
| 241 |
+
align="start"
|
| 242 |
+
sideOffset={5}
|
| 243 |
+
alignOffset={-14}
|
| 244 |
+
>
|
| 245 |
+
<Menubar.CheckboxItem
|
| 246 |
+
className={classNames(MenubarCheckboxItem, inset)}
|
| 247 |
+
checked={editor.MoveMode}
|
| 248 |
+
onCheckedChange={() => {
|
| 249 |
+
editor.MoveMode = !editor.MoveMode
|
| 250 |
+
forceUpdate()
|
| 251 |
+
}}
|
| 252 |
+
>
|
| 253 |
+
<Menubar.ItemIndicator
|
| 254 |
+
className={MenubarItemIndicator}
|
| 255 |
+
>
|
| 256 |
+
<CheckIcon />
|
| 257 |
+
</Menubar.ItemIndicator>
|
| 258 |
+
{i18n.t('Move Mode')}
|
| 259 |
+
</Menubar.CheckboxItem>
|
| 260 |
+
<Menubar.CheckboxItem
|
| 261 |
+
className={classNames(MenubarCheckboxItem, inset)}
|
| 262 |
+
checked={editor.FreeMode}
|
| 263 |
+
onCheckedChange={() => {
|
| 264 |
+
editor.FreeMode = !editor.FreeMode
|
| 265 |
+
forceUpdate()
|
| 266 |
+
}}
|
| 267 |
+
>
|
| 268 |
+
<Menubar.ItemIndicator
|
| 269 |
+
className={MenubarItemIndicator}
|
| 270 |
+
>
|
| 271 |
+
<CheckIcon />
|
| 272 |
+
</Menubar.ItemIndicator>
|
| 273 |
+
{i18n.t('Free Mode')}
|
| 274 |
+
</Menubar.CheckboxItem>
|
| 275 |
+
<Menubar.CheckboxItem
|
| 276 |
+
className={classNames(MenubarCheckboxItem, inset)}
|
| 277 |
+
checked={editor.OnlyHand}
|
| 278 |
+
onCheckedChange={() => {
|
| 279 |
+
editor.OnlyHand = !editor.OnlyHand
|
| 280 |
+
forceUpdate()
|
| 281 |
+
}}
|
| 282 |
+
>
|
| 283 |
+
<Menubar.ItemIndicator
|
| 284 |
+
className={MenubarItemIndicator}
|
| 285 |
+
>
|
| 286 |
+
<CheckIcon />
|
| 287 |
+
</Menubar.ItemIndicator>
|
| 288 |
+
{i18n.t('Only Hand')}
|
| 289 |
+
</Menubar.CheckboxItem>
|
| 290 |
+
<Menubar.CheckboxItem
|
| 291 |
+
className={classNames(MenubarCheckboxItem, inset)}
|
| 292 |
+
checked={editor.enablePreview}
|
| 293 |
+
onCheckedChange={() => {
|
| 294 |
+
editor.enablePreview = !editor.enablePreview
|
| 295 |
+
forceUpdate()
|
| 296 |
+
}}
|
| 297 |
+
>
|
| 298 |
+
<Menubar.ItemIndicator
|
| 299 |
+
className={MenubarItemIndicator}
|
| 300 |
+
>
|
| 301 |
+
<CheckIcon />
|
| 302 |
+
</Menubar.ItemIndicator>
|
| 303 |
+
{i18n.t('Show Preview')}
|
| 304 |
+
</Menubar.CheckboxItem>
|
| 305 |
+
<Menubar.CheckboxItem
|
| 306 |
+
className={classNames(MenubarCheckboxItem, inset)}
|
| 307 |
+
checked={editor.EnableHelper}
|
| 308 |
+
onCheckedChange={() => {
|
| 309 |
+
editor.EnableHelper = !editor.EnableHelper
|
| 310 |
+
forceUpdate()
|
| 311 |
+
}}
|
| 312 |
+
>
|
| 313 |
+
<Menubar.ItemIndicator
|
| 314 |
+
className={MenubarItemIndicator}
|
| 315 |
+
>
|
| 316 |
+
<CheckIcon />
|
| 317 |
+
</Menubar.ItemIndicator>
|
| 318 |
+
{i18n.t('Show Grid')}
|
| 319 |
+
</Menubar.CheckboxItem>
|
| 320 |
+
</Menubar.Content>
|
| 321 |
+
</Menubar.Portal>
|
| 322 |
+
</Menubar.Menu>
|
| 323 |
+
|
| 324 |
+
<Menubar.Menu>
|
| 325 |
+
<Menubar.Trigger className={MenubarTrigger}>
|
| 326 |
+
{i18n.t('Feedback')}
|
| 327 |
+
</Menubar.Trigger>
|
| 328 |
+
<Menubar.Portal>
|
| 329 |
+
<Menubar.Content
|
| 330 |
+
className={MenubarContent}
|
| 331 |
+
align="start"
|
| 332 |
+
sideOffset={5}
|
| 333 |
+
alignOffset={-14}
|
| 334 |
+
>
|
| 335 |
+
<Menubar.Item
|
| 336 |
+
className={classNames(MenubarItem, inset)}
|
| 337 |
+
onSelect={() => {
|
| 338 |
+
helper.FeedbackByGithub()
|
| 339 |
+
}}
|
| 340 |
+
>
|
| 341 |
+
Github
|
| 342 |
+
</Menubar.Item>
|
| 343 |
+
<Menubar.Item
|
| 344 |
+
className={classNames(MenubarItem, inset)}
|
| 345 |
+
onSelect={() => {
|
| 346 |
+
helper.FeedbackByQQ()
|
| 347 |
+
}}
|
| 348 |
+
>
|
| 349 |
+
QQ
|
| 350 |
+
</Menubar.Item>
|
| 351 |
+
</Menubar.Content>
|
| 352 |
+
</Menubar.Portal>
|
| 353 |
+
</Menubar.Menu>
|
| 354 |
+
<Menubar.Menu>
|
| 355 |
+
<Menubar.Trigger className={MenubarTrigger}>
|
| 356 |
+
Language
|
| 357 |
+
</Menubar.Trigger>
|
| 358 |
+
<Menubar.Portal>
|
| 359 |
+
<Menubar.Content
|
| 360 |
+
className={MenubarContent}
|
| 361 |
+
align="start"
|
| 362 |
+
sideOffset={5}
|
| 363 |
+
alignOffset={-14}
|
| 364 |
+
>
|
| 365 |
+
<Menubar.RadioGroup
|
| 366 |
+
value={current}
|
| 367 |
+
onValueChange={(v) => {
|
| 368 |
+
changeLanguage(v)
|
| 369 |
+
}}
|
| 370 |
+
>
|
| 371 |
+
{languagList.map((item) => (
|
| 372 |
+
<Menubar.RadioItem
|
| 373 |
+
className={classNames(
|
| 374 |
+
MenubarRadioItem,
|
| 375 |
+
inset
|
| 376 |
+
)}
|
| 377 |
+
key={item}
|
| 378 |
+
value={item}
|
| 379 |
+
>
|
| 380 |
+
<Menubar.ItemIndicator
|
| 381 |
+
className={MenubarItemIndicator}
|
| 382 |
+
>
|
| 383 |
+
<DotFilledIcon />
|
| 384 |
+
</Menubar.ItemIndicator>
|
| 385 |
+
{item}
|
| 386 |
+
</Menubar.RadioItem>
|
| 387 |
+
))}
|
| 388 |
+
</Menubar.RadioGroup>
|
| 389 |
+
</Menubar.Content>
|
| 390 |
+
</Menubar.Portal>
|
| 391 |
+
</Menubar.Menu>
|
| 392 |
+
<Menubar.Menu>
|
| 393 |
+
<Menubar.Trigger
|
| 394 |
+
className={classNames(MenubarTrigger, Blue)}
|
| 395 |
+
onClick={async () => {
|
| 396 |
+
const image = editor.MakeImages()
|
| 397 |
+
const result = Object.fromEntries(
|
| 398 |
+
Object.entries(image).map(([name, imgData]) => [
|
| 399 |
+
name,
|
| 400 |
+
{
|
| 401 |
+
src: imgData,
|
| 402 |
+
title: name + '_' + getCurrentTime(),
|
| 403 |
+
},
|
| 404 |
+
])
|
| 405 |
+
)
|
| 406 |
+
onScreenShot(result)
|
| 407 |
+
}}
|
| 408 |
+
>
|
| 409 |
+
{i18n.t('Generate')}
|
| 410 |
+
</Menubar.Trigger>
|
| 411 |
+
</Menubar.Menu>
|
| 412 |
+
</Menubar.Root>
|
| 413 |
+
)
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
export default MenubarDemo
|
sd-webui-3d-open-pose-editor/src/components/Menu/styles.module.css
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import '@radix-ui/colors/blackA.css';
|
| 2 |
+
@import '@radix-ui/colors/mauve.css';
|
| 3 |
+
@import '@radix-ui/colors/violet.css';
|
| 4 |
+
|
| 5 |
+
.MenubarRoot {
|
| 6 |
+
display: flex;
|
| 7 |
+
background-color: white;
|
| 8 |
+
padding: 3px;
|
| 9 |
+
border-radius: 6px;
|
| 10 |
+
box-shadow: 0 2px 10px var(--blackA7);
|
| 11 |
+
font-family: var(--openpose-editor-font-family);
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
.MenubarTrigger {
|
| 15 |
+
all: unset;
|
| 16 |
+
padding: 8px 12px;
|
| 17 |
+
outline: none;
|
| 18 |
+
user-select: none;
|
| 19 |
+
font-weight: 500;
|
| 20 |
+
line-height: 1;
|
| 21 |
+
border-radius: 4px;
|
| 22 |
+
color: var(--violet11);
|
| 23 |
+
font-size: 13px;
|
| 24 |
+
display: flex;
|
| 25 |
+
align-items: center;
|
| 26 |
+
justify-content: space-between;
|
| 27 |
+
gap: 2px;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
.MenubarTrigger[data-highlighted],
|
| 31 |
+
.MenubarTrigger[data-state='open'] {
|
| 32 |
+
background-color: var(--violet4);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.MenubarTrigger.Blue {
|
| 36 |
+
background-color: var(--violet9);
|
| 37 |
+
color: white;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.MenubarContent,
|
| 41 |
+
.MenubarSubContent {
|
| 42 |
+
min-width: 220px;
|
| 43 |
+
background-color: white;
|
| 44 |
+
border-radius: 6px;
|
| 45 |
+
padding: 5px;
|
| 46 |
+
box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35),
|
| 47 |
+
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
|
| 48 |
+
animation-duration: 400ms;
|
| 49 |
+
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
| 50 |
+
will-change: transform, opacity;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
.MenubarItem,
|
| 54 |
+
.MenubarSubTrigger,
|
| 55 |
+
.MenubarCheckboxItem,
|
| 56 |
+
.MenubarRadioItem {
|
| 57 |
+
all: unset;
|
| 58 |
+
font-size: 13px;
|
| 59 |
+
line-height: 1;
|
| 60 |
+
color: var(--violet11);
|
| 61 |
+
border-radius: 4px;
|
| 62 |
+
display: flex;
|
| 63 |
+
align-items: center;
|
| 64 |
+
height: 25px;
|
| 65 |
+
padding: 0 10px;
|
| 66 |
+
position: relative;
|
| 67 |
+
user-select: none;
|
| 68 |
+
font-family: var(--openpose-editor-font-family);
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
.MenubarItem.inset,
|
| 72 |
+
.MenubarSubTrigger.inset,
|
| 73 |
+
.MenubarCheckboxItem.inset,
|
| 74 |
+
.MenubarRadioItem.inset {
|
| 75 |
+
padding-left: 20px;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.MenubarItem[data-state='open'],
|
| 79 |
+
.MenubarSubTrigger[data-state='open'] {
|
| 80 |
+
background-color: var(--violet4);
|
| 81 |
+
color: var(--violet11);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.MenubarItem[data-highlighted],
|
| 85 |
+
.MenubarSubTrigger[data-highlighted],
|
| 86 |
+
.MenubarCheckboxItem[data-highlighted],
|
| 87 |
+
.MenubarRadioItem[data-highlighted] {
|
| 88 |
+
background-image: linear-gradient(
|
| 89 |
+
135deg,
|
| 90 |
+
var(--violet9) 0%,
|
| 91 |
+
var(--violet10) 100%
|
| 92 |
+
);
|
| 93 |
+
color: var(--violet1);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.MenubarItem[data-disabled],
|
| 97 |
+
.MenubarSubTrigger[data-disabled],
|
| 98 |
+
.MenubarCheckboxItem[data-disabled],
|
| 99 |
+
.MenubarRadioItem[data-disabled] {
|
| 100 |
+
color: var(--mauve8);
|
| 101 |
+
pointer-events: none;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.MenubarItemIndicator {
|
| 105 |
+
position: absolute;
|
| 106 |
+
left: 0;
|
| 107 |
+
width: 20px;
|
| 108 |
+
display: inline-flex;
|
| 109 |
+
align-items: center;
|
| 110 |
+
justify-content: center;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
.MenubarSeparator {
|
| 114 |
+
height: 1px;
|
| 115 |
+
background-color: var(--violet6);
|
| 116 |
+
margin: 5px;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
.RightSlot {
|
| 120 |
+
margin-left: auto;
|
| 121 |
+
padding-left: 20px;
|
| 122 |
+
color: var(--mauve9);
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
[data-highlighted] > .RightSlot {
|
| 126 |
+
color: white;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
[data-disabled] > .RightSlot {
|
| 130 |
+
color: var(--mauve8);
|
| 131 |
+
}
|
sd-webui-3d-open-pose-editor/src/components/Oops.tsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import i18n from '../i18n'
|
| 2 |
+
import { ShowDialog } from './Dialog'
|
| 3 |
+
|
| 4 |
+
export function Oops(error: any) {
|
| 5 |
+
const stack = error?.stack
|
| 6 |
+
const text = `${i18n.t('Something went wrong!')}\n${stack || error}`
|
| 7 |
+
ShowDialog({
|
| 8 |
+
title: i18n.t('Oops...') ?? '',
|
| 9 |
+
description: text,
|
| 10 |
+
children: (
|
| 11 |
+
<a href="https://github.com/nonnonstop/sd-webui-3d-open-pose-editor/issues/new/choose">
|
| 12 |
+
{i18n.t(
|
| 13 |
+
'If the problem persists, please click here to ask a question.'
|
| 14 |
+
)}
|
| 15 |
+
</a>
|
| 16 |
+
),
|
| 17 |
+
button: i18n.t('Close') ?? '',
|
| 18 |
+
})
|
| 19 |
+
}
|
sd-webui-3d-open-pose-editor/src/components/PopupOver/index.tsx
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useEffect, useMemo, useState } from 'react'
|
| 2 |
+
import * as Popover from '@radix-ui/react-popover'
|
| 3 |
+
import { PinLeftIcon } from '@radix-ui/react-icons'
|
| 4 |
+
import classes from './styles.module.css'
|
| 5 |
+
import Slider from '../Slider'
|
| 6 |
+
import { BodyEditor } from '../../editor'
|
| 7 |
+
import useForceUpdate from '../../hooks/useFoceUpdate'
|
| 8 |
+
import i18n from '../../i18n'
|
| 9 |
+
import { BodyControlor } from '../../body'
|
| 10 |
+
|
| 11 |
+
const { PopoverContent, IconButton, PopoverArrow, Input } = classes
|
| 12 |
+
|
| 13 |
+
const Slider2: React.FC<{
|
| 14 |
+
type: 'int' | 'float' | undefined
|
| 15 |
+
name: string
|
| 16 |
+
range: [number, number]
|
| 17 |
+
getValue(): number
|
| 18 |
+
onChange?: (value: number) => void
|
| 19 |
+
onValueCommit?: (value: number) => void
|
| 20 |
+
forceUpdate: () => any
|
| 21 |
+
}> = ({
|
| 22 |
+
type,
|
| 23 |
+
name,
|
| 24 |
+
range,
|
| 25 |
+
getValue,
|
| 26 |
+
onChange,
|
| 27 |
+
onValueCommit,
|
| 28 |
+
forceUpdate,
|
| 29 |
+
}) => {
|
| 30 |
+
const value = getValue()
|
| 31 |
+
const [inputValue, setInputValue] = useState(() =>
|
| 32 |
+
type == 'int' ? value.toString() : getValue().toFixed(2)
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
useEffect(() => {
|
| 36 |
+
setInputValue(type == 'int' ? value.toString() : getValue().toFixed(2))
|
| 37 |
+
}, [value])
|
| 38 |
+
|
| 39 |
+
return (
|
| 40 |
+
<div
|
| 41 |
+
style={{
|
| 42 |
+
display: 'flex',
|
| 43 |
+
justifyContent: 'flex-end',
|
| 44 |
+
}}
|
| 45 |
+
>
|
| 46 |
+
<div
|
| 47 |
+
style={{
|
| 48 |
+
minWidth: 60,
|
| 49 |
+
maxWidth: 120,
|
| 50 |
+
// width:"max-content",
|
| 51 |
+
overflow: 'hidden',
|
| 52 |
+
color: 'gray',
|
| 53 |
+
fontSize: '70%',
|
| 54 |
+
marginInlineEnd: 10,
|
| 55 |
+
// whiteSpace: 'nowrap',
|
| 56 |
+
textAlign: 'end',
|
| 57 |
+
textOverflow: 'ellipsis',
|
| 58 |
+
}}
|
| 59 |
+
>
|
| 60 |
+
{name}
|
| 61 |
+
</div>
|
| 62 |
+
<Slider
|
| 63 |
+
key={name}
|
| 64 |
+
range={range}
|
| 65 |
+
value={getValue()}
|
| 66 |
+
onValueChange={(value: number) => {
|
| 67 |
+
if (type == 'int') {
|
| 68 |
+
onChange?.(Math.round(value))
|
| 69 |
+
} else onChange?.(value)
|
| 70 |
+
forceUpdate()
|
| 71 |
+
}}
|
| 72 |
+
onValueCommit={onValueCommit}
|
| 73 |
+
style={{
|
| 74 |
+
width: 150,
|
| 75 |
+
}}
|
| 76 |
+
></Slider>
|
| 77 |
+
<input
|
| 78 |
+
className={Input}
|
| 79 |
+
style={{
|
| 80 |
+
marginInlineStart: 10,
|
| 81 |
+
width: 60,
|
| 82 |
+
height: 20,
|
| 83 |
+
color: 'gray',
|
| 84 |
+
fontSize: '70%',
|
| 85 |
+
}}
|
| 86 |
+
value={inputValue}
|
| 87 |
+
onChange={(event) => {
|
| 88 |
+
const value = event.target.value
|
| 89 |
+
|
| 90 |
+
setInputValue(value)
|
| 91 |
+
}}
|
| 92 |
+
onBlur={() => {
|
| 93 |
+
try {
|
| 94 |
+
let v = parseFloat(inputValue)
|
| 95 |
+
if (isNaN(v)) throw 'Is NaN'
|
| 96 |
+
v = Math.max(Math.min(v, range[1]), range[0])
|
| 97 |
+
console.log(v)
|
| 98 |
+
onChange?.(v)
|
| 99 |
+
} catch (error) {
|
| 100 |
+
console.log('invalid input')
|
| 101 |
+
setInputValue(value.toString())
|
| 102 |
+
}
|
| 103 |
+
}}
|
| 104 |
+
></input>
|
| 105 |
+
</div>
|
| 106 |
+
)
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
function GetCameraParamControlor(editor: BodyEditor) {
|
| 110 |
+
const CameraParamsInit = {
|
| 111 |
+
OutputWidth: { type: 'int', range: [128, 3000], name: i18n.t('Width') },
|
| 112 |
+
OutputHeight: {
|
| 113 |
+
type: 'int',
|
| 114 |
+
range: [128, 3000],
|
| 115 |
+
name: i18n.t('Height'),
|
| 116 |
+
},
|
| 117 |
+
CameraNear: { range: [0.1, 2000], name: i18n.t('Camera Near') },
|
| 118 |
+
CameraFar: { range: [0.1, 20000], name: i18n.t('Camera Far') },
|
| 119 |
+
CameraFocalLength: {
|
| 120 |
+
range: [0.1, 100],
|
| 121 |
+
name: i18n.t('Camera Focal Length'),
|
| 122 |
+
},
|
| 123 |
+
} as const
|
| 124 |
+
|
| 125 |
+
return Object.entries(
|
| 126 |
+
CameraParamsInit as Record<
|
| 127 |
+
keyof typeof CameraParamsInit,
|
| 128 |
+
{
|
| 129 |
+
type: 'int' | 'float' | undefined
|
| 130 |
+
range: [number, number]
|
| 131 |
+
name: string
|
| 132 |
+
}
|
| 133 |
+
>
|
| 134 |
+
).map(([paramName, { type, range, name }]) => {
|
| 135 |
+
return {
|
| 136 |
+
type,
|
| 137 |
+
name,
|
| 138 |
+
range,
|
| 139 |
+
getValue() {
|
| 140 |
+
const value = editor[paramName as keyof typeof CameraParamsInit]
|
| 141 |
+
// webui exception in launch
|
| 142 |
+
return isNaN(value) ? range[0] : value
|
| 143 |
+
},
|
| 144 |
+
onChange(value: number) {
|
| 145 |
+
editor[paramName as keyof typeof CameraParamsInit] = value
|
| 146 |
+
},
|
| 147 |
+
}
|
| 148 |
+
})
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
function GetBodyParamControlor(editor: BodyEditor) {
|
| 152 |
+
const BodyParamsInit = {
|
| 153 |
+
BoneThickness: { range: [0.1, 3], name: i18n.t('Bone Thickness') },
|
| 154 |
+
HeadSize: { range: [0.1, 100], name: i18n.t('Head Size') },
|
| 155 |
+
NoseToNeck: { range: [0.1, 100], name: i18n.t('Nose To Neck') },
|
| 156 |
+
ShoulderWidth: { range: [0.1, 100], name: i18n.t('Shoulder Width') },
|
| 157 |
+
ShoulderToHip: { range: [0.1, 100], name: i18n.t('Shoulder To Hip') },
|
| 158 |
+
ArmLength: { range: [0.1, 100], name: i18n.t('Arm Length') },
|
| 159 |
+
Forearm: { range: [0.1, 100], name: i18n.t('Forearm') },
|
| 160 |
+
UpperArm: { range: [0.1, 100], name: i18n.t('Upper Arm') },
|
| 161 |
+
HandSize: { range: [0.1, 10], name: i18n.t('Hand Size') },
|
| 162 |
+
Hips: { range: [0.1, 100], name: i18n.t('Hips') },
|
| 163 |
+
LegLength: { range: [0.1, 100], name: i18n.t('Leg Length') },
|
| 164 |
+
Thigh: { range: [0.1, 100], name: i18n.t('Thigh') },
|
| 165 |
+
LowerLeg: { range: [0.1, 100], name: i18n.t('Lower Leg') },
|
| 166 |
+
FootSize: { range: [0.1, 10], name: i18n.t('Foot Size') },
|
| 167 |
+
} as const
|
| 168 |
+
|
| 169 |
+
function PushExecuteBodyParamsCommand(
|
| 170 |
+
editor: BodyEditor,
|
| 171 |
+
controlor: BodyControlor,
|
| 172 |
+
name: keyof typeof BodyParamsInit,
|
| 173 |
+
oldValue: number,
|
| 174 |
+
value: number
|
| 175 |
+
) {
|
| 176 |
+
console.log(oldValue, value)
|
| 177 |
+
const cmd = {
|
| 178 |
+
execute: () => {
|
| 179 |
+
controlor[name] = value
|
| 180 |
+
controlor.Update()
|
| 181 |
+
},
|
| 182 |
+
undo: () => {
|
| 183 |
+
controlor[name] = oldValue
|
| 184 |
+
controlor.Update()
|
| 185 |
+
},
|
| 186 |
+
}
|
| 187 |
+
cmd.execute()
|
| 188 |
+
editor.pushCommand(cmd)
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
let currentBody = editor.getSelectedBody()
|
| 192 |
+
let currentControlor: BodyControlor | null = currentBody
|
| 193 |
+
? new BodyControlor(currentBody)
|
| 194 |
+
: null
|
| 195 |
+
|
| 196 |
+
const getCurrentControlor = () => {
|
| 197 |
+
const body = editor.getSelectedBody()
|
| 198 |
+
|
| 199 |
+
if (body !== currentBody) {
|
| 200 |
+
currentBody = body
|
| 201 |
+
currentControlor = body ? new BodyControlor(body) : null
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
return currentControlor
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
let oldValue = 0
|
| 208 |
+
let changing = false
|
| 209 |
+
|
| 210 |
+
return Object.entries(
|
| 211 |
+
BodyParamsInit as Record<
|
| 212 |
+
keyof typeof BodyParamsInit,
|
| 213 |
+
{
|
| 214 |
+
type: 'int' | 'float' | undefined
|
| 215 |
+
range: [number, number]
|
| 216 |
+
name: string
|
| 217 |
+
}
|
| 218 |
+
>
|
| 219 |
+
).map(([_paramName, { type, range, name }]) => {
|
| 220 |
+
return {
|
| 221 |
+
type,
|
| 222 |
+
name,
|
| 223 |
+
range,
|
| 224 |
+
getValue: () => {
|
| 225 |
+
const paramName = _paramName as keyof typeof BodyParamsInit
|
| 226 |
+
const controlor = getCurrentControlor()
|
| 227 |
+
if (controlor) {
|
| 228 |
+
return controlor[paramName]
|
| 229 |
+
}
|
| 230 |
+
return -1
|
| 231 |
+
},
|
| 232 |
+
onChange(value: number) {
|
| 233 |
+
const paramName = _paramName as keyof typeof BodyParamsInit
|
| 234 |
+
const controlor = getCurrentControlor()
|
| 235 |
+
|
| 236 |
+
if (controlor) {
|
| 237 |
+
// the first time
|
| 238 |
+
if (changing == false) oldValue = controlor[paramName]
|
| 239 |
+
changing = true
|
| 240 |
+
controlor[paramName] = value
|
| 241 |
+
}
|
| 242 |
+
},
|
| 243 |
+
onValueCommit(value: number) {
|
| 244 |
+
const paramName = _paramName as keyof typeof BodyParamsInit
|
| 245 |
+
const controlor = getCurrentControlor()
|
| 246 |
+
|
| 247 |
+
if (controlor) {
|
| 248 |
+
changing = false
|
| 249 |
+
PushExecuteBodyParamsCommand(
|
| 250 |
+
editor,
|
| 251 |
+
controlor,
|
| 252 |
+
paramName,
|
| 253 |
+
oldValue,
|
| 254 |
+
value
|
| 255 |
+
)
|
| 256 |
+
controlor[paramName] = value
|
| 257 |
+
}
|
| 258 |
+
},
|
| 259 |
+
}
|
| 260 |
+
})
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
const ControlorPopover: React.FC<{
|
| 264 |
+
editor: BodyEditor
|
| 265 |
+
style?: React.CSSProperties
|
| 266 |
+
}> = ({ editor, style }) => {
|
| 267 |
+
const forceUpdate = useForceUpdate()
|
| 268 |
+
const [open, setOpen] = useState(true)
|
| 269 |
+
|
| 270 |
+
const cameraParamControlor = useMemo(() => {
|
| 271 |
+
return GetCameraParamControlor(editor)
|
| 272 |
+
}, [editor])
|
| 273 |
+
const bodyParamControlor = useMemo(() => {
|
| 274 |
+
return GetBodyParamControlor(editor)
|
| 275 |
+
}, [editor])
|
| 276 |
+
|
| 277 |
+
const [bodySelected, setBodySelected] = useState(false)
|
| 278 |
+
useEffect(() => {
|
| 279 |
+
const select = () => {
|
| 280 |
+
setBodySelected(true)
|
| 281 |
+
}
|
| 282 |
+
const unselect = () => {
|
| 283 |
+
setBodySelected(false)
|
| 284 |
+
}
|
| 285 |
+
editor.SelectEventManager.AddEventListener(select)
|
| 286 |
+
editor.UnselectEventManager.AddEventListener(unselect)
|
| 287 |
+
|
| 288 |
+
return () => {
|
| 289 |
+
editor.SelectEventManager.RemoveEventListener(select)
|
| 290 |
+
editor.UnselectEventManager.RemoveEventListener(unselect)
|
| 291 |
+
}
|
| 292 |
+
}, [editor])
|
| 293 |
+
return (
|
| 294 |
+
<Popover.Root open={open}>
|
| 295 |
+
<Popover.Trigger asChild>
|
| 296 |
+
<button
|
| 297 |
+
className={IconButton}
|
| 298 |
+
style={style}
|
| 299 |
+
onClick={() => setOpen((v) => !v)}
|
| 300 |
+
>
|
| 301 |
+
<PinLeftIcon />
|
| 302 |
+
</button>
|
| 303 |
+
</Popover.Trigger>
|
| 304 |
+
<Popover.Portal>
|
| 305 |
+
<Popover.Content className={PopoverContent} sideOffset={5}>
|
| 306 |
+
<div
|
| 307 |
+
style={{
|
| 308 |
+
display: 'flex',
|
| 309 |
+
flexDirection: 'column',
|
| 310 |
+
gap: 10,
|
| 311 |
+
}}
|
| 312 |
+
>
|
| 313 |
+
{cameraParamControlor.map((props, index) => (
|
| 314 |
+
<Slider2
|
| 315 |
+
key={index}
|
| 316 |
+
{...props}
|
| 317 |
+
forceUpdate={forceUpdate}
|
| 318 |
+
></Slider2>
|
| 319 |
+
))}
|
| 320 |
+
{bodySelected ? (
|
| 321 |
+
<>
|
| 322 |
+
<div
|
| 323 |
+
style={{
|
| 324 |
+
fontSize: 15,
|
| 325 |
+
marginTop: 10,
|
| 326 |
+
marginBottom: 8,
|
| 327 |
+
}}
|
| 328 |
+
>
|
| 329 |
+
{i18n.t('Body Parameters')}
|
| 330 |
+
</div>
|
| 331 |
+
{bodyParamControlor.map((props, index) => (
|
| 332 |
+
<Slider2
|
| 333 |
+
key={index}
|
| 334 |
+
{...props}
|
| 335 |
+
forceUpdate={forceUpdate}
|
| 336 |
+
></Slider2>
|
| 337 |
+
))}
|
| 338 |
+
</>
|
| 339 |
+
) : undefined}
|
| 340 |
+
</div>
|
| 341 |
+
<Popover.Arrow className={PopoverArrow} />
|
| 342 |
+
</Popover.Content>
|
| 343 |
+
</Popover.Portal>
|
| 344 |
+
</Popover.Root>
|
| 345 |
+
)
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
export default ControlorPopover
|
sd-webui-3d-open-pose-editor/src/components/PopupOver/styles.module.css
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import '@radix-ui/colors/blackA.css';
|
| 2 |
+
@import '@radix-ui/colors/mauve.css';
|
| 3 |
+
@import '@radix-ui/colors/violet.css';
|
| 4 |
+
|
| 5 |
+
.PopoverContent {
|
| 6 |
+
border-radius: 4px;
|
| 7 |
+
padding: 20px;
|
| 8 |
+
/* width: 260px; */
|
| 9 |
+
background-color: white;
|
| 10 |
+
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
|
| 11 |
+
hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
|
| 12 |
+
animation-duration: 400ms;
|
| 13 |
+
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
| 14 |
+
will-change: transform, opacity;
|
| 15 |
+
font-family: var(--openpose-editor-font-family);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.PopoverContent:focus {
|
| 19 |
+
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
|
| 20 |
+
hsl(206 22% 7% / 20%) 0px 10px 20px -15px, 0 0 0 2px var(--violet7);
|
| 21 |
+
}
|
| 22 |
+
.PopoverContent[data-state='open'][data-side='top'] {
|
| 23 |
+
animation-name: slideDownAndFade;
|
| 24 |
+
}
|
| 25 |
+
.PopoverContent[data-state='open'][data-side='right'] {
|
| 26 |
+
animation-name: slideLeftAndFade;
|
| 27 |
+
}
|
| 28 |
+
.PopoverContent[data-state='open'][data-side='bottom'] {
|
| 29 |
+
animation-name: slideUpAndFade;
|
| 30 |
+
}
|
| 31 |
+
.PopoverContent[data-state='open'][data-side='left'] {
|
| 32 |
+
animation-name: slideRightAndFade;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.PopoverArrow {
|
| 36 |
+
fill: white;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
.IconButton {
|
| 40 |
+
all: unset;
|
| 41 |
+
font-family: inherit;
|
| 42 |
+
border-radius: 100%;
|
| 43 |
+
height: 35px;
|
| 44 |
+
width: 35px;
|
| 45 |
+
display: inline-flex;
|
| 46 |
+
align-items: center;
|
| 47 |
+
justify-content: center;
|
| 48 |
+
color: var(--violet11);
|
| 49 |
+
background-color: white;
|
| 50 |
+
box-shadow: 0 2px 10px var(--blackA7);
|
| 51 |
+
}
|
| 52 |
+
.IconButton:hover {
|
| 53 |
+
background-color: var(--violet3);
|
| 54 |
+
}
|
| 55 |
+
.IconButton:focus {
|
| 56 |
+
box-shadow: 0 0 0 2px var(--violet6);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
@keyframes slideUpAndFade {
|
| 60 |
+
from {
|
| 61 |
+
opacity: 0;
|
| 62 |
+
transform: translateY(2px);
|
| 63 |
+
}
|
| 64 |
+
to {
|
| 65 |
+
opacity: 1;
|
| 66 |
+
transform: translateY(0);
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
@keyframes slideRightAndFade {
|
| 71 |
+
from {
|
| 72 |
+
opacity: 0;
|
| 73 |
+
transform: translateX(-2px);
|
| 74 |
+
}
|
| 75 |
+
to {
|
| 76 |
+
opacity: 1;
|
| 77 |
+
transform: translateX(0);
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
@keyframes slideDownAndFade {
|
| 82 |
+
from {
|
| 83 |
+
opacity: 0;
|
| 84 |
+
transform: translateY(-2px);
|
| 85 |
+
}
|
| 86 |
+
to {
|
| 87 |
+
opacity: 1;
|
| 88 |
+
transform: translateY(0);
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
@keyframes slideLeftAndFade {
|
| 93 |
+
from {
|
| 94 |
+
opacity: 0;
|
| 95 |
+
transform: translateX(2px);
|
| 96 |
+
}
|
| 97 |
+
to {
|
| 98 |
+
opacity: 1;
|
| 99 |
+
transform: translateX(0);
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
Input {
|
| 104 |
+
background-color: var(--violet5);
|
| 105 |
+
border: none;
|
| 106 |
+
box-sizing: border-box;
|
| 107 |
+
padding-left: 5px;
|
| 108 |
+
padding-right: 5px;
|
| 109 |
+
border-radius: 7px;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
Input:focus {
|
| 113 |
+
border: 2px var(--violet11) solid;
|
| 114 |
+
border-radius: 5;
|
| 115 |
+
}
|
sd-webui-3d-open-pose-editor/src/components/Slider/index.tsx
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react'
|
| 2 |
+
import * as Slider from '@radix-ui/react-slider'
|
| 3 |
+
import classes from './styles.module.css'
|
| 4 |
+
|
| 5 |
+
const { SliderRoot, SliderTrack, SliderRange } = classes
|
| 6 |
+
const SliderDemo: React.FC<{
|
| 7 |
+
range: [number, number]
|
| 8 |
+
value: number
|
| 9 |
+
onValueChange?: (value: number) => void
|
| 10 |
+
onValueCommit?: (value: number) => void
|
| 11 |
+
style?: React.CSSProperties
|
| 12 |
+
}> = ({ range, value, onValueChange, onValueCommit, style }) => (
|
| 13 |
+
<form
|
| 14 |
+
style={{
|
| 15 |
+
...style,
|
| 16 |
+
}}
|
| 17 |
+
>
|
| 18 |
+
<Slider.Root
|
| 19 |
+
className={SliderRoot}
|
| 20 |
+
value={[value]}
|
| 21 |
+
min={range[0]}
|
| 22 |
+
max={range[1]}
|
| 23 |
+
step={(range[1] - range[0]) / 150.0}
|
| 24 |
+
onValueChange={([value]: number[]) => {
|
| 25 |
+
onValueChange?.(value)
|
| 26 |
+
}}
|
| 27 |
+
onValueCommit={([value]: number[]) => {
|
| 28 |
+
onValueCommit?.(value)
|
| 29 |
+
}}
|
| 30 |
+
>
|
| 31 |
+
<Slider.Track className={SliderTrack}>
|
| 32 |
+
<Slider.Range className={SliderRange} />
|
| 33 |
+
</Slider.Track>
|
| 34 |
+
</Slider.Root>
|
| 35 |
+
</form>
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
export default SliderDemo
|
sd-webui-3d-open-pose-editor/src/components/Slider/styles.module.css
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import '@radix-ui/colors/blackA.css';
|
| 2 |
+
@import '@radix-ui/colors/violet.css';
|
| 3 |
+
|
| 4 |
+
.SliderRoot {
|
| 5 |
+
position: relative;
|
| 6 |
+
display: flex;
|
| 7 |
+
align-items: center;
|
| 8 |
+
user-select: none;
|
| 9 |
+
touch-action: none;
|
| 10 |
+
height: 20px;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
.SliderTrack {
|
| 14 |
+
background-color: var(--violet5);
|
| 15 |
+
position: relative;
|
| 16 |
+
flex-grow: 1;
|
| 17 |
+
border-radius: 9999px;
|
| 18 |
+
height: 10px;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
.SliderRange {
|
| 22 |
+
position: absolute;
|
| 23 |
+
background-color: var(--violet9);
|
| 24 |
+
border-radius: 9999px;
|
| 25 |
+
height: 100%;
|
| 26 |
+
}
|
sd-webui-3d-open-pose-editor/src/components/Toast/index.tsx
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react'
|
| 2 |
+
import * as Toast from '@radix-ui/react-toast'
|
| 3 |
+
import NiceModal, { useModal } from '@ebay/nice-modal-react'
|
| 4 |
+
import { debounce } from 'lodash-es'
|
| 5 |
+
|
| 6 |
+
import classes from './styles.module.css'
|
| 7 |
+
import classNames from 'classnames'
|
| 8 |
+
|
| 9 |
+
const {
|
| 10 |
+
ToastViewport,
|
| 11 |
+
ToastRoot,
|
| 12 |
+
ToastTitle,
|
| 13 |
+
ToastAction,
|
| 14 |
+
Button,
|
| 15 |
+
small,
|
| 16 |
+
green,
|
| 17 |
+
} = classes
|
| 18 |
+
|
| 19 |
+
const MyToast = NiceModal.create<{
|
| 20 |
+
title: string
|
| 21 |
+
button?: string
|
| 22 |
+
duration?: number
|
| 23 |
+
}>(({ title, button, duration = 3000 }) => {
|
| 24 |
+
const modal = useToast()
|
| 25 |
+
return (
|
| 26 |
+
<Toast.Provider swipeDirection="right" duration={duration}>
|
| 27 |
+
<Toast.Root
|
| 28 |
+
className={ToastRoot}
|
| 29 |
+
defaultOpen={true}
|
| 30 |
+
open={modal.visible}
|
| 31 |
+
onOpenChange={(open) => {
|
| 32 |
+
if (open == false) {
|
| 33 |
+
modal.hide()
|
| 34 |
+
}
|
| 35 |
+
}}
|
| 36 |
+
>
|
| 37 |
+
<Toast.Title className={ToastTitle}>{title}</Toast.Title>
|
| 38 |
+
{button ? (
|
| 39 |
+
<Toast.Action className={ToastAction} asChild altText="">
|
| 40 |
+
<button
|
| 41 |
+
className={classNames(Button, small, green)}
|
| 42 |
+
onClick={() => {
|
| 43 |
+
modal.resolve('action')
|
| 44 |
+
modal.hide()
|
| 45 |
+
}}
|
| 46 |
+
>
|
| 47 |
+
{button}
|
| 48 |
+
</button>
|
| 49 |
+
</Toast.Action>
|
| 50 |
+
) : undefined}
|
| 51 |
+
</Toast.Root>
|
| 52 |
+
<Toast.Viewport className={ToastViewport} />
|
| 53 |
+
</Toast.Provider>
|
| 54 |
+
)
|
| 55 |
+
})
|
| 56 |
+
|
| 57 |
+
export function useToast() {
|
| 58 |
+
return useModal(MyToast)
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
declare type NiceModalArgs<T> = T extends
|
| 62 |
+
| keyof JSX.IntrinsicElements
|
| 63 |
+
| React.JSXElementConstructor<any>
|
| 64 |
+
? Omit<React.ComponentProps<T>, 'id'>
|
| 65 |
+
: Record<string, unknown>
|
| 66 |
+
|
| 67 |
+
export type ShowProps = NiceModalArgs<typeof MyToast>
|
| 68 |
+
|
| 69 |
+
export function GetToast(wait = 0) {
|
| 70 |
+
const show = debounce((props: ShowProps) => {
|
| 71 |
+
NiceModal.show(MyToast, props)
|
| 72 |
+
}, wait)
|
| 73 |
+
|
| 74 |
+
return {
|
| 75 |
+
show: (props: ShowProps) => {
|
| 76 |
+
show(props)
|
| 77 |
+
},
|
| 78 |
+
hide: () => {
|
| 79 |
+
show.cancel()
|
| 80 |
+
HideToast()
|
| 81 |
+
},
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
export async function ShowToast(props: ShowProps): Promise<string> {
|
| 86 |
+
return await NiceModal.show(MyToast, props)
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
export function HideToast() {
|
| 90 |
+
return NiceModal.hide(MyToast)
|
| 91 |
+
}
|
sd-webui-3d-open-pose-editor/src/components/Toast/styles.module.css
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import '@radix-ui/colors/blackA.css';
|
| 2 |
+
@import '@radix-ui/colors/green.css';
|
| 3 |
+
@import '@radix-ui/colors/mauve.css';
|
| 4 |
+
@import '@radix-ui/colors/slate.css';
|
| 5 |
+
@import '@radix-ui/colors/violet.css';
|
| 6 |
+
|
| 7 |
+
.ToastViewport {
|
| 8 |
+
font-family: var(--openpose-editor-font-family);
|
| 9 |
+
--viewport-padding: 25px;
|
| 10 |
+
position: fixed;
|
| 11 |
+
bottom: 0;
|
| 12 |
+
right: 0;
|
| 13 |
+
display: flex;
|
| 14 |
+
flex-direction: column;
|
| 15 |
+
padding: var(--viewport-padding);
|
| 16 |
+
gap: 10px;
|
| 17 |
+
width: 390px;
|
| 18 |
+
max-width: 100vw;
|
| 19 |
+
margin: 0;
|
| 20 |
+
list-style: none;
|
| 21 |
+
z-index: 2147483647;
|
| 22 |
+
outline: none;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.ToastRoot {
|
| 26 |
+
background-color: white;
|
| 27 |
+
border-radius: 6px;
|
| 28 |
+
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
|
| 29 |
+
hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
|
| 30 |
+
padding: 15px;
|
| 31 |
+
display: grid;
|
| 32 |
+
grid-template-areas: 'title action' 'description action';
|
| 33 |
+
grid-template-columns: auto max-content;
|
| 34 |
+
column-gap: 15px;
|
| 35 |
+
align-items: center;
|
| 36 |
+
}
|
| 37 |
+
.ToastRoot[data-state='open'] {
|
| 38 |
+
animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
| 39 |
+
}
|
| 40 |
+
.ToastRoot[data-state='closed'] {
|
| 41 |
+
animation: hide 100ms ease-in;
|
| 42 |
+
}
|
| 43 |
+
.ToastRoot[data-swipe='move'] {
|
| 44 |
+
transform: translateX(var(--radix-toast-swipe-move-x));
|
| 45 |
+
}
|
| 46 |
+
.ToastRoot[data-swipe='cancel'] {
|
| 47 |
+
transform: translateX(0);
|
| 48 |
+
transition: transform 200ms ease-out;
|
| 49 |
+
}
|
| 50 |
+
.ToastRoot[data-swipe='end'] {
|
| 51 |
+
animation: swipeOut 100ms ease-out;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
@keyframes hide {
|
| 55 |
+
from {
|
| 56 |
+
opacity: 1;
|
| 57 |
+
}
|
| 58 |
+
to {
|
| 59 |
+
opacity: 0;
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
@keyframes slideIn {
|
| 64 |
+
from {
|
| 65 |
+
transform: translateX(calc(100% + var(--viewport-padding)));
|
| 66 |
+
}
|
| 67 |
+
to {
|
| 68 |
+
transform: translateX(0);
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
@keyframes swipeOut {
|
| 73 |
+
from {
|
| 74 |
+
transform: translateX(var(--radix-toast-swipe-end-x));
|
| 75 |
+
}
|
| 76 |
+
to {
|
| 77 |
+
transform: translateX(calc(100% + var(--viewport-padding)));
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.ToastTitle {
|
| 82 |
+
grid-area: title;
|
| 83 |
+
margin-bottom: 5px;
|
| 84 |
+
font-weight: 500;
|
| 85 |
+
color: var(--slate12);
|
| 86 |
+
font-size: 15px;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.ToastDescription {
|
| 90 |
+
grid-area: description;
|
| 91 |
+
margin: 0;
|
| 92 |
+
color: var(--slate11);
|
| 93 |
+
font-size: 13px;
|
| 94 |
+
line-height: 1.3;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.ToastAction {
|
| 98 |
+
grid-area: action;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
.Button {
|
| 102 |
+
all: unset;
|
| 103 |
+
display: inline-flex;
|
| 104 |
+
align-items: center;
|
| 105 |
+
justify-content: center;
|
| 106 |
+
border-radius: 4px;
|
| 107 |
+
font-weight: 500;
|
| 108 |
+
}
|
| 109 |
+
.Button.small {
|
| 110 |
+
font-size: 12px;
|
| 111 |
+
padding: 0 10px;
|
| 112 |
+
line-height: 25px;
|
| 113 |
+
height: 25px;
|
| 114 |
+
}
|
| 115 |
+
.Button.large {
|
| 116 |
+
font-size: 15px;
|
| 117 |
+
padding: 0 15px;
|
| 118 |
+
line-height: 35px;
|
| 119 |
+
height: 35px;
|
| 120 |
+
}
|
| 121 |
+
.Button.violet {
|
| 122 |
+
background-color: white;
|
| 123 |
+
color: var(--violet11);
|
| 124 |
+
box-shadow: 0 2px 10px var(--blackA7);
|
| 125 |
+
}
|
| 126 |
+
.Button.violet:hover {
|
| 127 |
+
background-color: var(--mauve3);
|
| 128 |
+
}
|
| 129 |
+
.Button.violet:focus {
|
| 130 |
+
box-shadow: 0 0 0 2px black;
|
| 131 |
+
}
|
| 132 |
+
.Button.green {
|
| 133 |
+
background-color: var(--green2);
|
| 134 |
+
color: var(--green11);
|
| 135 |
+
box-shadow: inset 0 0 0 1px var(--green7);
|
| 136 |
+
}
|
| 137 |
+
.Button.green:hover {
|
| 138 |
+
box-shadow: inset 0 0 0 1px var(--green8);
|
| 139 |
+
}
|
| 140 |
+
.Button.green:focus {
|
| 141 |
+
box-shadow: 0 0 0 2px var(--green8);
|
| 142 |
+
}
|
sd-webui-3d-open-pose-editor/src/defines.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const OpenposeyKeypointsConst = [
|
| 2 |
+
'nose',
|
| 3 |
+
'neck',
|
| 4 |
+
'right_shoulder',
|
| 5 |
+
'right_elbow',
|
| 6 |
+
'right_wrist',
|
| 7 |
+
'left_shoulder',
|
| 8 |
+
'left_elbow',
|
| 9 |
+
'left_wrist',
|
| 10 |
+
'right_hip',
|
| 11 |
+
'right_knee',
|
| 12 |
+
'right_ankle',
|
| 13 |
+
'left_hip',
|
| 14 |
+
'left_knee',
|
| 15 |
+
'left_ankle',
|
| 16 |
+
'right_eye',
|
| 17 |
+
'left_eye',
|
| 18 |
+
'right_ear',
|
| 19 |
+
'left_ear',
|
| 20 |
+
] as const
|
| 21 |
+
|
| 22 |
+
export const OpenposeKeypoints = OpenposeyKeypointsConst as unknown as string[]
|
| 23 |
+
|
| 24 |
+
export const ConnectKeypoints = [
|
| 25 |
+
[1, 2],
|
| 26 |
+
[1, 5],
|
| 27 |
+
[2, 3],
|
| 28 |
+
[3, 4],
|
| 29 |
+
[5, 6],
|
| 30 |
+
[6, 7],
|
| 31 |
+
[1, 8],
|
| 32 |
+
[8, 9],
|
| 33 |
+
[9, 10],
|
| 34 |
+
[1, 11],
|
| 35 |
+
[11, 12],
|
| 36 |
+
[12, 13],
|
| 37 |
+
[0, 1],
|
| 38 |
+
[0, 14],
|
| 39 |
+
[14, 16],
|
| 40 |
+
[0, 15],
|
| 41 |
+
[15, 17],
|
| 42 |
+
] as const
|
| 43 |
+
|
| 44 |
+
export const ConnectColor = [
|
| 45 |
+
[255, 0, 0], // [1, 2], 0
|
| 46 |
+
[255, 85, 0], // [1, 5], 1
|
| 47 |
+
[255, 170, 0], // [2, 3], 2
|
| 48 |
+
[255, 255, 0], // [3, 4], 3
|
| 49 |
+
[170, 255, 0], // [5, 6], 4
|
| 50 |
+
[85, 255, 0], // [6, 7], 5
|
| 51 |
+
[0, 255, 0], // [1, 8], 6
|
| 52 |
+
[0, 255, 85], // [8, 9], 7
|
| 53 |
+
[0, 255, 170], // [9, 10], 8
|
| 54 |
+
[0, 255, 255], // [1, 11], 9
|
| 55 |
+
[0, 170, 255], // [11, 12], 10
|
| 56 |
+
[0, 85, 255], // [12, 13], 11
|
| 57 |
+
[0, 0, 255], // [0, 1], 12
|
| 58 |
+
[85, 0, 255], // [0, 14], 13
|
| 59 |
+
[170, 0, 255], // [14, 16], 14
|
| 60 |
+
[255, 0, 255], // [0, 15], 15
|
| 61 |
+
[255, 0, 170], // [15, 17], 16
|
| 62 |
+
[255, 0, 85], // 17
|
| 63 |
+
] as const
|
| 64 |
+
|
| 65 |
+
export function ToHexColor([r, g, b]: readonly [number, number, number]) {
|
| 66 |
+
return (r << 16) + (g << 8) + b
|
| 67 |
+
}
|
| 68 |
+
function SearchColor(start: number, end: number) {
|
| 69 |
+
const index = ConnectKeypoints.findIndex(
|
| 70 |
+
([s, e]) => s === start && e === end
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
if (typeof index !== 'undefined') {
|
| 74 |
+
const [r, g, b] = ConnectColor[index]
|
| 75 |
+
|
| 76 |
+
return (r << 16) + (g << 8) + b
|
| 77 |
+
}
|
| 78 |
+
return null
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
export function GetColorOfLinkByName(startName: string, endName: string) {
|
| 82 |
+
if (!startName || !endName) return null
|
| 83 |
+
|
| 84 |
+
const indexStart = OpenposeKeypoints.indexOf(startName)
|
| 85 |
+
const indexEnd = OpenposeKeypoints.indexOf(endName)
|
| 86 |
+
|
| 87 |
+
if (indexStart === -1 || indexEnd === -1) return null
|
| 88 |
+
|
| 89 |
+
if (indexStart > indexEnd) return SearchColor(indexEnd, indexStart)
|
| 90 |
+
else return SearchColor(indexStart, indexEnd)
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
export const BoneThickness = 1
|
| 94 |
+
|
| 95 |
+
export const PartIndexMappingOfPoseModel = {
|
| 96 |
+
Root: 0,
|
| 97 |
+
Hips: 1,
|
| 98 |
+
Spine: 2,
|
| 99 |
+
Spine1: 3,
|
| 100 |
+
Spine2: 4,
|
| 101 |
+
Chest: 5,
|
| 102 |
+
Neck: 6,
|
| 103 |
+
Head: 7,
|
| 104 |
+
Eye_R: 8,
|
| 105 |
+
Eye_L: 9,
|
| 106 |
+
Head_Null: 10, // maybe null
|
| 107 |
+
Shoulder_L: 11,
|
| 108 |
+
Arm_L: 12,
|
| 109 |
+
ForeArm_L: 13,
|
| 110 |
+
Hand_L: 14,
|
| 111 |
+
HandPinky1_L: 15,
|
| 112 |
+
HandPinky2_L: 16,
|
| 113 |
+
HandPinky3_L: 17,
|
| 114 |
+
HandRing1_L: 18,
|
| 115 |
+
HandRing2_L: 19,
|
| 116 |
+
HandRing3_L: 20,
|
| 117 |
+
HandMiddle1_L: 21,
|
| 118 |
+
HandMiddle2_L: 22,
|
| 119 |
+
HandMiddle3_L: 23,
|
| 120 |
+
HandIndex1_L: 24,
|
| 121 |
+
HandIndex2_L: 25,
|
| 122 |
+
HandIndex3_L: 26,
|
| 123 |
+
HandThumb1_L: 27,
|
| 124 |
+
HandThumb2_L: 28,
|
| 125 |
+
HandThumb3_L: 29,
|
| 126 |
+
Elbow_L: 30,
|
| 127 |
+
ForeArmTwist_L: 31,
|
| 128 |
+
ArmTwist_L: 32,
|
| 129 |
+
Shoulder_R: 33,
|
| 130 |
+
Arm_R: 34,
|
| 131 |
+
ForeArm_R: 35,
|
| 132 |
+
Hand_R: 36,
|
| 133 |
+
HandPinky1_R: 37,
|
| 134 |
+
HandPinky2_R: 38,
|
| 135 |
+
HandPinky3_R: 39,
|
| 136 |
+
HandRing1_R: 40,
|
| 137 |
+
HandRing2_R: 41,
|
| 138 |
+
HandRing3_R: 42,
|
| 139 |
+
HandMiddle1_R: 43,
|
| 140 |
+
HandMiddle2_R: 44,
|
| 141 |
+
HandMiddle3_R: 45,
|
| 142 |
+
HandIndex1_R: 46,
|
| 143 |
+
HandIndex2_R: 47,
|
| 144 |
+
HandIndex3_R: 48,
|
| 145 |
+
HandThumb1_R: 49,
|
| 146 |
+
HandThumb2_R: 50,
|
| 147 |
+
HandThumb3_R: 51,
|
| 148 |
+
Elbow_R: 52,
|
| 149 |
+
ForeArmTwist_R: 53,
|
| 150 |
+
ArmTwist_R: 54,
|
| 151 |
+
UpLeg_L: 55,
|
| 152 |
+
Leg_L: 56,
|
| 153 |
+
Knee_L: 57,
|
| 154 |
+
Foot_L: 58,
|
| 155 |
+
FootPinky1_L: 59,
|
| 156 |
+
FootRing_L: 60,
|
| 157 |
+
FootMiddle_L: 61,
|
| 158 |
+
FootIndex_L: 62,
|
| 159 |
+
FootThumb_L: 63,
|
| 160 |
+
UpLegTwist_L: 64,
|
| 161 |
+
ThighFront_L: 65,
|
| 162 |
+
UpLeg_R: 66,
|
| 163 |
+
Leg_R: 67,
|
| 164 |
+
Knee_R: 68,
|
| 165 |
+
Foot_R: 69,
|
| 166 |
+
FootPinky1_R: 70,
|
| 167 |
+
FootRing_R: 71,
|
| 168 |
+
FootMiddle_R: 72,
|
| 169 |
+
FootIndex_R: 73,
|
| 170 |
+
FootThumb_R: 74,
|
| 171 |
+
UpLegTwist_R: 75,
|
| 172 |
+
ThighFront_R: 76,
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
export const PartIndexMappingOfBlazePoseModel = {
|
| 176 |
+
nose: 0,
|
| 177 |
+
left_eye_inner: 1,
|
| 178 |
+
left_eye: 2,
|
| 179 |
+
left_eye_outer: 3,
|
| 180 |
+
right_eye_inner: 4,
|
| 181 |
+
right_eye: 5,
|
| 182 |
+
right_eye_outer: 6,
|
| 183 |
+
left_ear: 7,
|
| 184 |
+
right_ear: 8,
|
| 185 |
+
mouth_left: 9,
|
| 186 |
+
mouth_right: 10,
|
| 187 |
+
left_shoulder: 11,
|
| 188 |
+
right_shoulder: 12,
|
| 189 |
+
left_elbow: 13,
|
| 190 |
+
right_elbow: 14,
|
| 191 |
+
left_wrist: 15,
|
| 192 |
+
right_wrist: 16,
|
| 193 |
+
left_pinky: 17,
|
| 194 |
+
right_pinky: 18,
|
| 195 |
+
left_index: 19,
|
| 196 |
+
right_index: 20,
|
| 197 |
+
left_thumb: 21,
|
| 198 |
+
right_thumb: 22,
|
| 199 |
+
left_hip: 23,
|
| 200 |
+
right_hip: 24,
|
| 201 |
+
left_knee: 25,
|
| 202 |
+
right_knee: 26,
|
| 203 |
+
left_ankle: 27,
|
| 204 |
+
right_ankle: 28,
|
| 205 |
+
left_heel: 29,
|
| 206 |
+
right_heel: 30,
|
| 207 |
+
left_foot_index: 31,
|
| 208 |
+
right_foot_index: 32,
|
| 209 |
+
}
|
sd-webui-3d-open-pose-editor/src/editor.ts
ADDED
|
@@ -0,0 +1,1875 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as THREE from 'three'
|
| 2 |
+
import {
|
| 3 |
+
Bone,
|
| 4 |
+
Material,
|
| 5 |
+
Mesh,
|
| 6 |
+
MeshBasicMaterial,
|
| 7 |
+
MeshDepthMaterial,
|
| 8 |
+
MeshNormalMaterial,
|
| 9 |
+
MeshPhongMaterial,
|
| 10 |
+
Object3D,
|
| 11 |
+
Skeleton,
|
| 12 |
+
SkinnedMesh,
|
| 13 |
+
} from 'three'
|
| 14 |
+
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
| 15 |
+
import { TransformControls } from 'three/examples/jsm/controls/TransformControls'
|
| 16 |
+
|
| 17 |
+
// @ts-ignore
|
| 18 |
+
// import {
|
| 19 |
+
// CCDIKHelper,
|
| 20 |
+
// CCDIKSolver,
|
| 21 |
+
// IKS,
|
| 22 |
+
// } from 'three/examples/jsm/animate/CCDIKSolver'
|
| 23 |
+
import { CCDIKSolver } from './utils/CCDIKSolver'
|
| 24 |
+
import Stats from 'three/examples/jsm/libs/stats.module'
|
| 25 |
+
import {
|
| 26 |
+
BodyControlor,
|
| 27 |
+
BodyData,
|
| 28 |
+
CloneBody,
|
| 29 |
+
GetExtremityMesh,
|
| 30 |
+
IsBone,
|
| 31 |
+
IsExtremities,
|
| 32 |
+
IsFoot,
|
| 33 |
+
IsHand,
|
| 34 |
+
IsMask,
|
| 35 |
+
IsNeedSaveObject,
|
| 36 |
+
IsPickable,
|
| 37 |
+
IsSkeleton,
|
| 38 |
+
IsTarget,
|
| 39 |
+
IsTranslate,
|
| 40 |
+
} from './body'
|
| 41 |
+
|
| 42 |
+
import { downloadJson, uploadJson } from './utils/transfer'
|
| 43 |
+
|
| 44 |
+
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
|
| 45 |
+
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
|
| 46 |
+
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
|
| 47 |
+
|
| 48 |
+
import { LuminosityShader } from 'three/examples/jsm/shaders/LuminosityShader.js'
|
| 49 |
+
import { SobelOperatorShader } from 'three/examples/jsm/shaders/SobelOperatorShader.js'
|
| 50 |
+
import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils'
|
| 51 |
+
import { Oops } from './components/Oops'
|
| 52 |
+
import { getCurrentTime } from './utils/time'
|
| 53 |
+
import { sendToAll } from './hooks/useMessageDispatch'
|
| 54 |
+
|
| 55 |
+
type EditorEventHandler<T> = (args: T) => void
|
| 56 |
+
|
| 57 |
+
class EditorEventManager<T> {
|
| 58 |
+
private eventHandlers: EditorEventHandler<T>[] = []
|
| 59 |
+
|
| 60 |
+
AddEventListener(handler: EditorEventHandler<T>): void {
|
| 61 |
+
this.eventHandlers.push(handler)
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
RemoveEventListener(handler: EditorEventHandler<T>): void {
|
| 65 |
+
this.eventHandlers = this.eventHandlers.filter((h) => h !== handler)
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
TriggerEvent(args: T): void {
|
| 69 |
+
this.eventHandlers.forEach((h) => h(args))
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
interface CameraData {
|
| 74 |
+
position: ReturnType<THREE.Vector3['toArray']>
|
| 75 |
+
rotation: ReturnType<THREE.Euler['toArray']>
|
| 76 |
+
target: ReturnType<THREE.Vector3['toArray']>
|
| 77 |
+
near: number
|
| 78 |
+
far: number
|
| 79 |
+
zoom: number
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
interface TransformValue {
|
| 83 |
+
scale: Object3D['scale']
|
| 84 |
+
rotation: Object3D['rotation']
|
| 85 |
+
position: Object3D['position']
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
function GetTransformValue(obj: Object3D): TransformValue {
|
| 89 |
+
return {
|
| 90 |
+
scale: obj.scale.clone(),
|
| 91 |
+
rotation: obj.rotation.clone(),
|
| 92 |
+
position: obj.position.clone(),
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
export interface Command {
|
| 97 |
+
execute: () => void
|
| 98 |
+
undo: () => void
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
export interface ParentElement {
|
| 102 |
+
addEventListener(
|
| 103 |
+
type: 'keydown',
|
| 104 |
+
listener: (this: any, ev: KeyboardEvent) => any,
|
| 105 |
+
options?: boolean | AddEventListenerOptions | undefined
|
| 106 |
+
): void
|
| 107 |
+
addEventListener(
|
| 108 |
+
type: 'keyup',
|
| 109 |
+
listener: (this: any, ev: KeyboardEvent) => any,
|
| 110 |
+
options?: boolean | AddEventListenerOptions | undefined
|
| 111 |
+
): void
|
| 112 |
+
removeEventListener(
|
| 113 |
+
type: 'keydown',
|
| 114 |
+
listener: (this: Document, ev: KeyboardEvent) => any,
|
| 115 |
+
options?: boolean | EventListenerOptions | undefined
|
| 116 |
+
): void
|
| 117 |
+
removeEventListener(
|
| 118 |
+
type: 'keyup',
|
| 119 |
+
listener: (this: Document, ev: KeyboardEvent) => any,
|
| 120 |
+
options?: boolean | EventListenerOptions | undefined
|
| 121 |
+
): void
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
class PreviewRenderer {
|
| 125 |
+
scene: THREE.Scene
|
| 126 |
+
camera: THREE.PerspectiveCamera
|
| 127 |
+
canvas?: HTMLCanvasElement
|
| 128 |
+
renderer: THREE.WebGLRenderer
|
| 129 |
+
orbitControls: OrbitControls
|
| 130 |
+
|
| 131 |
+
constructor(setting: {
|
| 132 |
+
scene: THREE.Scene
|
| 133 |
+
camera: THREE.PerspectiveCamera
|
| 134 |
+
orbitControls: OrbitControls
|
| 135 |
+
|
| 136 |
+
canvas?: HTMLCanvasElement
|
| 137 |
+
renderer?: THREE.WebGLRenderer
|
| 138 |
+
}) {
|
| 139 |
+
this.scene = setting.scene
|
| 140 |
+
this.camera = setting.camera
|
| 141 |
+
this.canvas = setting.canvas
|
| 142 |
+
this.orbitControls = setting.orbitControls
|
| 143 |
+
if (setting.renderer) {
|
| 144 |
+
this.renderer = setting.renderer
|
| 145 |
+
} else {
|
| 146 |
+
this.renderer = new THREE.WebGLRenderer({
|
| 147 |
+
antialias: true,
|
| 148 |
+
canvas: setting.canvas,
|
| 149 |
+
// logarithmicDepthBuffer: true
|
| 150 |
+
})
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
renderBySize(
|
| 155 |
+
outputWidth: number,
|
| 156 |
+
outputHeight: number,
|
| 157 |
+
render: (outputWidth: number, outputHeight: number) => void
|
| 158 |
+
) {
|
| 159 |
+
const save = {
|
| 160 |
+
aspect: this.camera.aspect,
|
| 161 |
+
}
|
| 162 |
+
this.camera.aspect = outputWidth / outputHeight
|
| 163 |
+
this.camera.updateProjectionMatrix()
|
| 164 |
+
this.renderer.setSize(outputWidth, outputHeight, true)
|
| 165 |
+
|
| 166 |
+
render(outputWidth, outputHeight)
|
| 167 |
+
|
| 168 |
+
this.camera.aspect = save.aspect
|
| 169 |
+
this.camera.updateProjectionMatrix()
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
GetCameraData() {
|
| 173 |
+
const result = {
|
| 174 |
+
position: this.camera.position.toArray(),
|
| 175 |
+
rotation: this.camera.rotation.toArray(),
|
| 176 |
+
target: this.orbitControls.target.toArray(),
|
| 177 |
+
near: this.camera.near,
|
| 178 |
+
far: this.camera.far,
|
| 179 |
+
zoom: this.camera.zoom,
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
return result
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
RestoreCamera(data: CameraData, updateOrbitControl = true) {
|
| 186 |
+
this.camera.position.fromArray(data.position)
|
| 187 |
+
this.camera.rotation.fromArray(data.rotation as any)
|
| 188 |
+
this.camera.near = data.near
|
| 189 |
+
this.camera.far = data.far
|
| 190 |
+
this.camera.zoom = data.zoom
|
| 191 |
+
this.camera.updateProjectionMatrix()
|
| 192 |
+
|
| 193 |
+
if (data.target) this.orbitControls.target.fromArray(data.target)
|
| 194 |
+
if (updateOrbitControl) this.orbitControls.update() // fix position change
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
changeView(cameraDataOfView?: CameraData) {
|
| 198 |
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
| 199 |
+
if (!cameraDataOfView) return () => {}
|
| 200 |
+
|
| 201 |
+
const old = this.GetCameraData()
|
| 202 |
+
this.RestoreCamera(cameraDataOfView, false)
|
| 203 |
+
return () => {
|
| 204 |
+
this.RestoreCamera(old)
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
render(
|
| 209 |
+
outputWidth: number,
|
| 210 |
+
outputHeight: number,
|
| 211 |
+
cameraDataOfView?: CameraData,
|
| 212 |
+
custom?: (outputWidth: number, outputHeight: number) => void
|
| 213 |
+
) {
|
| 214 |
+
const render = () => {
|
| 215 |
+
this.renderer.render(this.scene, this.camera)
|
| 216 |
+
}
|
| 217 |
+
const restoreView = this.changeView(cameraDataOfView)
|
| 218 |
+
this.renderBySize(outputWidth, outputHeight, custom ?? render)
|
| 219 |
+
restoreView()
|
| 220 |
+
}
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
export class BodyEditor {
|
| 224 |
+
renderer: THREE.WebGLRenderer
|
| 225 |
+
outputRenderer: THREE.WebGLRenderer
|
| 226 |
+
previewRenderer: PreviewRenderer
|
| 227 |
+
scene: THREE.Scene
|
| 228 |
+
gridHelper: THREE.GridHelper
|
| 229 |
+
axesHelper: THREE.AxesHelper
|
| 230 |
+
camera: THREE.PerspectiveCamera
|
| 231 |
+
orbitControls: OrbitControls
|
| 232 |
+
transformControl: TransformControls
|
| 233 |
+
|
| 234 |
+
dlight: THREE.DirectionalLight
|
| 235 |
+
alight: THREE.AmbientLight
|
| 236 |
+
raycaster = new THREE.Raycaster()
|
| 237 |
+
IsClick = false
|
| 238 |
+
stats: Stats | undefined
|
| 239 |
+
|
| 240 |
+
// ikSolver?: CCDIKSolver
|
| 241 |
+
composer?: EffectComposer
|
| 242 |
+
finalComposer?: EffectComposer
|
| 243 |
+
effectSobel?: ShaderPass
|
| 244 |
+
enableComposer = false
|
| 245 |
+
enablePreview = true
|
| 246 |
+
enableHelper = true
|
| 247 |
+
|
| 248 |
+
paused = false
|
| 249 |
+
|
| 250 |
+
parentElem: ParentElement
|
| 251 |
+
|
| 252 |
+
clearColor = 0xaaaaaa
|
| 253 |
+
constructor({
|
| 254 |
+
canvas,
|
| 255 |
+
previewCanvas,
|
| 256 |
+
parentElem = document,
|
| 257 |
+
statsElem,
|
| 258 |
+
}: {
|
| 259 |
+
canvas: HTMLCanvasElement
|
| 260 |
+
previewCanvas: HTMLCanvasElement
|
| 261 |
+
parentElem?: ParentElement
|
| 262 |
+
statsElem?: Element
|
| 263 |
+
}) {
|
| 264 |
+
this.parentElem = parentElem
|
| 265 |
+
this.renderer = new THREE.WebGLRenderer({
|
| 266 |
+
canvas,
|
| 267 |
+
antialias: true,
|
| 268 |
+
// logarithmicDepthBuffer: true
|
| 269 |
+
})
|
| 270 |
+
this.outputRenderer = new THREE.WebGLRenderer({
|
| 271 |
+
antialias: true,
|
| 272 |
+
// logarithmicDepthBuffer: true
|
| 273 |
+
})
|
| 274 |
+
this.outputRenderer.domElement.style.display = 'none'
|
| 275 |
+
document.body.appendChild(this.outputRenderer.domElement)
|
| 276 |
+
|
| 277 |
+
this.renderer.setClearColor(this.clearColor, 0.0)
|
| 278 |
+
this.scene = new THREE.Scene()
|
| 279 |
+
|
| 280 |
+
this.gridHelper = new THREE.GridHelper(8000, 200)
|
| 281 |
+
this.axesHelper = new THREE.AxesHelper(1000)
|
| 282 |
+
this.scene.add(this.gridHelper)
|
| 283 |
+
this.scene.add(this.axesHelper)
|
| 284 |
+
|
| 285 |
+
const aspect = window.innerWidth / window.innerHeight
|
| 286 |
+
|
| 287 |
+
this.camera = new THREE.PerspectiveCamera(60, aspect, 0.1, 10000)
|
| 288 |
+
|
| 289 |
+
this.camera.position.set(0, 100, 200)
|
| 290 |
+
this.camera.lookAt(0, 100, 0)
|
| 291 |
+
// this.camera.near = 130
|
| 292 |
+
// this.camera.far = 600
|
| 293 |
+
this.camera.updateProjectionMatrix()
|
| 294 |
+
|
| 295 |
+
this.orbitControls = new OrbitControls(
|
| 296 |
+
this.camera,
|
| 297 |
+
this.renderer.domElement
|
| 298 |
+
)
|
| 299 |
+
this.orbitControls.target = new THREE.Vector3(0, 100, 0)
|
| 300 |
+
this.orbitControls.update()
|
| 301 |
+
|
| 302 |
+
this.transformControl = new TransformControls(
|
| 303 |
+
this.camera,
|
| 304 |
+
this.renderer.domElement
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
this.transformControl.setMode('rotate') //旋转
|
| 308 |
+
this.transformControl.setSize(0.4)
|
| 309 |
+
this.transformControl.setSpace('local')
|
| 310 |
+
this.registerTranformControlEvent()
|
| 311 |
+
this.scene.add(this.transformControl)
|
| 312 |
+
|
| 313 |
+
this.previewRenderer = new PreviewRenderer({
|
| 314 |
+
scene: this.scene,
|
| 315 |
+
camera: this.camera,
|
| 316 |
+
orbitControls: this.orbitControls,
|
| 317 |
+
canvas: previewCanvas,
|
| 318 |
+
})
|
| 319 |
+
|
| 320 |
+
// Light
|
| 321 |
+
this.dlight = new THREE.DirectionalLight(0xffffff, 1.0)
|
| 322 |
+
this.dlight.position.set(0, 160, 1000)
|
| 323 |
+
this.scene.add(this.dlight)
|
| 324 |
+
this.alight = new THREE.AmbientLight(0xffffff, 0.5)
|
| 325 |
+
this.scene.add(this.alight)
|
| 326 |
+
|
| 327 |
+
this.onMouseDown = this.onMouseDown.bind(this)
|
| 328 |
+
this.onMouseMove = this.onMouseMove.bind(this)
|
| 329 |
+
this.onMouseUp = this.onMouseUp.bind(this)
|
| 330 |
+
this.handleResize = this.handleResize.bind(this)
|
| 331 |
+
this.handleKeyDown = this.handleKeyDown.bind(this)
|
| 332 |
+
this.handleKeyUp = this.handleKeyUp.bind(this)
|
| 333 |
+
|
| 334 |
+
this.addEvent()
|
| 335 |
+
|
| 336 |
+
this.initEdgeComposer()
|
| 337 |
+
|
| 338 |
+
// // Create a render target with depth texture
|
| 339 |
+
// this.setupRenderTarget();
|
| 340 |
+
|
| 341 |
+
// // Setup post-processing step
|
| 342 |
+
// this.setupPost();
|
| 343 |
+
|
| 344 |
+
if (statsElem) {
|
| 345 |
+
this.stats = Stats()
|
| 346 |
+
statsElem.appendChild(this.stats.dom)
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
this.animate = this.animate.bind(this)
|
| 350 |
+
this.animate()
|
| 351 |
+
this.handleResize()
|
| 352 |
+
this.AutoSaveScene()
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
disponse() {
|
| 356 |
+
this.pause()
|
| 357 |
+
this.removeEvent()
|
| 358 |
+
this.renderer.dispose()
|
| 359 |
+
this.outputRenderer.dispose()
|
| 360 |
+
|
| 361 |
+
console.log('BodyEditor disponse')
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
commandHistory: Command[] = []
|
| 365 |
+
historyIndex = -1
|
| 366 |
+
pushCommand(cmd: Command) {
|
| 367 |
+
console.log('pushCommand')
|
| 368 |
+
if (this.historyIndex != this.commandHistory.length - 1)
|
| 369 |
+
this.commandHistory = this.commandHistory.slice(
|
| 370 |
+
0,
|
| 371 |
+
this.historyIndex + 1
|
| 372 |
+
)
|
| 373 |
+
this.commandHistory.push(cmd)
|
| 374 |
+
this.historyIndex = this.commandHistory.length - 1
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
CreateTransformCommand(obj: Object3D, _old: TransformValue): Command {
|
| 378 |
+
const oldValue = _old
|
| 379 |
+
const newValue = GetTransformValue(obj)
|
| 380 |
+
const controlor = new BodyControlor(this.getBodyByPart(obj)!)
|
| 381 |
+
return {
|
| 382 |
+
execute: () => {
|
| 383 |
+
obj.position.copy(newValue.position)
|
| 384 |
+
obj.rotation.copy(newValue.rotation)
|
| 385 |
+
obj.scale.copy(newValue.scale)
|
| 386 |
+
controlor.Update()
|
| 387 |
+
},
|
| 388 |
+
undo: () => {
|
| 389 |
+
obj.position.copy(oldValue.position)
|
| 390 |
+
obj.rotation.copy(oldValue.rotation)
|
| 391 |
+
obj.scale.copy(oldValue.scale)
|
| 392 |
+
controlor.Update()
|
| 393 |
+
},
|
| 394 |
+
}
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
CreateAllTransformCommand(obj: Object3D, _old: BodyData): Command {
|
| 398 |
+
const oldValue = _old
|
| 399 |
+
const body = this.getBodyByPart(obj)!
|
| 400 |
+
const controlor = new BodyControlor(body)
|
| 401 |
+
const newValue = controlor.GetBodyData()
|
| 402 |
+
|
| 403 |
+
return {
|
| 404 |
+
execute: () => {
|
| 405 |
+
controlor.RestoreBody(newValue)
|
| 406 |
+
controlor.Update()
|
| 407 |
+
},
|
| 408 |
+
undo: () => {
|
| 409 |
+
controlor.RestoreBody(oldValue)
|
| 410 |
+
controlor.Update()
|
| 411 |
+
},
|
| 412 |
+
}
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
CreateAddBodyCommand(obj: Object3D): Command {
|
| 416 |
+
return {
|
| 417 |
+
execute: () => {
|
| 418 |
+
this.scene.add(obj)
|
| 419 |
+
},
|
| 420 |
+
undo: () => {
|
| 421 |
+
obj.removeFromParent()
|
| 422 |
+
this.DetachTransfromControl()
|
| 423 |
+
},
|
| 424 |
+
}
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
CreateRemoveBodyCommand(obj: Object3D): Command {
|
| 428 |
+
return {
|
| 429 |
+
execute: () => {
|
| 430 |
+
obj.removeFromParent()
|
| 431 |
+
this.DetachTransfromControl()
|
| 432 |
+
},
|
| 433 |
+
undo: () => {
|
| 434 |
+
this.scene.add(obj)
|
| 435 |
+
},
|
| 436 |
+
}
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
Undo() {
|
| 440 |
+
console.log('Undo', this.historyIndex)
|
| 441 |
+
|
| 442 |
+
if (this.historyIndex >= 0) {
|
| 443 |
+
const cmd = this.commandHistory[this.historyIndex]
|
| 444 |
+
cmd.undo()
|
| 445 |
+
this.historyIndex--
|
| 446 |
+
}
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
Redo() {
|
| 450 |
+
console.log('Redo', this.historyIndex)
|
| 451 |
+
|
| 452 |
+
if (this.historyIndex < this.commandHistory.length - 1) {
|
| 453 |
+
const cmd = this.commandHistory[this.historyIndex + 1]
|
| 454 |
+
cmd.execute()
|
| 455 |
+
this.historyIndex++
|
| 456 |
+
}
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
handleKeyDown(e: KeyboardEvent) {
|
| 460 |
+
if (this.paused) {
|
| 461 |
+
return
|
| 462 |
+
}
|
| 463 |
+
if (e.code === 'KeyZ' && (e.ctrlKey || e.metaKey) && e.shiftKey) {
|
| 464 |
+
this.Redo()
|
| 465 |
+
} else if (e.code === 'KeyY' && (e.ctrlKey || e.metaKey)) {
|
| 466 |
+
this.Redo()
|
| 467 |
+
// prevent brower refresh
|
| 468 |
+
e.preventDefault()
|
| 469 |
+
} else if (e.code === 'KeyZ' && (e.ctrlKey || e.metaKey)) {
|
| 470 |
+
this.Undo()
|
| 471 |
+
} else if (e.code === 'KeyD' && e.shiftKey) {
|
| 472 |
+
this.CopySelectedBody()
|
| 473 |
+
} else if (e.key === 'Delete') {
|
| 474 |
+
this.RemoveBody()
|
| 475 |
+
} else if (e.code === 'KeyX') {
|
| 476 |
+
this.MoveMode = true
|
| 477 |
+
}
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
handleKeyUp(e: KeyboardEvent) {
|
| 481 |
+
if (this.paused) {
|
| 482 |
+
return
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
if (e.code === 'KeyX') {
|
| 486 |
+
this.MoveMode = false
|
| 487 |
+
}
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
registerTranformControlEvent() {
|
| 491 |
+
let oldTransformValue: TransformValue = {
|
| 492 |
+
scale: new THREE.Vector3(),
|
| 493 |
+
rotation: new THREE.Euler(),
|
| 494 |
+
position: new THREE.Vector3(),
|
| 495 |
+
}
|
| 496 |
+
let oldBodyData: BodyData = {} as BodyData
|
| 497 |
+
this.transformControl.addEventListener('change', () => {
|
| 498 |
+
const body = this.getSelectedBody()
|
| 499 |
+
|
| 500 |
+
if (body) {
|
| 501 |
+
new BodyControlor(body).UpdateBones()
|
| 502 |
+
}
|
| 503 |
+
// console.log('change')
|
| 504 |
+
// this.renderer.render(this.scene, this.camera)
|
| 505 |
+
})
|
| 506 |
+
|
| 507 |
+
this.transformControl.addEventListener('objectChange', () => {
|
| 508 |
+
// console.log('objectChange')
|
| 509 |
+
// this.renderer.render(this.scene, this.camera)
|
| 510 |
+
})
|
| 511 |
+
|
| 512 |
+
this.transformControl.addEventListener('mouseDown', () => {
|
| 513 |
+
const part = this.getSelectedPart()
|
| 514 |
+
if (part) {
|
| 515 |
+
oldTransformValue = GetTransformValue(part)
|
| 516 |
+
const body = this.getBodyByPart(part)!
|
| 517 |
+
oldBodyData = new BodyControlor(body).GetBodyData()
|
| 518 |
+
}
|
| 519 |
+
this.orbitControls.enabled = false
|
| 520 |
+
})
|
| 521 |
+
this.transformControl.addEventListener('mouseUp', () => {
|
| 522 |
+
const part = this.getSelectedPart()
|
| 523 |
+
if (part) {
|
| 524 |
+
if (IsTarget(part.name))
|
| 525 |
+
this.pushCommand(
|
| 526 |
+
this.CreateAllTransformCommand(part, oldBodyData)
|
| 527 |
+
)
|
| 528 |
+
else
|
| 529 |
+
this.pushCommand(
|
| 530 |
+
this.CreateTransformCommand(part, oldTransformValue)
|
| 531 |
+
)
|
| 532 |
+
}
|
| 533 |
+
this.orbitControls.enabled = true
|
| 534 |
+
|
| 535 |
+
this.saveSelectedBodyControlor?.Update()
|
| 536 |
+
})
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
ikSolver?: CCDIKSolver
|
| 540 |
+
saveSelectedBodyControlor?: BodyControlor
|
| 541 |
+
|
| 542 |
+
updateSelectedBodyIKSolver() {
|
| 543 |
+
const body = this.getSelectedBody() ?? undefined
|
| 544 |
+
|
| 545 |
+
if (body !== this.saveSelectedBodyControlor) {
|
| 546 |
+
this.saveSelectedBodyControlor = body
|
| 547 |
+
? new BodyControlor(body!)
|
| 548 |
+
: undefined
|
| 549 |
+
this.ikSolver = body
|
| 550 |
+
? this.saveSelectedBodyControlor?.GetIKSolver()
|
| 551 |
+
: undefined
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
if (IsTranslate(this.getSelectedPart()?.name ?? ''))
|
| 555 |
+
this.ikSolver?.update()
|
| 556 |
+
else this.saveSelectedBodyControlor?.ResetAllTargetsPosition()
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
+
render(width: number = this.Width, height: number = this.Height) {
|
| 560 |
+
this.updateSelectedBodyIKSolver()
|
| 561 |
+
|
| 562 |
+
this.renderer.setViewport(0, 0, width, height)
|
| 563 |
+
this.renderer.setScissor(0, 0, width, height)
|
| 564 |
+
this.renderer.setScissorTest(true)
|
| 565 |
+
|
| 566 |
+
this.renderer.render(this.scene, this.camera)
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
autoSize = true
|
| 570 |
+
outputWidth = 0
|
| 571 |
+
outputHeight = 0
|
| 572 |
+
get OutputWidth() {
|
| 573 |
+
return this.autoSize
|
| 574 |
+
? this.Width
|
| 575 |
+
: this.outputWidth === 0
|
| 576 |
+
? this.Height
|
| 577 |
+
: this.outputWidth
|
| 578 |
+
}
|
| 579 |
+
set OutputWidth(value: number) {
|
| 580 |
+
this.autoSize = false
|
| 581 |
+
this.outputWidth = value
|
| 582 |
+
}
|
| 583 |
+
get OutputHeight() {
|
| 584 |
+
return this.autoSize
|
| 585 |
+
? this.Height
|
| 586 |
+
: this.outputHeight === 0
|
| 587 |
+
? this.Height
|
| 588 |
+
: this.outputHeight
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
set OutputHeight(value: number) {
|
| 592 |
+
this.autoSize = false
|
| 593 |
+
this.outputHeight = value
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
+
renderPreview() {
|
| 597 |
+
const outputWidth = this.OutputWidth
|
| 598 |
+
const outputHeight = this.OutputHeight
|
| 599 |
+
|
| 600 |
+
const outputAspect = outputWidth / outputHeight
|
| 601 |
+
const maxOutoutAspect = 2
|
| 602 |
+
const [left, bottom, width, height] =
|
| 603 |
+
outputAspect > maxOutoutAspect
|
| 604 |
+
? [
|
| 605 |
+
this.Width - 50 - 150 * maxOutoutAspect,
|
| 606 |
+
220,
|
| 607 |
+
150 * maxOutoutAspect,
|
| 608 |
+
(150 * maxOutoutAspect * outputHeight) / outputWidth,
|
| 609 |
+
]
|
| 610 |
+
: [
|
| 611 |
+
this.Width - 50 - (150 * outputWidth) / outputHeight,
|
| 612 |
+
220,
|
| 613 |
+
(150 * outputWidth) / outputHeight,
|
| 614 |
+
150,
|
| 615 |
+
]
|
| 616 |
+
const save = {
|
| 617 |
+
viewport: new THREE.Vector4(),
|
| 618 |
+
scissor: new THREE.Vector4(),
|
| 619 |
+
scissorTest: this.renderer.getScissorTest(),
|
| 620 |
+
aspect: this.camera.aspect,
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
this.renderer.getViewport(save.viewport)
|
| 624 |
+
this.renderer.getScissor(save.viewport)
|
| 625 |
+
|
| 626 |
+
this.renderer.setViewport(left, bottom, width, height)
|
| 627 |
+
this.renderer.setScissor(left, bottom, width, height)
|
| 628 |
+
this.renderer.setScissorTest(true)
|
| 629 |
+
this.camera.aspect = width / height
|
| 630 |
+
this.camera.updateProjectionMatrix()
|
| 631 |
+
|
| 632 |
+
const restoreView = this.changeView()
|
| 633 |
+
this.renderer.render(this.scene, this.camera)
|
| 634 |
+
restoreView()
|
| 635 |
+
// restore
|
| 636 |
+
this.renderer.setViewport(save.viewport)
|
| 637 |
+
this.renderer.setScissor(save.scissor)
|
| 638 |
+
this.renderer.setScissorTest(save.scissorTest)
|
| 639 |
+
this.camera.aspect = save.aspect
|
| 640 |
+
this.camera.updateProjectionMatrix()
|
| 641 |
+
}
|
| 642 |
+
|
| 643 |
+
renderOutputBySize(
|
| 644 |
+
outputWidth: number,
|
| 645 |
+
outputHeight: number,
|
| 646 |
+
render: (outputWidth: number, outputHeight: number) => void
|
| 647 |
+
) {
|
| 648 |
+
const save = {
|
| 649 |
+
aspect: this.camera.aspect,
|
| 650 |
+
}
|
| 651 |
+
this.camera.aspect = outputWidth / outputHeight
|
| 652 |
+
this.camera.updateProjectionMatrix()
|
| 653 |
+
this.outputRenderer.setSize(outputWidth, outputHeight, true)
|
| 654 |
+
|
| 655 |
+
render(outputWidth, outputHeight)
|
| 656 |
+
|
| 657 |
+
this.camera.aspect = save.aspect
|
| 658 |
+
this.camera.updateProjectionMatrix()
|
| 659 |
+
}
|
| 660 |
+
|
| 661 |
+
renderOutput(
|
| 662 |
+
scale = 1,
|
| 663 |
+
custom?: (outputWidth: number, outputHeight: number) => void
|
| 664 |
+
) {
|
| 665 |
+
const outputWidth = this.OutputWidth * scale
|
| 666 |
+
const outputHeight = this.OutputHeight * scale
|
| 667 |
+
|
| 668 |
+
const render = () => {
|
| 669 |
+
this.outputRenderer.render(this.scene, this.camera)
|
| 670 |
+
}
|
| 671 |
+
this.renderOutputBySize(outputWidth, outputHeight, custom ?? render)
|
| 672 |
+
}
|
| 673 |
+
getOutputPNG() {
|
| 674 |
+
return this.outputRenderer.domElement.toDataURL('image/png')
|
| 675 |
+
}
|
| 676 |
+
animate() {
|
| 677 |
+
if (this.paused) {
|
| 678 |
+
return
|
| 679 |
+
}
|
| 680 |
+
requestAnimationFrame(this.animate)
|
| 681 |
+
this.handleResize()
|
| 682 |
+
this.render()
|
| 683 |
+
this.outputPreview()
|
| 684 |
+
this.stats?.update()
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
outputPreview() {
|
| 688 |
+
if (this.enablePreview) this.CapturePreview()
|
| 689 |
+
this.PreviewEventManager.TriggerEvent(this.enablePreview)
|
| 690 |
+
}
|
| 691 |
+
pause() {
|
| 692 |
+
this.paused = true
|
| 693 |
+
}
|
| 694 |
+
|
| 695 |
+
resume() {
|
| 696 |
+
this.paused = false
|
| 697 |
+
this.animate()
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
getAncestors(o: Object3D) {
|
| 701 |
+
const ancestors: Object3D[] = []
|
| 702 |
+
o.traverseAncestors((ancestor) => ancestors.push(ancestor))
|
| 703 |
+
return ancestors
|
| 704 |
+
}
|
| 705 |
+
getBodyByPart(o: Object3D) {
|
| 706 |
+
if (o?.name === 'torso') return o
|
| 707 |
+
|
| 708 |
+
const body =
|
| 709 |
+
this.getAncestors(o).find((o) => o?.name === 'torso') ?? null
|
| 710 |
+
return body
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
SelectEventManager = new EditorEventManager<BodyControlor>()
|
| 714 |
+
UnselectEventManager = new EditorEventManager<void>()
|
| 715 |
+
ContextMenuEventManager = new EditorEventManager<{
|
| 716 |
+
mouseX: number
|
| 717 |
+
mouseY: number
|
| 718 |
+
}>()
|
| 719 |
+
PreviewEventManager = new EditorEventManager<boolean>()
|
| 720 |
+
LockViewEventManager = new EditorEventManager<boolean>()
|
| 721 |
+
|
| 722 |
+
triggerSelectEvent(body: Object3D) {
|
| 723 |
+
const c = new BodyControlor(body)
|
| 724 |
+
this.SelectEventManager.TriggerEvent(c)
|
| 725 |
+
this.UpdateBones()
|
| 726 |
+
}
|
| 727 |
+
triggerUnselectEvent() {
|
| 728 |
+
this.UnselectEventManager.TriggerEvent()
|
| 729 |
+
this.UpdateBones()
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
addEvent() {
|
| 733 |
+
this.renderer.domElement.addEventListener(
|
| 734 |
+
'mousedown',
|
| 735 |
+
this.onMouseDown,
|
| 736 |
+
false
|
| 737 |
+
)
|
| 738 |
+
this.renderer.domElement.addEventListener(
|
| 739 |
+
'mousemove',
|
| 740 |
+
this.onMouseMove,
|
| 741 |
+
false
|
| 742 |
+
)
|
| 743 |
+
this.renderer.domElement.addEventListener(
|
| 744 |
+
'mouseup',
|
| 745 |
+
this.onMouseUp,
|
| 746 |
+
false
|
| 747 |
+
)
|
| 748 |
+
|
| 749 |
+
this.renderer.domElement.addEventListener('resize', this.handleResize)
|
| 750 |
+
|
| 751 |
+
this.parentElem.addEventListener('keydown', this.handleKeyDown)
|
| 752 |
+
this.parentElem.addEventListener('keyup', this.handleKeyUp)
|
| 753 |
+
}
|
| 754 |
+
|
| 755 |
+
removeEvent() {
|
| 756 |
+
this.renderer.domElement.removeEventListener(
|
| 757 |
+
'mousedown',
|
| 758 |
+
this.onMouseDown,
|
| 759 |
+
false
|
| 760 |
+
)
|
| 761 |
+
this.renderer.domElement.removeEventListener(
|
| 762 |
+
'mousemove',
|
| 763 |
+
this.onMouseMove,
|
| 764 |
+
false
|
| 765 |
+
)
|
| 766 |
+
this.renderer.domElement.removeEventListener(
|
| 767 |
+
'mouseup',
|
| 768 |
+
this.onMouseUp,
|
| 769 |
+
false
|
| 770 |
+
)
|
| 771 |
+
|
| 772 |
+
this.renderer.domElement.removeEventListener(
|
| 773 |
+
'resize',
|
| 774 |
+
this.handleResize
|
| 775 |
+
)
|
| 776 |
+
|
| 777 |
+
this.parentElem.removeEventListener('keydown', this.handleKeyDown)
|
| 778 |
+
this.parentElem.removeEventListener('keyup', this.handleKeyUp)
|
| 779 |
+
}
|
| 780 |
+
|
| 781 |
+
onMouseDown(event: MouseEvent) {
|
| 782 |
+
event.preventDefault()
|
| 783 |
+
this.IsClick = true
|
| 784 |
+
}
|
| 785 |
+
onMouseMove(event: MouseEvent) {
|
| 786 |
+
// some devices still send movemove event, filter it.
|
| 787 |
+
if (event.movementX == 0 && event.movementY == 0) return
|
| 788 |
+
this.IsClick = false
|
| 789 |
+
}
|
| 790 |
+
|
| 791 |
+
onMouseUp(event: MouseEvent) {
|
| 792 |
+
const x = event.offsetX - this.renderer.domElement.offsetLeft
|
| 793 |
+
const y = event.offsetY - this.renderer.domElement.offsetTop
|
| 794 |
+
this.raycaster.setFromCamera(
|
| 795 |
+
{
|
| 796 |
+
x: (x / this.renderer.domElement.clientWidth) * 2 - 1,
|
| 797 |
+
y: -(y / this.renderer.domElement.clientHeight) * 2 + 1,
|
| 798 |
+
},
|
| 799 |
+
this.camera
|
| 800 |
+
)
|
| 801 |
+
const intersects: THREE.Intersection[] =
|
| 802 |
+
this.raycaster.intersectObjects(this.GetBodies(), true)
|
| 803 |
+
// If read_point is found, choose it first
|
| 804 |
+
const point = intersects.find((o) => o.object.name === 'red_point')
|
| 805 |
+
const intersectedObject: THREE.Object3D | null = point
|
| 806 |
+
? point.object
|
| 807 |
+
: intersects.length > 0
|
| 808 |
+
? intersects[0].object
|
| 809 |
+
: null
|
| 810 |
+
const name = intersectedObject ? intersectedObject.name : ''
|
| 811 |
+
let obj: Object3D | null = intersectedObject
|
| 812 |
+
|
| 813 |
+
console.log(obj?.name)
|
| 814 |
+
|
| 815 |
+
if (this.IsClick) {
|
| 816 |
+
if (event.button === 2 || event.which === 3) {
|
| 817 |
+
console.log('Right mouse button released')
|
| 818 |
+
this.ContextMenuEventManager.TriggerEvent({
|
| 819 |
+
mouseX: x,
|
| 820 |
+
mouseY: y,
|
| 821 |
+
})
|
| 822 |
+
return
|
| 823 |
+
}
|
| 824 |
+
|
| 825 |
+
if (!obj) {
|
| 826 |
+
this.DetachTransfromControl()
|
| 827 |
+
this.triggerUnselectEvent()
|
| 828 |
+
return
|
| 829 |
+
}
|
| 830 |
+
|
| 831 |
+
if (this.MoveMode) {
|
| 832 |
+
const isOk = IsPickable(name, this.FreeMode)
|
| 833 |
+
|
| 834 |
+
if (!isOk) {
|
| 835 |
+
obj =
|
| 836 |
+
this.getAncestors(obj).find((o) =>
|
| 837 |
+
IsPickable(o.name, this.FreeMode)
|
| 838 |
+
) ?? null
|
| 839 |
+
}
|
| 840 |
+
|
| 841 |
+
if (obj) {
|
| 842 |
+
if (IsTranslate(obj.name, this.FreeMode) === false)
|
| 843 |
+
obj = this.getBodyByPart(obj)
|
| 844 |
+
}
|
| 845 |
+
|
| 846 |
+
if (obj) {
|
| 847 |
+
console.log(obj.name)
|
| 848 |
+
this.transformControl.setMode('translate')
|
| 849 |
+
this.transformControl.setSpace('world')
|
| 850 |
+
this.transformControl.attach(obj)
|
| 851 |
+
const body = this.getBodyByPart(obj)
|
| 852 |
+
if (body) this.triggerSelectEvent(body)
|
| 853 |
+
}
|
| 854 |
+
} else {
|
| 855 |
+
const isOk = IsPickable(name)
|
| 856 |
+
|
| 857 |
+
if (!isOk) {
|
| 858 |
+
obj =
|
| 859 |
+
this.getAncestors(obj).find((o) =>
|
| 860 |
+
IsPickable(o.name)
|
| 861 |
+
) ?? null
|
| 862 |
+
}
|
| 863 |
+
|
| 864 |
+
if (obj) {
|
| 865 |
+
console.log(obj.name)
|
| 866 |
+
|
| 867 |
+
if (IsTranslate(obj.name)) {
|
| 868 |
+
this.transformControl.setMode('translate')
|
| 869 |
+
this.transformControl.setSpace('world')
|
| 870 |
+
} else {
|
| 871 |
+
this.transformControl.setMode('rotate')
|
| 872 |
+
this.transformControl.setSpace('local')
|
| 873 |
+
}
|
| 874 |
+
|
| 875 |
+
this.transformControl.attach(obj)
|
| 876 |
+
|
| 877 |
+
const body = this.getBodyByPart(obj)
|
| 878 |
+
if (body) this.triggerSelectEvent(body)
|
| 879 |
+
}
|
| 880 |
+
}
|
| 881 |
+
}
|
| 882 |
+
}
|
| 883 |
+
|
| 884 |
+
traverseHandObjecct(handle: (o: THREE.Mesh) => void) {
|
| 885 |
+
this.GetBodies().forEach((o) => {
|
| 886 |
+
o.traverse((child) => {
|
| 887 |
+
if (IsHand(child?.name)) {
|
| 888 |
+
handle(child as THREE.Mesh)
|
| 889 |
+
}
|
| 890 |
+
})
|
| 891 |
+
})
|
| 892 |
+
}
|
| 893 |
+
|
| 894 |
+
traverseBodies(handle: (o: Object3D) => void) {
|
| 895 |
+
this.GetBodies().forEach((o) => {
|
| 896 |
+
o.traverse((child) => {
|
| 897 |
+
handle(child)
|
| 898 |
+
})
|
| 899 |
+
})
|
| 900 |
+
}
|
| 901 |
+
|
| 902 |
+
traverseBones(handle: (o: Bone) => void) {
|
| 903 |
+
this.GetBodies().forEach((o) => {
|
| 904 |
+
o.traverse((child) => {
|
| 905 |
+
if (child instanceof Bone && IsBone(child.name)) handle(child)
|
| 906 |
+
})
|
| 907 |
+
})
|
| 908 |
+
}
|
| 909 |
+
|
| 910 |
+
traverseExtremities(handle: (o: THREE.Mesh) => void) {
|
| 911 |
+
this.GetBodies().forEach((o) => {
|
| 912 |
+
o.traverse((child) => {
|
| 913 |
+
if (IsExtremities(child.name)) {
|
| 914 |
+
handle(child as THREE.Mesh)
|
| 915 |
+
}
|
| 916 |
+
})
|
| 917 |
+
})
|
| 918 |
+
}
|
| 919 |
+
|
| 920 |
+
onlyShowSkeleton() {
|
| 921 |
+
const recoveryArr: Object3D[] = []
|
| 922 |
+
this.traverseBodies((o) => {
|
| 923 |
+
if (IsSkeleton(o.name) === false) {
|
| 924 |
+
if (o.visible == true) {
|
| 925 |
+
o.visible = false
|
| 926 |
+
recoveryArr.push(o)
|
| 927 |
+
}
|
| 928 |
+
}
|
| 929 |
+
})
|
| 930 |
+
|
| 931 |
+
return () => {
|
| 932 |
+
recoveryArr.forEach((o) => (o.visible = true))
|
| 933 |
+
}
|
| 934 |
+
}
|
| 935 |
+
|
| 936 |
+
showMask() {
|
| 937 |
+
const recoveryArr: Object3D[] = []
|
| 938 |
+
this.scene.traverse((o) => {
|
| 939 |
+
if (IsMask(o.name)) {
|
| 940 |
+
console.log(o.name)
|
| 941 |
+
o.visible = true
|
| 942 |
+
recoveryArr.push(o)
|
| 943 |
+
}
|
| 944 |
+
})
|
| 945 |
+
|
| 946 |
+
return () => {
|
| 947 |
+
recoveryArr.forEach((o) => (o.visible = false))
|
| 948 |
+
}
|
| 949 |
+
}
|
| 950 |
+
|
| 951 |
+
hideSkeleten() {
|
| 952 |
+
const map = new Map<Object3D, Object3D | null>()
|
| 953 |
+
|
| 954 |
+
this.GetBodies().forEach((o) => {
|
| 955 |
+
o.traverse((child) => {
|
| 956 |
+
if (IsExtremities(child?.name)) {
|
| 957 |
+
map.set(child, child.parent)
|
| 958 |
+
this.scene.attach(child)
|
| 959 |
+
} else if (child?.name === 'red_point') {
|
| 960 |
+
child.visible = false
|
| 961 |
+
}
|
| 962 |
+
})
|
| 963 |
+
o.visible = false
|
| 964 |
+
})
|
| 965 |
+
return map
|
| 966 |
+
}
|
| 967 |
+
|
| 968 |
+
GetBodies() {
|
| 969 |
+
return this.scene.children.filter((o) => o?.name === 'torso')
|
| 970 |
+
}
|
| 971 |
+
showSkeleten(map: Map<Object3D, Object3D | null>) {
|
| 972 |
+
for (const [k, v] of map.entries()) {
|
| 973 |
+
v?.attach(k)
|
| 974 |
+
}
|
| 975 |
+
|
| 976 |
+
map.clear()
|
| 977 |
+
|
| 978 |
+
this.GetBodies().forEach((o) => {
|
| 979 |
+
o.traverse((child) => {
|
| 980 |
+
if (child?.name === 'red_point') {
|
| 981 |
+
child.visible = true
|
| 982 |
+
}
|
| 983 |
+
})
|
| 984 |
+
o.visible = true
|
| 985 |
+
})
|
| 986 |
+
}
|
| 987 |
+
|
| 988 |
+
changeComposer(enable: boolean) {
|
| 989 |
+
const save = this.enableComposer
|
| 990 |
+
this.enableComposer = enable
|
| 991 |
+
|
| 992 |
+
return () => (this.enableComposer = save)
|
| 993 |
+
}
|
| 994 |
+
|
| 995 |
+
changeHandMaterialTraverse(type: 'depth' | 'normal' | 'phone') {
|
| 996 |
+
const map = new Map<THREE.Mesh, Material | Material[]>()
|
| 997 |
+
this.scene.traverse((child) => {
|
| 998 |
+
if (!IsExtremities(child.name)) return
|
| 999 |
+
const o = GetExtremityMesh(child) as THREE.Mesh
|
| 1000 |
+
map.set(o, o.material)
|
| 1001 |
+
if (type == 'depth') o.material = new MeshDepthMaterial()
|
| 1002 |
+
else if (type == 'normal') o.material = new MeshNormalMaterial()
|
| 1003 |
+
else if (type == 'phone') o.material = new MeshPhongMaterial()
|
| 1004 |
+
})
|
| 1005 |
+
|
| 1006 |
+
return () => {
|
| 1007 |
+
for (const [k, v] of map.entries()) {
|
| 1008 |
+
k.material = v
|
| 1009 |
+
}
|
| 1010 |
+
|
| 1011 |
+
map.clear()
|
| 1012 |
+
}
|
| 1013 |
+
}
|
| 1014 |
+
|
| 1015 |
+
changeHandMaterial(type: 'depth' | 'normal' | 'phone') {
|
| 1016 |
+
if (type == 'depth')
|
| 1017 |
+
this.scene.overrideMaterial = new MeshDepthMaterial()
|
| 1018 |
+
else if (type == 'normal')
|
| 1019 |
+
this.scene.overrideMaterial = new MeshNormalMaterial()
|
| 1020 |
+
else if (type == 'phone')
|
| 1021 |
+
this.scene.overrideMaterial = new MeshPhongMaterial()
|
| 1022 |
+
|
| 1023 |
+
return () => {
|
| 1024 |
+
this.scene.overrideMaterial = null
|
| 1025 |
+
}
|
| 1026 |
+
}
|
| 1027 |
+
|
| 1028 |
+
// https://stackoverflow.com/questions/15696963/three-js-set-and-read-camera-look-vector?noredirect=1&lq=1
|
| 1029 |
+
getCameraLookAtVector() {
|
| 1030 |
+
const lookAtVector = new THREE.Vector3(0, 0, -1)
|
| 1031 |
+
lookAtVector.applyQuaternion(this.camera.quaternion)
|
| 1032 |
+
return lookAtVector
|
| 1033 |
+
}
|
| 1034 |
+
|
| 1035 |
+
getZDistanceFromCamera(p: THREE.Vector3) {
|
| 1036 |
+
const lookAt = this.getCameraLookAtVector().normalize()
|
| 1037 |
+
const v = p.clone().sub(this.camera.position)
|
| 1038 |
+
return v.dot(lookAt)
|
| 1039 |
+
}
|
| 1040 |
+
changeCamera() {
|
| 1041 |
+
let hands: THREE.Mesh[] = []
|
| 1042 |
+
this.scene.traverse((o) => {
|
| 1043 |
+
if (this.OnlyHand) {
|
| 1044 |
+
if (IsHand(o?.name)) hands.push(o as THREE.Mesh)
|
| 1045 |
+
} else {
|
| 1046 |
+
if (IsExtremities(o?.name)) hands.push(o as THREE.Mesh)
|
| 1047 |
+
}
|
| 1048 |
+
})
|
| 1049 |
+
|
| 1050 |
+
// filter object in frustum
|
| 1051 |
+
hands = this.objectInView(hands)
|
| 1052 |
+
|
| 1053 |
+
const cameraPos = new THREE.Vector3()
|
| 1054 |
+
this.camera.getWorldPosition(cameraPos)
|
| 1055 |
+
|
| 1056 |
+
const handsPos = hands.map((o) => {
|
| 1057 |
+
const cameraPos = new THREE.Vector3()
|
| 1058 |
+
o.getWorldPosition(cameraPos)
|
| 1059 |
+
return cameraPos
|
| 1060 |
+
})
|
| 1061 |
+
|
| 1062 |
+
const handsDis = handsPos.map((pos) => {
|
| 1063 |
+
return this.getZDistanceFromCamera(pos)
|
| 1064 |
+
})
|
| 1065 |
+
|
| 1066 |
+
const minDis = Math.min(...handsDis)
|
| 1067 |
+
const maxDis = Math.max(...handsDis)
|
| 1068 |
+
|
| 1069 |
+
const saveNear = this.camera.near
|
| 1070 |
+
const saveFar = this.camera.far
|
| 1071 |
+
|
| 1072 |
+
this.camera.near = Math.max(minDis - 20, 0)
|
| 1073 |
+
this.camera.far = Math.max(maxDis + 20, 20)
|
| 1074 |
+
console.log('camera', this.camera.near, this.camera.far)
|
| 1075 |
+
|
| 1076 |
+
this.camera.updateProjectionMatrix()
|
| 1077 |
+
return () => {
|
| 1078 |
+
this.camera.near = saveNear
|
| 1079 |
+
this.camera.far = saveFar
|
| 1080 |
+
this.camera.updateProjectionMatrix()
|
| 1081 |
+
}
|
| 1082 |
+
}
|
| 1083 |
+
|
| 1084 |
+
Capture() {
|
| 1085 |
+
const restore = this.onlyShowSkeleton()
|
| 1086 |
+
|
| 1087 |
+
this.renderOutput()
|
| 1088 |
+
const imgData = this.getOutputPNG()
|
| 1089 |
+
|
| 1090 |
+
restore()
|
| 1091 |
+
|
| 1092 |
+
return imgData
|
| 1093 |
+
}
|
| 1094 |
+
|
| 1095 |
+
CapturePreview() {
|
| 1096 |
+
const scale = (window.devicePixelRatio * 140.0) / this.OutputHeight
|
| 1097 |
+
|
| 1098 |
+
const outputWidth = this.OutputWidth * scale
|
| 1099 |
+
const outputHeight = this.OutputHeight * scale
|
| 1100 |
+
|
| 1101 |
+
this.previewRenderer.render(
|
| 1102 |
+
outputWidth,
|
| 1103 |
+
outputHeight,
|
| 1104 |
+
this.cameraDataOfView
|
| 1105 |
+
)
|
| 1106 |
+
}
|
| 1107 |
+
|
| 1108 |
+
CaptureCanny() {
|
| 1109 |
+
this.renderOutput(1, (outputWidth, outputHeight) => {
|
| 1110 |
+
this.changeComposerResoultion(outputWidth, outputHeight)
|
| 1111 |
+
const restoreMaterialTraverse =
|
| 1112 |
+
this.changeHandMaterialTraverse('normal')
|
| 1113 |
+
// step 1: get mask image
|
| 1114 |
+
const restoreMask = this.showMask()
|
| 1115 |
+
this.composer?.render()
|
| 1116 |
+
restoreMask()
|
| 1117 |
+
|
| 1118 |
+
// step 2:
|
| 1119 |
+
// get sobel image
|
| 1120 |
+
// filer out pixels not in mask
|
| 1121 |
+
// get binarized pixels
|
| 1122 |
+
this.finalComposer?.render()
|
| 1123 |
+
restoreMaterialTraverse()
|
| 1124 |
+
})
|
| 1125 |
+
|
| 1126 |
+
return this.getOutputPNG()
|
| 1127 |
+
}
|
| 1128 |
+
|
| 1129 |
+
CaptureNormal() {
|
| 1130 |
+
const restoreHand = this.changeHandMaterial('normal')
|
| 1131 |
+
this.renderOutput()
|
| 1132 |
+
restoreHand()
|
| 1133 |
+
return this.getOutputPNG()
|
| 1134 |
+
}
|
| 1135 |
+
|
| 1136 |
+
CaptureDepth() {
|
| 1137 |
+
const restoreHand = this.changeHandMaterial('depth')
|
| 1138 |
+
const restoreCamera = this.changeCamera()
|
| 1139 |
+
this.renderOutput()
|
| 1140 |
+
restoreCamera()
|
| 1141 |
+
restoreHand()
|
| 1142 |
+
return this.getOutputPNG()
|
| 1143 |
+
}
|
| 1144 |
+
|
| 1145 |
+
changeTransformControl() {
|
| 1146 |
+
const part = this.getSelectedPart()
|
| 1147 |
+
|
| 1148 |
+
if (part) {
|
| 1149 |
+
this.DetachTransfromControl()
|
| 1150 |
+
return () => {
|
| 1151 |
+
this.transformControl.attach(part)
|
| 1152 |
+
}
|
| 1153 |
+
}
|
| 1154 |
+
|
| 1155 |
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
| 1156 |
+
return () => {}
|
| 1157 |
+
}
|
| 1158 |
+
changeHelper() {
|
| 1159 |
+
const old = {
|
| 1160 |
+
axesHelper: this.axesHelper.visible,
|
| 1161 |
+
gridHelper: this.gridHelper.visible,
|
| 1162 |
+
}
|
| 1163 |
+
this.axesHelper.visible = false
|
| 1164 |
+
this.gridHelper.visible = false
|
| 1165 |
+
|
| 1166 |
+
return () => {
|
| 1167 |
+
this.axesHelper.visible = old.axesHelper
|
| 1168 |
+
this.gridHelper.visible = old.gridHelper
|
| 1169 |
+
}
|
| 1170 |
+
}
|
| 1171 |
+
MakeImages() {
|
| 1172 |
+
this.renderer.setClearColor(0x000000)
|
| 1173 |
+
|
| 1174 |
+
const restoreHelper = this.changeHelper()
|
| 1175 |
+
|
| 1176 |
+
const restoreTransfromControl = this.changeTransformControl()
|
| 1177 |
+
const restoreView = this.changeView()
|
| 1178 |
+
|
| 1179 |
+
const poseImage = this.Capture()
|
| 1180 |
+
|
| 1181 |
+
/// begin
|
| 1182 |
+
const map = this.hideSkeleten()
|
| 1183 |
+
const depthImage = this.CaptureDepth()
|
| 1184 |
+
const normalImage = this.CaptureNormal()
|
| 1185 |
+
const cannyImage = this.CaptureCanny()
|
| 1186 |
+
this.showSkeleten(map)
|
| 1187 |
+
/// end
|
| 1188 |
+
|
| 1189 |
+
this.renderer.setClearColor(0x000000, 0)
|
| 1190 |
+
restoreHelper()
|
| 1191 |
+
|
| 1192 |
+
restoreTransfromControl()
|
| 1193 |
+
restoreView()
|
| 1194 |
+
|
| 1195 |
+
const result = {
|
| 1196 |
+
pose: poseImage,
|
| 1197 |
+
depth: depthImage,
|
| 1198 |
+
normal: normalImage,
|
| 1199 |
+
canny: cannyImage,
|
| 1200 |
+
}
|
| 1201 |
+
|
| 1202 |
+
sendToAll({
|
| 1203 |
+
method: 'MakeImages',
|
| 1204 |
+
type: 'event',
|
| 1205 |
+
payload: result,
|
| 1206 |
+
})
|
| 1207 |
+
return result
|
| 1208 |
+
}
|
| 1209 |
+
|
| 1210 |
+
CopySelectedBody() {
|
| 1211 |
+
const list = this.GetBodies()
|
| 1212 |
+
|
| 1213 |
+
const selectedBody = this.getSelectedBody()
|
| 1214 |
+
|
| 1215 |
+
if (!selectedBody && list.length !== 0) return
|
| 1216 |
+
|
| 1217 |
+
const body =
|
| 1218 |
+
list.length === 0 ? CloneBody() : SkeletonUtils.clone(selectedBody!)
|
| 1219 |
+
|
| 1220 |
+
if (!body) return
|
| 1221 |
+
|
| 1222 |
+
this.pushCommand(this.CreateAddBodyCommand(body))
|
| 1223 |
+
|
| 1224 |
+
if (list.length !== 0) body.position.x += 10
|
| 1225 |
+
this.scene.add(body)
|
| 1226 |
+
this.fixFootVisible()
|
| 1227 |
+
this.transformControl.setMode('translate')
|
| 1228 |
+
this.transformControl.setSpace('world')
|
| 1229 |
+
|
| 1230 |
+
this.transformControl.attach(body)
|
| 1231 |
+
}
|
| 1232 |
+
|
| 1233 |
+
CopyBodyZ() {
|
| 1234 |
+
const body = CloneBody()
|
| 1235 |
+
if (!body) return
|
| 1236 |
+
|
| 1237 |
+
const list = this.GetBodies()
|
| 1238 |
+
.filter((o) => o.position.x === 0)
|
| 1239 |
+
.map((o) => Math.ceil(o.position.z / 30))
|
| 1240 |
+
|
| 1241 |
+
if (list.length > 0) body.translateZ((Math.min(...list) - 1) * 30)
|
| 1242 |
+
|
| 1243 |
+
this.pushCommand(this.CreateAddBodyCommand(body))
|
| 1244 |
+
|
| 1245 |
+
this.scene.add(body)
|
| 1246 |
+
this.fixFootVisible()
|
| 1247 |
+
}
|
| 1248 |
+
|
| 1249 |
+
CopyBodyX() {
|
| 1250 |
+
const body = CloneBody()
|
| 1251 |
+
if (!body) return
|
| 1252 |
+
|
| 1253 |
+
const list = this.GetBodies()
|
| 1254 |
+
.filter((o) => o.position.z === 0)
|
| 1255 |
+
.map((o) => Math.ceil(o.position.x / 50))
|
| 1256 |
+
|
| 1257 |
+
if (list.length > 0) body.translateX((Math.min(...list) - 1) * 50)
|
| 1258 |
+
|
| 1259 |
+
this.pushCommand(this.CreateAddBodyCommand(body))
|
| 1260 |
+
|
| 1261 |
+
this.scene.add(body)
|
| 1262 |
+
this.fixFootVisible()
|
| 1263 |
+
}
|
| 1264 |
+
|
| 1265 |
+
getSelectedBody() {
|
| 1266 |
+
let obj: Object3D | null = this.getSelectedPart() ?? null
|
| 1267 |
+
obj = obj ? this.getBodyByPart(obj) : null
|
| 1268 |
+
|
| 1269 |
+
return obj
|
| 1270 |
+
}
|
| 1271 |
+
getSelectedPart() {
|
| 1272 |
+
return this.transformControl.object
|
| 1273 |
+
}
|
| 1274 |
+
|
| 1275 |
+
getHandByPart(o: Object3D) {
|
| 1276 |
+
if (IsHand(o?.name)) return o
|
| 1277 |
+
|
| 1278 |
+
const body = this.getAncestors(o).find((o) => IsHand(o?.name)) ?? null
|
| 1279 |
+
return body
|
| 1280 |
+
}
|
| 1281 |
+
|
| 1282 |
+
getSelectedHand() {
|
| 1283 |
+
let obj: Object3D | null = this.getSelectedPart() ?? null
|
| 1284 |
+
obj = obj ? this.getHandByPart(obj) : null
|
| 1285 |
+
return obj
|
| 1286 |
+
}
|
| 1287 |
+
RemoveBody() {
|
| 1288 |
+
const obj = this.getSelectedBody()
|
| 1289 |
+
|
| 1290 |
+
if (obj) {
|
| 1291 |
+
this.pushCommand(this.CreateRemoveBodyCommand(obj))
|
| 1292 |
+
console.log(obj.name)
|
| 1293 |
+
obj.removeFromParent()
|
| 1294 |
+
this.DetachTransfromControl()
|
| 1295 |
+
}
|
| 1296 |
+
}
|
| 1297 |
+
|
| 1298 |
+
pointsInView(points: THREE.Vector3[]) {
|
| 1299 |
+
this.camera.updateMatrix() // make sure camera's local matrix is updated
|
| 1300 |
+
this.camera.updateMatrixWorld() // make sure camera's world matrix is updated
|
| 1301 |
+
|
| 1302 |
+
const frustum = new THREE.Frustum().setFromProjectionMatrix(
|
| 1303 |
+
new THREE.Matrix4().multiplyMatrices(
|
| 1304 |
+
this.camera.projectionMatrix,
|
| 1305 |
+
this.camera.matrixWorldInverse
|
| 1306 |
+
)
|
| 1307 |
+
)
|
| 1308 |
+
|
| 1309 |
+
//console.log(points);
|
| 1310 |
+
return points.filter((p) => frustum.containsPoint(p))
|
| 1311 |
+
}
|
| 1312 |
+
|
| 1313 |
+
getBouningSphere(o: Object3D) {
|
| 1314 |
+
const bbox = new THREE.Box3().setFromObject(o, true)
|
| 1315 |
+
// const helper = new THREE.Box3Helper(bbox, new THREE.Color(0, 255, 0))
|
| 1316 |
+
// this.scene.add(helper)
|
| 1317 |
+
|
| 1318 |
+
const center = new THREE.Vector3()
|
| 1319 |
+
bbox.getCenter(center)
|
| 1320 |
+
|
| 1321 |
+
const bsphere = bbox.getBoundingSphere(new THREE.Sphere(center))
|
| 1322 |
+
|
| 1323 |
+
return bsphere
|
| 1324 |
+
}
|
| 1325 |
+
objectInView<T extends Object3D>(objs: T[]) {
|
| 1326 |
+
this.camera.updateMatrix() // make sure camera's local matrix is updated
|
| 1327 |
+
this.camera.updateMatrixWorld() // make sure camera's world matrix is updated
|
| 1328 |
+
|
| 1329 |
+
const frustum = new THREE.Frustum().setFromProjectionMatrix(
|
| 1330 |
+
new THREE.Matrix4().multiplyMatrices(
|
| 1331 |
+
this.camera.projectionMatrix,
|
| 1332 |
+
this.camera.matrixWorldInverse
|
| 1333 |
+
)
|
| 1334 |
+
)
|
| 1335 |
+
|
| 1336 |
+
//console.log(points);
|
| 1337 |
+
return objs.filter((obj) => {
|
| 1338 |
+
const sphere = this.getBouningSphere(obj)
|
| 1339 |
+
return frustum.intersectsSphere(sphere)
|
| 1340 |
+
})
|
| 1341 |
+
}
|
| 1342 |
+
isMoveMode = false
|
| 1343 |
+
get MoveMode() {
|
| 1344 |
+
return this.isMoveMode
|
| 1345 |
+
}
|
| 1346 |
+
set MoveMode(move: boolean) {
|
| 1347 |
+
let IsTranslateMode = move
|
| 1348 |
+
this.isMoveMode = move
|
| 1349 |
+
|
| 1350 |
+
const name = this.getSelectedPart()?.name ?? ''
|
| 1351 |
+
|
| 1352 |
+
if (move) {
|
| 1353 |
+
if (IsTranslate(name, this.FreeMode)) {
|
| 1354 |
+
IsTranslateMode = true
|
| 1355 |
+
} else {
|
| 1356 |
+
const obj = this.getSelectedBody()
|
| 1357 |
+
if (obj) this.transformControl.attach(obj)
|
| 1358 |
+
}
|
| 1359 |
+
} else {
|
| 1360 |
+
if (IsTarget(name)) {
|
| 1361 |
+
IsTranslateMode = true
|
| 1362 |
+
}
|
| 1363 |
+
}
|
| 1364 |
+
|
| 1365 |
+
if (IsTranslateMode) {
|
| 1366 |
+
this.transformControl.setMode('translate')
|
| 1367 |
+
this.transformControl.setSpace('world')
|
| 1368 |
+
} else {
|
| 1369 |
+
this.transformControl.setMode('rotate')
|
| 1370 |
+
this.transformControl.setSpace('local')
|
| 1371 |
+
}
|
| 1372 |
+
}
|
| 1373 |
+
|
| 1374 |
+
FreeMode = true
|
| 1375 |
+
|
| 1376 |
+
get Width() {
|
| 1377 |
+
return this.renderer.domElement.clientWidth
|
| 1378 |
+
}
|
| 1379 |
+
|
| 1380 |
+
get Height() {
|
| 1381 |
+
return this.renderer.domElement.clientHeight
|
| 1382 |
+
}
|
| 1383 |
+
|
| 1384 |
+
onlyHand = false
|
| 1385 |
+
get OnlyHand() {
|
| 1386 |
+
return this.onlyHand
|
| 1387 |
+
}
|
| 1388 |
+
|
| 1389 |
+
set OnlyHand(value: boolean) {
|
| 1390 |
+
this.onlyHand = value
|
| 1391 |
+
this.setFootVisible(!this.onlyHand)
|
| 1392 |
+
}
|
| 1393 |
+
|
| 1394 |
+
get EnableHelper() {
|
| 1395 |
+
return this.enableHelper
|
| 1396 |
+
}
|
| 1397 |
+
set EnableHelper(value: boolean) {
|
| 1398 |
+
this.enableHelper = value
|
| 1399 |
+
this.gridHelper.visible = value
|
| 1400 |
+
this.axesHelper.visible = value
|
| 1401 |
+
}
|
| 1402 |
+
setFootVisible(value: boolean) {
|
| 1403 |
+
this.traverseExtremities((o) => {
|
| 1404 |
+
if (IsFoot(o.name)) {
|
| 1405 |
+
o.visible = value
|
| 1406 |
+
}
|
| 1407 |
+
})
|
| 1408 |
+
}
|
| 1409 |
+
|
| 1410 |
+
fixFootVisible() {
|
| 1411 |
+
this.setFootVisible(!this.OnlyHand)
|
| 1412 |
+
}
|
| 1413 |
+
|
| 1414 |
+
handleResize() {
|
| 1415 |
+
const size = new THREE.Vector2()
|
| 1416 |
+
this.renderer.getSize(size)
|
| 1417 |
+
|
| 1418 |
+
if (size.width == this.Width && size.height === this.Height) return
|
| 1419 |
+
|
| 1420 |
+
const canvas = this.renderer.domElement
|
| 1421 |
+
if (canvas.clientWidth == 0 || canvas.clientHeight == 0) return
|
| 1422 |
+
this.camera.aspect = canvas.clientWidth / canvas.clientHeight
|
| 1423 |
+
|
| 1424 |
+
this.camera.updateProjectionMatrix()
|
| 1425 |
+
|
| 1426 |
+
// console.log(canvas.clientWidth, canvas.clientHeight)
|
| 1427 |
+
this.renderer.setSize(canvas.clientWidth, canvas.clientHeight, false)
|
| 1428 |
+
// console.log(this.Width, this.Height)
|
| 1429 |
+
}
|
| 1430 |
+
|
| 1431 |
+
initEdgeComposer() {
|
| 1432 |
+
this.composer = new EffectComposer(this.outputRenderer)
|
| 1433 |
+
const renderPass = new RenderPass(this.scene, this.camera)
|
| 1434 |
+
this.composer.addPass(renderPass)
|
| 1435 |
+
this.composer.renderToScreen = false
|
| 1436 |
+
|
| 1437 |
+
const finalPass = new ShaderPass(
|
| 1438 |
+
new THREE.ShaderMaterial({
|
| 1439 |
+
uniforms: {
|
| 1440 |
+
baseTexture: { value: null },
|
| 1441 |
+
bloomTexture: {
|
| 1442 |
+
value: this.composer.renderTarget2.texture,
|
| 1443 |
+
},
|
| 1444 |
+
},
|
| 1445 |
+
vertexShader: `
|
| 1446 |
+
varying vec2 vUv;
|
| 1447 |
+
void main() {
|
| 1448 |
+
vUv = uv;
|
| 1449 |
+
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
| 1450 |
+
|
| 1451 |
+
}`,
|
| 1452 |
+
fragmentShader: `
|
| 1453 |
+
uniform sampler2D baseTexture;
|
| 1454 |
+
uniform sampler2D bloomTexture;
|
| 1455 |
+
|
| 1456 |
+
varying vec2 vUv;
|
| 1457 |
+
|
| 1458 |
+
void main() {
|
| 1459 |
+
vec4 bloomColor = texture2D(bloomTexture, vUv);
|
| 1460 |
+
float grayValue = dot(bloomColor.rgb, vec3(0.299, 0.587, 0.114));
|
| 1461 |
+
vec4 baseColor = texture2D(baseTexture, vUv);
|
| 1462 |
+
vec4 masked = vec4(baseColor.rgb * step(0.001, grayValue), 1.0);
|
| 1463 |
+
gl_FragColor = step(0.5, masked) * vec4(1.0); // Binarization
|
| 1464 |
+
// gl_FragColor = bloomColor;
|
| 1465 |
+
}
|
| 1466 |
+
|
| 1467 |
+
`,
|
| 1468 |
+
defines: {},
|
| 1469 |
+
}),
|
| 1470 |
+
'baseTexture'
|
| 1471 |
+
)
|
| 1472 |
+
finalPass.needsSwap = true
|
| 1473 |
+
|
| 1474 |
+
this.finalComposer = new EffectComposer(this.outputRenderer)
|
| 1475 |
+
this.finalComposer.addPass(renderPass)
|
| 1476 |
+
|
| 1477 |
+
// color to grayscale conversion
|
| 1478 |
+
const effectGrayScale = new ShaderPass(LuminosityShader)
|
| 1479 |
+
this.finalComposer.addPass(effectGrayScale)
|
| 1480 |
+
|
| 1481 |
+
// Sobel operator
|
| 1482 |
+
const effectSobel = new ShaderPass(SobelOperatorShader)
|
| 1483 |
+
effectSobel.uniforms['resolution'].value.x =
|
| 1484 |
+
this.Width * window.devicePixelRatio
|
| 1485 |
+
effectSobel.uniforms['resolution'].value.y =
|
| 1486 |
+
this.Height * window.devicePixelRatio
|
| 1487 |
+
this.finalComposer.addPass(effectSobel)
|
| 1488 |
+
|
| 1489 |
+
this.effectSobel = effectSobel
|
| 1490 |
+
|
| 1491 |
+
this.finalComposer.addPass(finalPass)
|
| 1492 |
+
}
|
| 1493 |
+
|
| 1494 |
+
changeComposerResoultion(width: number, height: number) {
|
| 1495 |
+
this.composer?.setSize(width, height)
|
| 1496 |
+
this.finalComposer?.setSize(width, height)
|
| 1497 |
+
|
| 1498 |
+
if (this.effectSobel) {
|
| 1499 |
+
this.effectSobel.uniforms['resolution'].value.x =
|
| 1500 |
+
width * window.devicePixelRatio
|
| 1501 |
+
this.effectSobel.uniforms['resolution'].value.y =
|
| 1502 |
+
height * window.devicePixelRatio
|
| 1503 |
+
}
|
| 1504 |
+
}
|
| 1505 |
+
|
| 1506 |
+
get CameraNear() {
|
| 1507 |
+
return this.camera.near
|
| 1508 |
+
}
|
| 1509 |
+
set CameraNear(value: number) {
|
| 1510 |
+
this.camera.near = value
|
| 1511 |
+
this.camera.updateProjectionMatrix()
|
| 1512 |
+
}
|
| 1513 |
+
|
| 1514 |
+
get CameraFar() {
|
| 1515 |
+
return this.camera.far
|
| 1516 |
+
}
|
| 1517 |
+
set CameraFar(value: number) {
|
| 1518 |
+
this.camera.far = value
|
| 1519 |
+
this.camera.updateProjectionMatrix()
|
| 1520 |
+
}
|
| 1521 |
+
|
| 1522 |
+
get CameraFocalLength() {
|
| 1523 |
+
return this.camera.getFocalLength()
|
| 1524 |
+
}
|
| 1525 |
+
set CameraFocalLength(value) {
|
| 1526 |
+
this.camera.setFocalLength(value)
|
| 1527 |
+
}
|
| 1528 |
+
|
| 1529 |
+
GetCameraData() {
|
| 1530 |
+
const result = {
|
| 1531 |
+
position: this.camera.position.toArray(),
|
| 1532 |
+
rotation: this.camera.rotation.toArray(),
|
| 1533 |
+
target: this.orbitControls.target.toArray(),
|
| 1534 |
+
near: this.camera.near,
|
| 1535 |
+
far: this.camera.far,
|
| 1536 |
+
zoom: this.camera.zoom,
|
| 1537 |
+
}
|
| 1538 |
+
|
| 1539 |
+
return result
|
| 1540 |
+
}
|
| 1541 |
+
GetSceneData() {
|
| 1542 |
+
const bodies = this.GetBodies().map((o) =>
|
| 1543 |
+
new BodyControlor(o).GetBodyData()
|
| 1544 |
+
)
|
| 1545 |
+
|
| 1546 |
+
const data = {
|
| 1547 |
+
header: 'Openpose Editor by Yu Zhu',
|
| 1548 |
+
version: __APP_VERSION__,
|
| 1549 |
+
object: {
|
| 1550 |
+
bodies: bodies,
|
| 1551 |
+
camera: this.GetCameraData(),
|
| 1552 |
+
},
|
| 1553 |
+
setting: {},
|
| 1554 |
+
}
|
| 1555 |
+
|
| 1556 |
+
return data
|
| 1557 |
+
}
|
| 1558 |
+
GetGesture() {
|
| 1559 |
+
const hand = this.getSelectedHand()
|
| 1560 |
+
const body = this.getSelectedBody()
|
| 1561 |
+
|
| 1562 |
+
if (!hand || !body) return null
|
| 1563 |
+
const data = {
|
| 1564 |
+
header: 'Openpose Editor by Yu Zhu',
|
| 1565 |
+
version: __APP_VERSION__,
|
| 1566 |
+
object: {
|
| 1567 |
+
hand: new BodyControlor(body).GetHandData(
|
| 1568 |
+
hand.name === 'left_hand' ? 'left_hand' : 'right_hand'
|
| 1569 |
+
),
|
| 1570 |
+
},
|
| 1571 |
+
setting: {},
|
| 1572 |
+
}
|
| 1573 |
+
|
| 1574 |
+
return data
|
| 1575 |
+
}
|
| 1576 |
+
|
| 1577 |
+
AutoSaveScene() {
|
| 1578 |
+
try {
|
| 1579 |
+
const rawData = localStorage.getItem('AutoSaveSceneData')
|
| 1580 |
+
if (rawData) {
|
| 1581 |
+
localStorage.setItem('LastSceneData', rawData)
|
| 1582 |
+
}
|
| 1583 |
+
setInterval(() => {
|
| 1584 |
+
localStorage.setItem(
|
| 1585 |
+
'AutoSaveSceneData',
|
| 1586 |
+
JSON.stringify(this.GetSceneData())
|
| 1587 |
+
)
|
| 1588 |
+
}, 5000)
|
| 1589 |
+
} catch (error) {
|
| 1590 |
+
console.error(error)
|
| 1591 |
+
}
|
| 1592 |
+
}
|
| 1593 |
+
|
| 1594 |
+
SaveScene() {
|
| 1595 |
+
try {
|
| 1596 |
+
downloadJson(
|
| 1597 |
+
JSON.stringify(this.GetSceneData()),
|
| 1598 |
+
`scene_${getCurrentTime()}.json`
|
| 1599 |
+
)
|
| 1600 |
+
} catch (error) {
|
| 1601 |
+
console.error(error)
|
| 1602 |
+
}
|
| 1603 |
+
}
|
| 1604 |
+
RestoreGesture(rawData: string) {
|
| 1605 |
+
const data = JSON.parse(rawData)
|
| 1606 |
+
|
| 1607 |
+
const {
|
| 1608 |
+
version,
|
| 1609 |
+
object: { hand: handData },
|
| 1610 |
+
setting,
|
| 1611 |
+
} = data
|
| 1612 |
+
|
| 1613 |
+
if (!handData) throw new Error('Invalid json')
|
| 1614 |
+
const hand = this.getSelectedHand()
|
| 1615 |
+
const body = this.getSelectedBody()
|
| 1616 |
+
|
| 1617 |
+
if (!hand || !body) throw new Error('!hand || !body')
|
| 1618 |
+
|
| 1619 |
+
new BodyControlor(body).RestoreHand(
|
| 1620 |
+
hand.name == 'left_hand' ? 'left_hand' : 'right_hand',
|
| 1621 |
+
handData
|
| 1622 |
+
)
|
| 1623 |
+
}
|
| 1624 |
+
SaveGesture() {
|
| 1625 |
+
const data = this.GetGesture()
|
| 1626 |
+
if (!data) throw new Error('Failed to get gesture')
|
| 1627 |
+
downloadJson(JSON.stringify(data), `gesture_${getCurrentTime()}.json`)
|
| 1628 |
+
}
|
| 1629 |
+
|
| 1630 |
+
ClearScene() {
|
| 1631 |
+
this.GetBodies().forEach((o) => o.removeFromParent())
|
| 1632 |
+
}
|
| 1633 |
+
|
| 1634 |
+
CreateBodiesFromData(bodies: BodyData[]) {
|
| 1635 |
+
return bodies.map((data) => {
|
| 1636 |
+
const body = CloneBody()!
|
| 1637 |
+
new BodyControlor(body).RestoreBody(data)
|
| 1638 |
+
return body
|
| 1639 |
+
})
|
| 1640 |
+
}
|
| 1641 |
+
RestoreCamera(data: CameraData, updateOrbitControl = true) {
|
| 1642 |
+
this.camera.position.fromArray(data.position)
|
| 1643 |
+
this.camera.rotation.fromArray(data.rotation as any)
|
| 1644 |
+
this.camera.near = data.near
|
| 1645 |
+
this.camera.far = data.far
|
| 1646 |
+
this.camera.zoom = data.zoom
|
| 1647 |
+
this.camera.updateProjectionMatrix()
|
| 1648 |
+
|
| 1649 |
+
if (data.target) this.orbitControls.target.fromArray(data.target)
|
| 1650 |
+
if (updateOrbitControl) this.orbitControls.update() // fix position change
|
| 1651 |
+
}
|
| 1652 |
+
RestoreScene(rawData: string) {
|
| 1653 |
+
try {
|
| 1654 |
+
if (!rawData) return
|
| 1655 |
+
const data = JSON.parse(rawData)
|
| 1656 |
+
|
| 1657 |
+
const {
|
| 1658 |
+
version,
|
| 1659 |
+
object: { bodies, camera },
|
| 1660 |
+
setting,
|
| 1661 |
+
} = data
|
| 1662 |
+
|
| 1663 |
+
const bodiesObject = this.CreateBodiesFromData(bodies)
|
| 1664 |
+
this.ClearScene()
|
| 1665 |
+
|
| 1666 |
+
if (bodiesObject.length > 0) this.scene.add(...bodiesObject)
|
| 1667 |
+
for (const body of bodiesObject) {
|
| 1668 |
+
new BodyControlor(body).ResetAllTargetsPosition()
|
| 1669 |
+
}
|
| 1670 |
+
this.RestoreCamera(camera)
|
| 1671 |
+
} catch (error: any) {
|
| 1672 |
+
Oops(error)
|
| 1673 |
+
console.error(error)
|
| 1674 |
+
}
|
| 1675 |
+
}
|
| 1676 |
+
ResetScene() {
|
| 1677 |
+
try {
|
| 1678 |
+
this.ClearScene()
|
| 1679 |
+
this.CopySelectedBody()
|
| 1680 |
+
const body = this.getSelectedBody()
|
| 1681 |
+
if (body) {
|
| 1682 |
+
this.scene.add(body)
|
| 1683 |
+
this.dlight.target = body
|
| 1684 |
+
}
|
| 1685 |
+
} catch (error: any) {
|
| 1686 |
+
Oops(error)
|
| 1687 |
+
console.error(error)
|
| 1688 |
+
}
|
| 1689 |
+
}
|
| 1690 |
+
RestoreLastSavedScene() {
|
| 1691 |
+
const rawData = localStorage.getItem('LastSceneData')
|
| 1692 |
+
if (rawData) this.RestoreScene(rawData)
|
| 1693 |
+
}
|
| 1694 |
+
async LoadScene() {
|
| 1695 |
+
const rawData = await uploadJson()
|
| 1696 |
+
if (rawData) this.RestoreScene(rawData)
|
| 1697 |
+
}
|
| 1698 |
+
|
| 1699 |
+
// drawPoseData(positions: THREE.Vector3[]) {
|
| 1700 |
+
// const objects: Record<
|
| 1701 |
+
// keyof typeof PartIndexMappingOfBlazePoseModel,
|
| 1702 |
+
// Object3D
|
| 1703 |
+
// > = Object.fromEntries(
|
| 1704 |
+
// Object.keys(PartIndexMappingOfBlazePoseModel).map((name) => {
|
| 1705 |
+
// const p = positions[PartIndexMappingOfBlazePoseModel[name]]
|
| 1706 |
+
// const material = new THREE.MeshBasicMaterial({
|
| 1707 |
+
// color: 0xff0000,
|
| 1708 |
+
// })
|
| 1709 |
+
// const mesh = new THREE.Mesh(
|
| 1710 |
+
// new THREE.SphereGeometry(1),
|
| 1711 |
+
// material
|
| 1712 |
+
// )
|
| 1713 |
+
// mesh.position.copy(p)
|
| 1714 |
+
// this.scene.add(mesh)
|
| 1715 |
+
// return [name, mesh]
|
| 1716 |
+
// })
|
| 1717 |
+
// )
|
| 1718 |
+
|
| 1719 |
+
// const CreateLink2 = (
|
| 1720 |
+
// startObject: THREE.Object3D,
|
| 1721 |
+
// endObject: THREE.Object3D
|
| 1722 |
+
// ) => {
|
| 1723 |
+
// const startPosition = startObject.position
|
| 1724 |
+
// const endPostion = endObject.position
|
| 1725 |
+
// const distance = startPosition.distanceTo(endPostion)
|
| 1726 |
+
|
| 1727 |
+
// const material = new THREE.MeshBasicMaterial({
|
| 1728 |
+
// color: 0x666666,
|
| 1729 |
+
// opacity: 0.6,
|
| 1730 |
+
// transparent: true,
|
| 1731 |
+
// })
|
| 1732 |
+
// const mesh = new THREE.Mesh(new THREE.SphereGeometry(1), material)
|
| 1733 |
+
|
| 1734 |
+
// // 将拉伸后的球体放在中点,并计算旋转轴和角度
|
| 1735 |
+
// const origin = startPosition
|
| 1736 |
+
// .clone()
|
| 1737 |
+
// .add(endPostion)
|
| 1738 |
+
// .multiplyScalar(0.5)
|
| 1739 |
+
// const v = endPostion.clone().sub(startPosition)
|
| 1740 |
+
// const unit = new THREE.Vector3(1, 0, 0)
|
| 1741 |
+
// const axis = unit.clone().cross(v)
|
| 1742 |
+
// const angle = unit.clone().angleTo(v)
|
| 1743 |
+
|
| 1744 |
+
// mesh.scale.copy(new THREE.Vector3(distance / 2, 1, 1))
|
| 1745 |
+
// mesh.position.copy(origin)
|
| 1746 |
+
// mesh.setRotationFromAxisAngle(axis.normalize(), angle)
|
| 1747 |
+
// this.scene.add(mesh)
|
| 1748 |
+
// }
|
| 1749 |
+
|
| 1750 |
+
// CreateLink2(objects['left_shoulder'], objects['left_elbow'])
|
| 1751 |
+
// CreateLink2(objects['left_elbow'], objects['left_wrist'])
|
| 1752 |
+
// CreateLink2(objects['left_hip'], objects['left_knee'])
|
| 1753 |
+
// CreateLink2(objects['left_knee'], objects['left_ankle'])
|
| 1754 |
+
// CreateLink2(objects['right_shoulder'], objects['right_elbow'])
|
| 1755 |
+
// CreateLink2(objects['right_elbow'], objects['right_wrist'])
|
| 1756 |
+
// CreateLink2(objects['right_hip'], objects['right_knee'])
|
| 1757 |
+
// CreateLink2(objects['right_knee'], objects['right_ankle'])
|
| 1758 |
+
|
| 1759 |
+
// CreateLink2(objects['left_shoulder'], objects['right_shoulder'])
|
| 1760 |
+
// CreateLink2(objects['nose'], objects['right_eye'])
|
| 1761 |
+
// CreateLink2(objects['nose'], objects['left_eye'])
|
| 1762 |
+
// CreateLink2(objects['left_eye'], objects['left_ear'])
|
| 1763 |
+
|
| 1764 |
+
// CreateLink2(objects['right_eye'], objects['right_ear'])
|
| 1765 |
+
// }
|
| 1766 |
+
async GetBodyToSetPose() {
|
| 1767 |
+
const bodies = this.GetBodies()
|
| 1768 |
+
const body = bodies.length == 1 ? bodies[0] : this.getSelectedBody()
|
| 1769 |
+
return body
|
| 1770 |
+
}
|
| 1771 |
+
async SetPose(poseData: [number, number, number][]) {
|
| 1772 |
+
const body = await this.GetBodyToSetPose()
|
| 1773 |
+
|
| 1774 |
+
if (!body) return
|
| 1775 |
+
|
| 1776 |
+
const controlor = new BodyControlor(body)
|
| 1777 |
+
const old: BodyData = controlor.GetBodyData()
|
| 1778 |
+
controlor.SetPose(poseData)
|
| 1779 |
+
this.pushCommand(this.CreateAllTransformCommand(body, old))
|
| 1780 |
+
}
|
| 1781 |
+
async SetBlazePose(positions: [number, number, number][]) {
|
| 1782 |
+
const body = await this.GetBodyToSetPose()
|
| 1783 |
+
if (!body) return
|
| 1784 |
+
|
| 1785 |
+
const controlor = new BodyControlor(body)
|
| 1786 |
+
const old: BodyData = controlor.GetBodyData()
|
| 1787 |
+
controlor.SetBlazePose(positions)
|
| 1788 |
+
this.pushCommand(this.CreateAllTransformCommand(body, old))
|
| 1789 |
+
}
|
| 1790 |
+
|
| 1791 |
+
DetachTransfromControl() {
|
| 1792 |
+
this.transformControl.detach()
|
| 1793 |
+
this.triggerUnselectEvent()
|
| 1794 |
+
}
|
| 1795 |
+
|
| 1796 |
+
cameraDataOfView?: CameraData
|
| 1797 |
+
LockView() {
|
| 1798 |
+
this.cameraDataOfView = this.GetCameraData()
|
| 1799 |
+
this.LockViewEventManager.TriggerEvent(true)
|
| 1800 |
+
}
|
| 1801 |
+
UnlockView() {
|
| 1802 |
+
this.cameraDataOfView = undefined
|
| 1803 |
+
this.LockViewEventManager.TriggerEvent(false)
|
| 1804 |
+
}
|
| 1805 |
+
RestoreView() {
|
| 1806 |
+
if (this.cameraDataOfView) this.RestoreCamera(this.cameraDataOfView)
|
| 1807 |
+
}
|
| 1808 |
+
|
| 1809 |
+
changeView() {
|
| 1810 |
+
if (this.cameraDataOfView) {
|
| 1811 |
+
const old = this.GetCameraData()
|
| 1812 |
+
this.RestoreCamera(this.cameraDataOfView, false)
|
| 1813 |
+
return () => {
|
| 1814 |
+
this.RestoreCamera(old)
|
| 1815 |
+
}
|
| 1816 |
+
}
|
| 1817 |
+
|
| 1818 |
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
| 1819 |
+
return () => {}
|
| 1820 |
+
}
|
| 1821 |
+
getSelectedBone() {
|
| 1822 |
+
const part = this.getSelectedPart()
|
| 1823 |
+
const isSelectBone = part && IsBone(part.name)
|
| 1824 |
+
return isSelectBone ? (part as Bone) : null
|
| 1825 |
+
}
|
| 1826 |
+
UpdateBones() {
|
| 1827 |
+
const DEFAULT_COLOR = 0xff0000
|
| 1828 |
+
const DEFAULT = 1
|
| 1829 |
+
const SELECTED = 1
|
| 1830 |
+
const SELECTED_COLOR = 0xeeee00
|
| 1831 |
+
const ACTIVE = 1
|
| 1832 |
+
const INACTIVE = 0.2
|
| 1833 |
+
|
| 1834 |
+
const setColor = (
|
| 1835 |
+
bone: Bone,
|
| 1836 |
+
opacity: number,
|
| 1837 |
+
color = DEFAULT_COLOR
|
| 1838 |
+
) => {
|
| 1839 |
+
const point = bone.children.find(
|
| 1840 |
+
(o) => o instanceof THREE.Mesh && !IsMask(o.name)
|
| 1841 |
+
) as THREE.Mesh
|
| 1842 |
+
if (point) {
|
| 1843 |
+
const material = point.material as MeshBasicMaterial
|
| 1844 |
+
|
| 1845 |
+
material.color.set(color)
|
| 1846 |
+
material.opacity = opacity
|
| 1847 |
+
material.needsUpdate = true
|
| 1848 |
+
}
|
| 1849 |
+
}
|
| 1850 |
+
const selectedBone = this.getSelectedBone()
|
| 1851 |
+
|
| 1852 |
+
this.traverseBones((bone) => {
|
| 1853 |
+
setColor(bone, selectedBone ? INACTIVE : DEFAULT)
|
| 1854 |
+
})
|
| 1855 |
+
|
| 1856 |
+
if (selectedBone) {
|
| 1857 |
+
let bone = selectedBone
|
| 1858 |
+
|
| 1859 |
+
setColor(bone, SELECTED, SELECTED_COLOR)
|
| 1860 |
+
|
| 1861 |
+
bone.traverseAncestors((ancestor) => {
|
| 1862 |
+
if (IsBone(ancestor.name)) {
|
| 1863 |
+
setColor(ancestor as Bone, ACTIVE)
|
| 1864 |
+
}
|
| 1865 |
+
})
|
| 1866 |
+
|
| 1867 |
+
for (;;) {
|
| 1868 |
+
const child = bone.children.filter((o) => o instanceof Bone)
|
| 1869 |
+
if (child.length !== 1) break
|
| 1870 |
+
setColor(child[0] as Bone, ACTIVE)
|
| 1871 |
+
bone = child[0] as Bone
|
| 1872 |
+
}
|
| 1873 |
+
}
|
| 1874 |
+
}
|
| 1875 |
+
}
|
sd-webui-3d-open-pose-editor/src/entry.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Main } from './environments/online/main'
|
| 2 |
+
|
| 3 |
+
Main()
|
sd-webui-3d-open-pose-editor/src/env.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
interface ImportMetaEnv {
|
| 2 |
+
readonly VITE_IS_ONLINE?: boolean
|
| 3 |
+
}
|
sd-webui-3d-open-pose-editor/src/environments/extension/@types/webui.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
declare const gradioApp: () => Document | ShadowRoot
|
| 2 |
+
declare const switch_to_txt2img: () => void
|
| 3 |
+
declare const switch_to_img2img: () => void
|
| 4 |
+
declare const onUiLoaded: (callback: () => void) => void
|
| 5 |
+
declare const onUiUpdate: (callback: () => void) => void
|
| 6 |
+
declare const onUiTabChange: (callback: () => void) => void
|
sd-webui-3d-open-pose-editor/src/environments/extension/@types/window.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
interface Window {
|
| 2 |
+
openpose3d?: {
|
| 3 |
+
sendTxt2img: (
|
| 4 |
+
pose_image: string | null,
|
| 5 |
+
pose_target: string,
|
| 6 |
+
depth_image: string | null,
|
| 7 |
+
depth_target: string,
|
| 8 |
+
normal_image: string | null,
|
| 9 |
+
normal_target: string,
|
| 10 |
+
canny_image: string | null,
|
| 11 |
+
canny_target: string
|
| 12 |
+
) => void
|
| 13 |
+
sendImg2img: (
|
| 14 |
+
pose_image: string,
|
| 15 |
+
pose_target: string,
|
| 16 |
+
depth_image: string,
|
| 17 |
+
depth_target: string,
|
| 18 |
+
normal_image: string,
|
| 19 |
+
normal_target: string,
|
| 20 |
+
canny_image: string,
|
| 21 |
+
canny_target: string
|
| 22 |
+
) => void
|
| 23 |
+
downloadImage: (image: string | null, name: string) => void
|
| 24 |
+
}
|
| 25 |
+
}
|
sd-webui-3d-open-pose-editor/src/environments/extension/assets.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const files: Record<string, string> = {
|
| 2 |
+
'models/hand.fbx': '../models/hand.fbx',
|
| 3 |
+
'models/foot.fbx': '../models/foot.fbx',
|
| 4 |
+
'src/poses/data.bin': '../src/poses/data.bin',
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
try {
|
| 8 |
+
const params = new URLSearchParams(window.location.search)
|
| 9 |
+
const url = params.get('config')
|
| 10 |
+
if (url?.startsWith('/')) {
|
| 11 |
+
const response = await fetch(url)
|
| 12 |
+
const config = await response.json()
|
| 13 |
+
Object.assign(files, config['assets'])
|
| 14 |
+
}
|
| 15 |
+
} catch (error) {
|
| 16 |
+
console.error(error)
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
export default files
|
sd-webui-3d-open-pose-editor/src/environments/extension/entry.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getCurrentTime } from '../../utils/time'
|
| 2 |
+
import { download } from '../../utils/transfer'
|
| 3 |
+
import {
|
| 4 |
+
openGradioAccordion,
|
| 5 |
+
switchGradioTab,
|
| 6 |
+
updateGradioImage,
|
| 7 |
+
waitForElementToBeInDocument,
|
| 8 |
+
} from './internal/gradio'
|
| 9 |
+
import {
|
| 10 |
+
AddMessageEventListener,
|
| 11 |
+
InitMessageListener,
|
| 12 |
+
InvokeCommand,
|
| 13 |
+
} from './internal/message'
|
| 14 |
+
|
| 15 |
+
const isTabActive = () => {
|
| 16 |
+
const tab = gradioApp().querySelector<HTMLElement>('#tab_threedopenpose')
|
| 17 |
+
return tab && tab.style.display != 'none'
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
let isInitialized = false
|
| 21 |
+
let isPaused = false
|
| 22 |
+
|
| 23 |
+
onUiLoaded(async () => {
|
| 24 |
+
console.log('sd-webui-3d-open-pose-editor: onUiLoaded')
|
| 25 |
+
|
| 26 |
+
const sendToControlNet = async (
|
| 27 |
+
container: Element,
|
| 28 |
+
poseImage: string | null,
|
| 29 |
+
poseTarget: string,
|
| 30 |
+
depthImage: string | null,
|
| 31 |
+
depthTarget: string,
|
| 32 |
+
normalImage: string | null,
|
| 33 |
+
normalTarget: string,
|
| 34 |
+
cannyImage: string | null,
|
| 35 |
+
cannyTarget: string
|
| 36 |
+
) => {
|
| 37 |
+
let element: Element | null | undefined =
|
| 38 |
+
container.querySelector('#controlnet')
|
| 39 |
+
if (!element) {
|
| 40 |
+
for (const spans of container.querySelectorAll<HTMLSpanElement>(
|
| 41 |
+
'.cursor-pointer > span'
|
| 42 |
+
)) {
|
| 43 |
+
if (!spans.textContent?.includes('ControlNet')) {
|
| 44 |
+
continue
|
| 45 |
+
}
|
| 46 |
+
if (spans.textContent?.includes('M2M')) {
|
| 47 |
+
continue
|
| 48 |
+
}
|
| 49 |
+
element = spans.parentElement?.parentElement
|
| 50 |
+
}
|
| 51 |
+
if (!element) {
|
| 52 |
+
console.error('ControlNet element not found')
|
| 53 |
+
return
|
| 54 |
+
}
|
| 55 |
+
} else {
|
| 56 |
+
openGradioAccordion(element)
|
| 57 |
+
}
|
| 58 |
+
await waitForElementToBeInDocument(element, 'div[data-testid="image"]')
|
| 59 |
+
const imageElems = element.querySelectorAll('div[data-testid="image"]')
|
| 60 |
+
const tabsElem = element.querySelector('.tab-nav')
|
| 61 |
+
if (poseImage && poseTarget != '' && poseTarget != '-') {
|
| 62 |
+
const tabIndex = Number(poseTarget)
|
| 63 |
+
if (tabsElem) {
|
| 64 |
+
switchGradioTab(tabsElem, tabIndex)
|
| 65 |
+
}
|
| 66 |
+
await updateGradioImage(imageElems[tabIndex], poseImage, 'pose.png')
|
| 67 |
+
}
|
| 68 |
+
if (depthImage && depthTarget != '' && depthTarget != '-') {
|
| 69 |
+
const tabIndex = Number(depthTarget)
|
| 70 |
+
if (tabsElem) {
|
| 71 |
+
switchGradioTab(tabsElem, tabIndex)
|
| 72 |
+
}
|
| 73 |
+
await updateGradioImage(
|
| 74 |
+
imageElems[tabIndex],
|
| 75 |
+
depthImage,
|
| 76 |
+
'depth.png'
|
| 77 |
+
)
|
| 78 |
+
}
|
| 79 |
+
if (normalImage && normalTarget != '' && normalTarget != '-') {
|
| 80 |
+
const tabIndex = Number(normalTarget)
|
| 81 |
+
if (tabsElem) {
|
| 82 |
+
switchGradioTab(tabsElem, tabIndex)
|
| 83 |
+
}
|
| 84 |
+
await updateGradioImage(
|
| 85 |
+
imageElems[tabIndex],
|
| 86 |
+
normalImage,
|
| 87 |
+
'normal.png'
|
| 88 |
+
)
|
| 89 |
+
}
|
| 90 |
+
if (cannyImage && cannyTarget != '' && cannyTarget != '-') {
|
| 91 |
+
const tabIndex = Number(cannyTarget)
|
| 92 |
+
if (tabsElem) {
|
| 93 |
+
switchGradioTab(tabsElem, tabIndex)
|
| 94 |
+
}
|
| 95 |
+
await updateGradioImage(
|
| 96 |
+
imageElems[tabIndex],
|
| 97 |
+
cannyImage,
|
| 98 |
+
'canny.png'
|
| 99 |
+
)
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
window.openpose3d = {
|
| 104 |
+
sendTxt2img: async (
|
| 105 |
+
poseImage: string | null,
|
| 106 |
+
poseTarget: string,
|
| 107 |
+
depthImage: string | null,
|
| 108 |
+
depthTarget: string,
|
| 109 |
+
normalImage: string | null,
|
| 110 |
+
normalTarget: string,
|
| 111 |
+
cannyImage: string | null,
|
| 112 |
+
cannyTarget: string
|
| 113 |
+
) => {
|
| 114 |
+
const container = gradioApp().querySelector(
|
| 115 |
+
'#txt2img_script_container'
|
| 116 |
+
)!
|
| 117 |
+
switch_to_txt2img()
|
| 118 |
+
await sendToControlNet(
|
| 119 |
+
container,
|
| 120 |
+
poseImage,
|
| 121 |
+
poseTarget,
|
| 122 |
+
depthImage,
|
| 123 |
+
depthTarget,
|
| 124 |
+
normalImage,
|
| 125 |
+
normalTarget,
|
| 126 |
+
cannyImage,
|
| 127 |
+
cannyTarget
|
| 128 |
+
)
|
| 129 |
+
},
|
| 130 |
+
sendImg2img: async (
|
| 131 |
+
poseImage: string,
|
| 132 |
+
poseTarget: string,
|
| 133 |
+
depthImage: string,
|
| 134 |
+
depthTarget: string,
|
| 135 |
+
normalImage: string,
|
| 136 |
+
normalTarget: string,
|
| 137 |
+
cannyImage: string,
|
| 138 |
+
cannyTarget: string
|
| 139 |
+
) => {
|
| 140 |
+
const container = gradioApp().querySelector(
|
| 141 |
+
'#img2img_script_container'
|
| 142 |
+
)!
|
| 143 |
+
switch_to_img2img()
|
| 144 |
+
await sendToControlNet(
|
| 145 |
+
container,
|
| 146 |
+
poseImage,
|
| 147 |
+
poseTarget,
|
| 148 |
+
depthImage,
|
| 149 |
+
depthTarget,
|
| 150 |
+
normalImage,
|
| 151 |
+
normalTarget,
|
| 152 |
+
cannyImage,
|
| 153 |
+
cannyTarget
|
| 154 |
+
)
|
| 155 |
+
},
|
| 156 |
+
downloadImage: (image: string | null, name: string) => {
|
| 157 |
+
if (!image) {
|
| 158 |
+
return
|
| 159 |
+
}
|
| 160 |
+
const fileName = name + '_' + getCurrentTime() + '.png'
|
| 161 |
+
download(image, fileName)
|
| 162 |
+
},
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
InitMessageListener()
|
| 166 |
+
AddMessageEventListener({
|
| 167 |
+
MakeImages: async (args: Record<string, string>) => {
|
| 168 |
+
for (const [name, url] of Object.entries(args)) {
|
| 169 |
+
const element = gradioApp().querySelector(
|
| 170 |
+
`#openpose3d_${name}_image`
|
| 171 |
+
)!
|
| 172 |
+
await updateGradioImage(element, url, name + '.png')
|
| 173 |
+
}
|
| 174 |
+
const tabs = gradioApp().querySelector('#openpose3d_main')!
|
| 175 |
+
switchGradioTab(tabs, 1)
|
| 176 |
+
},
|
| 177 |
+
})
|
| 178 |
+
|
| 179 |
+
for (let i = 0; i < 30; ++i) {
|
| 180 |
+
try {
|
| 181 |
+
await InvokeCommand('GetAppVersion')
|
| 182 |
+
isInitialized = true
|
| 183 |
+
break
|
| 184 |
+
} catch (error: any) {
|
| 185 |
+
if (error.status != 'Timeout') {
|
| 186 |
+
throw error
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
if (!isInitialized) {
|
| 191 |
+
console.error('sd-webui-3d-open-pose-editor: Timeout')
|
| 192 |
+
return
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
// await InvokeCommand('OutputWidth', 512)
|
| 196 |
+
// await InvokeCommand('OutputHeight', 512)
|
| 197 |
+
if (!isTabActive()) {
|
| 198 |
+
await InvokeCommand('Pause')
|
| 199 |
+
}
|
| 200 |
+
})
|
| 201 |
+
|
| 202 |
+
onUiUpdate(async () => {
|
| 203 |
+
if (!isInitialized) {
|
| 204 |
+
return
|
| 205 |
+
}
|
| 206 |
+
if (isTabActive()) {
|
| 207 |
+
if (isPaused) {
|
| 208 |
+
isPaused = false
|
| 209 |
+
await InvokeCommand('Resume')
|
| 210 |
+
}
|
| 211 |
+
} else {
|
| 212 |
+
if (!isPaused) {
|
| 213 |
+
isPaused = true
|
| 214 |
+
await InvokeCommand('Pause')
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
+
})
|
sd-webui-3d-open-pose-editor/src/environments/extension/internal/gradio.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const waitForElement = async (
|
| 2 |
+
parent: Element,
|
| 3 |
+
selector: string,
|
| 4 |
+
exist: boolean
|
| 5 |
+
) => {
|
| 6 |
+
return new Promise((resolve) => {
|
| 7 |
+
const observer = new MutationObserver(() => {
|
| 8 |
+
if (!!parent.querySelector(selector) != exist) {
|
| 9 |
+
return
|
| 10 |
+
}
|
| 11 |
+
observer.disconnect()
|
| 12 |
+
resolve(undefined)
|
| 13 |
+
})
|
| 14 |
+
|
| 15 |
+
observer.observe(parent, {
|
| 16 |
+
childList: true,
|
| 17 |
+
subtree: true,
|
| 18 |
+
})
|
| 19 |
+
|
| 20 |
+
if (!!parent.querySelector(selector) == exist) {
|
| 21 |
+
resolve(undefined)
|
| 22 |
+
}
|
| 23 |
+
})
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
const timeout = (ms: number) => {
|
| 27 |
+
return new Promise(function (resolve, reject) {
|
| 28 |
+
setTimeout(() => reject('Timeout'), ms)
|
| 29 |
+
})
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
export const waitForElementToBeInDocument = (
|
| 33 |
+
parent: Element,
|
| 34 |
+
selector: string
|
| 35 |
+
) => Promise.race([waitForElement(parent, selector, true), timeout(10000)])
|
| 36 |
+
export const waitForElementToBeRemoved = (parent: Element, selector: string) =>
|
| 37 |
+
Promise.race([waitForElement(parent, selector, false), timeout(10000)])
|
| 38 |
+
|
| 39 |
+
export const updateGradioImage = async (
|
| 40 |
+
element: Element,
|
| 41 |
+
url: string,
|
| 42 |
+
name: string
|
| 43 |
+
) => {
|
| 44 |
+
const blob = await (await fetch(url)).blob()
|
| 45 |
+
const file = new File([blob], name)
|
| 46 |
+
const dt = new DataTransfer()
|
| 47 |
+
dt.items.add(file)
|
| 48 |
+
|
| 49 |
+
element
|
| 50 |
+
.querySelector<HTMLButtonElement>("button[aria-label='Clear']")
|
| 51 |
+
?.click()
|
| 52 |
+
await waitForElementToBeRemoved(element, "button[aria-label='Clear']")
|
| 53 |
+
const input = element.querySelector<HTMLInputElement>("input[type='file']")!
|
| 54 |
+
input.value = ''
|
| 55 |
+
input.files = dt.files
|
| 56 |
+
input.dispatchEvent(
|
| 57 |
+
new Event('change', {
|
| 58 |
+
bubbles: true,
|
| 59 |
+
composed: true,
|
| 60 |
+
})
|
| 61 |
+
)
|
| 62 |
+
await waitForElementToBeInDocument(element, "button[aria-label='Clear']")
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
export const switchGradioTab = (element: Element, index: number) => {
|
| 66 |
+
element.querySelectorAll('button')[index].click()
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
export const openGradioAccordion = (element: Element) => {
|
| 70 |
+
const labelElem = element.querySelector<HTMLElement>(':scope > .label-wrap')
|
| 71 |
+
if (!labelElem) {
|
| 72 |
+
return
|
| 73 |
+
}
|
| 74 |
+
if (labelElem.classList.contains('open')) {
|
| 75 |
+
return
|
| 76 |
+
}
|
| 77 |
+
labelElem.click()
|
| 78 |
+
}
|
sd-webui-3d-open-pose-editor/src/environments/extension/internal/message.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { IPostMessage as IPostMessageBase } from '../../../hooks/useMessageDispatch'
|
| 2 |
+
|
| 3 |
+
export type IPostMessage = IPostMessageBase & {
|
| 4 |
+
cmd: string
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
const poseMessage = (message: IPostMessage) => {
|
| 8 |
+
const iframe =
|
| 9 |
+
gradioApp().querySelector<HTMLIFrameElement>('#openpose3d_iframe')!
|
| 10 |
+
iframe.contentWindow!.postMessage(message, '*')
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
const MessageReturnHandler: Record<string, (arg: any) => void> = {}
|
| 14 |
+
const MessageEventHandler: Record<string, (arg: any) => void> = {}
|
| 15 |
+
|
| 16 |
+
export const InitMessageListener = () => {
|
| 17 |
+
window.addEventListener('message', (event: MessageEvent<IPostMessage>) => {
|
| 18 |
+
const { data } = event
|
| 19 |
+
if (data && data.cmd && data.cmd == 'openpose-3d' && data.method) {
|
| 20 |
+
const method = data.method
|
| 21 |
+
console.log('Method', method, event)
|
| 22 |
+
if (data.type == 'return') {
|
| 23 |
+
MessageReturnHandler[method]?.(data.payload)
|
| 24 |
+
} else if (data.type == 'event') {
|
| 25 |
+
console.log(MessageEventHandler)
|
| 26 |
+
MessageEventHandler[method]?.(data.payload)
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
})
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
export const AddMessageEventListener = (
|
| 33 |
+
listeners: Record<string, (arg: any) => void>
|
| 34 |
+
) => {
|
| 35 |
+
Object.assign(MessageEventHandler, listeners)
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
export function InvokeCommand(method: string, ...args: any[]) {
|
| 39 |
+
return new Promise((resolve, reject) => {
|
| 40 |
+
const id = setTimeout(() => {
|
| 41 |
+
delete MessageReturnHandler[method]
|
| 42 |
+
|
| 43 |
+
reject({
|
| 44 |
+
method,
|
| 45 |
+
status: 'Timeout',
|
| 46 |
+
})
|
| 47 |
+
}, 1000)
|
| 48 |
+
|
| 49 |
+
const onReutrn = (arg: any) => {
|
| 50 |
+
clearTimeout(id)
|
| 51 |
+
resolve(arg)
|
| 52 |
+
}
|
| 53 |
+
MessageReturnHandler[method] = onReutrn
|
| 54 |
+
|
| 55 |
+
poseMessage({
|
| 56 |
+
cmd: 'openpose-3d',
|
| 57 |
+
method,
|
| 58 |
+
type: 'call',
|
| 59 |
+
payload: args,
|
| 60 |
+
})
|
| 61 |
+
})
|
| 62 |
+
}
|
sd-webui-3d-open-pose-editor/src/environments/online/App.module.css
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.app {
|
| 2 |
+
width: 100vw;
|
| 3 |
+
height: 100vh;
|
| 4 |
+
position: absolute;
|
| 5 |
+
top: 0px;
|
| 6 |
+
left: 0px;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
.threejsCanvas {
|
| 10 |
+
display: block;
|
| 11 |
+
width: 100vw;
|
| 12 |
+
height: 100vh;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.gallery {
|
| 16 |
+
user-select: none;
|
| 17 |
+
height: 140px;
|
| 18 |
+
position: fixed;
|
| 19 |
+
bottom: 50px;
|
| 20 |
+
right: 50px;
|
| 21 |
+
display: flex;
|
| 22 |
+
justify-content: flex-end;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.gallery img {
|
| 26 |
+
width: 100px;
|
| 27 |
+
height: 140px;
|
| 28 |
+
object-fit: contain;
|
| 29 |
+
background-color: gray;
|
| 30 |
+
user-select: none;
|
| 31 |
+
margin-right: 2px;
|
| 32 |
+
margin-left: 2px;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.background {
|
| 36 |
+
width: 100vw;
|
| 37 |
+
height: 100vh;
|
| 38 |
+
background-color: rgb(214, 214, 214);
|
| 39 |
+
background-position: center;
|
| 40 |
+
background-repeat: no-repeat;
|
| 41 |
+
background-size: contain;
|
| 42 |
+
}
|
sd-webui-3d-open-pose-editor/src/environments/online/App.tsx
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
| 2 |
+
import { download } from '../../utils/transfer'
|
| 3 |
+
import classes from './App.module.css'
|
| 4 |
+
import Menu from '../../components/Menu'
|
| 5 |
+
import PopupOver from '../../components/PopupOver'
|
| 6 |
+
import { useBodyEditor } from '../../hooks'
|
| 7 |
+
import {
|
| 8 |
+
LockClosedIcon,
|
| 9 |
+
LockOpen2Icon,
|
| 10 |
+
ResetIcon,
|
| 11 |
+
ResumeIcon,
|
| 12 |
+
} from '@radix-ui/react-icons'
|
| 13 |
+
import { getCurrentTime } from '../../utils/time'
|
| 14 |
+
import useMessageDispatch from '../../hooks/useMessageDispatch'
|
| 15 |
+
|
| 16 |
+
const { app, threejsCanvas, gallery, background } = classes
|
| 17 |
+
|
| 18 |
+
const PreviewCanvas = React.forwardRef<
|
| 19 |
+
HTMLCanvasElement,
|
| 20 |
+
{
|
| 21 |
+
isLock: boolean
|
| 22 |
+
enable: boolean
|
| 23 |
+
onChange: (isLock: boolean) => void
|
| 24 |
+
onRestore: () => void
|
| 25 |
+
onRun: () => void
|
| 26 |
+
}
|
| 27 |
+
>(({ isLock, enable, onChange, onRestore, onRun }, ref) => {
|
| 28 |
+
const Icon = isLock ? LockClosedIcon : LockOpen2Icon
|
| 29 |
+
return (
|
| 30 |
+
<div
|
| 31 |
+
style={{
|
| 32 |
+
position: 'relative',
|
| 33 |
+
display: enable ? 'flex' : 'none',
|
| 34 |
+
justifyContent: 'center',
|
| 35 |
+
backgroundColor: 'gray',
|
| 36 |
+
}}
|
| 37 |
+
>
|
| 38 |
+
<canvas
|
| 39 |
+
ref={ref}
|
| 40 |
+
style={{
|
| 41 |
+
objectFit: 'contain',
|
| 42 |
+
width: 'unset',
|
| 43 |
+
// height:"unset",
|
| 44 |
+
maxHeight: '100%',
|
| 45 |
+
maxWidth: 300,
|
| 46 |
+
}}
|
| 47 |
+
></canvas>
|
| 48 |
+
<ResumeIcon
|
| 49 |
+
style={{
|
| 50 |
+
position: 'absolute',
|
| 51 |
+
top: -10,
|
| 52 |
+
right: 5,
|
| 53 |
+
backgroundColor: 'white',
|
| 54 |
+
borderRadius: 10,
|
| 55 |
+
padding: 5,
|
| 56 |
+
}}
|
| 57 |
+
onClick={() => {
|
| 58 |
+
onRun()
|
| 59 |
+
}}
|
| 60 |
+
></ResumeIcon>
|
| 61 |
+
<Icon
|
| 62 |
+
style={{
|
| 63 |
+
position: 'absolute',
|
| 64 |
+
top: 20,
|
| 65 |
+
right: 5,
|
| 66 |
+
backgroundColor: 'white',
|
| 67 |
+
borderRadius: 10,
|
| 68 |
+
padding: 5,
|
| 69 |
+
}}
|
| 70 |
+
onClick={() => {
|
| 71 |
+
onChange(!isLock)
|
| 72 |
+
}}
|
| 73 |
+
></Icon>
|
| 74 |
+
|
| 75 |
+
<ResetIcon
|
| 76 |
+
style={{
|
| 77 |
+
position: 'absolute',
|
| 78 |
+
top: 50,
|
| 79 |
+
right: 5,
|
| 80 |
+
backgroundColor: !isLock ? 'gray' : 'white',
|
| 81 |
+
borderRadius: 10,
|
| 82 |
+
padding: 5,
|
| 83 |
+
}}
|
| 84 |
+
onClick={() => {
|
| 85 |
+
if (isLock) onRestore()
|
| 86 |
+
}}
|
| 87 |
+
></ResetIcon>
|
| 88 |
+
</div>
|
| 89 |
+
)
|
| 90 |
+
})
|
| 91 |
+
|
| 92 |
+
function App() {
|
| 93 |
+
const canvasRef = useRef(null)
|
| 94 |
+
const previewCanvasRef = useRef(null)
|
| 95 |
+
|
| 96 |
+
const backgroundRef = useRef<HTMLDivElement>(null)
|
| 97 |
+
const editor = useBodyEditor(canvasRef, previewCanvasRef, backgroundRef)
|
| 98 |
+
const [imageData, setImageData] = useState<
|
| 99 |
+
Record<string, { title: string; src: string }>
|
| 100 |
+
>(() => ({
|
| 101 |
+
pose: {
|
| 102 |
+
title: '',
|
| 103 |
+
src: '',
|
| 104 |
+
},
|
| 105 |
+
depth: {
|
| 106 |
+
title: '',
|
| 107 |
+
src: '',
|
| 108 |
+
},
|
| 109 |
+
normal: {
|
| 110 |
+
title: '',
|
| 111 |
+
src: '',
|
| 112 |
+
},
|
| 113 |
+
canny: {
|
| 114 |
+
title: '',
|
| 115 |
+
src: '',
|
| 116 |
+
},
|
| 117 |
+
}))
|
| 118 |
+
|
| 119 |
+
const onChangeBackground = useCallback((url: string) => {
|
| 120 |
+
const div = backgroundRef.current
|
| 121 |
+
if (div) {
|
| 122 |
+
div.style.backgroundImage = url ? `url(${url})` : 'none'
|
| 123 |
+
}
|
| 124 |
+
}, [])
|
| 125 |
+
|
| 126 |
+
const onScreenShot = useCallback(
|
| 127 |
+
(data: Record<string, { src: string; title: string }>) => {
|
| 128 |
+
setImageData(data)
|
| 129 |
+
},
|
| 130 |
+
[]
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
const [preview, setPreivew] = useState(false)
|
| 134 |
+
const [lockView, setLockView] = useState(false)
|
| 135 |
+
|
| 136 |
+
useEffect(() => {
|
| 137 |
+
const preview = (enable: boolean) => {
|
| 138 |
+
setPreivew(enable)
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
const lcokView = (value: boolean) => {
|
| 142 |
+
setLockView(value)
|
| 143 |
+
}
|
| 144 |
+
editor?.PreviewEventManager.AddEventListener(preview)
|
| 145 |
+
editor?.LockViewEventManager.AddEventListener(lcokView)
|
| 146 |
+
|
| 147 |
+
return () => {
|
| 148 |
+
editor?.PreviewEventManager.RemoveEventListener(preview)
|
| 149 |
+
editor?.LockViewEventManager.RemoveEventListener(lcokView)
|
| 150 |
+
}
|
| 151 |
+
}, [editor])
|
| 152 |
+
|
| 153 |
+
useMessageDispatch({
|
| 154 |
+
GetAppVersion: () => __APP_VERSION__,
|
| 155 |
+
MakeImages: () => editor?.MakeImages(),
|
| 156 |
+
Pause: () => editor?.pause(),
|
| 157 |
+
Resume: () => editor?.resume(),
|
| 158 |
+
OutputWidth: (value: number) => {
|
| 159 |
+
if (editor && typeof value === 'number') {
|
| 160 |
+
editor.OutputWidth = value
|
| 161 |
+
return true
|
| 162 |
+
} else return false
|
| 163 |
+
},
|
| 164 |
+
OutputHeight: (value: number) => {
|
| 165 |
+
if (editor && typeof value === 'number') {
|
| 166 |
+
editor.OutputHeight = value
|
| 167 |
+
return true
|
| 168 |
+
} else return false
|
| 169 |
+
},
|
| 170 |
+
OnlyHand(value: boolean) {
|
| 171 |
+
if (editor && typeof value === 'boolean') {
|
| 172 |
+
editor.OnlyHand = value
|
| 173 |
+
return true
|
| 174 |
+
} else return false
|
| 175 |
+
},
|
| 176 |
+
MoveMode(value: boolean) {
|
| 177 |
+
if (editor && typeof value === 'boolean') {
|
| 178 |
+
editor.MoveMode = value
|
| 179 |
+
return true
|
| 180 |
+
} else return false
|
| 181 |
+
},
|
| 182 |
+
GetWidth: () => editor?.Width,
|
| 183 |
+
GetHeight: () => editor?.Height,
|
| 184 |
+
GetSceneData: () => editor?.GetSceneData(),
|
| 185 |
+
LockView: () => editor?.LockView(),
|
| 186 |
+
UnlockView: () => editor?.UnlockView(),
|
| 187 |
+
RestoreView: () => editor?.RestoreView(),
|
| 188 |
+
})
|
| 189 |
+
|
| 190 |
+
return (
|
| 191 |
+
<div ref={backgroundRef} className={background}>
|
| 192 |
+
<canvas
|
| 193 |
+
className={threejsCanvas}
|
| 194 |
+
tabIndex={-1}
|
| 195 |
+
ref={canvasRef}
|
| 196 |
+
onContextMenu={(e) => {
|
| 197 |
+
e.preventDefault()
|
| 198 |
+
}}
|
| 199 |
+
></canvas>
|
| 200 |
+
<div className={gallery}>
|
| 201 |
+
<PreviewCanvas
|
| 202 |
+
enable={preview}
|
| 203 |
+
ref={previewCanvasRef}
|
| 204 |
+
isLock={lockView}
|
| 205 |
+
onChange={(isLock) => {
|
| 206 |
+
if (isLock) {
|
| 207 |
+
editor?.LockView()
|
| 208 |
+
} else {
|
| 209 |
+
editor?.UnlockView()
|
| 210 |
+
}
|
| 211 |
+
}}
|
| 212 |
+
onRestore={() => {
|
| 213 |
+
editor?.RestoreView()
|
| 214 |
+
}}
|
| 215 |
+
onRun={async () => {
|
| 216 |
+
if (!editor) return
|
| 217 |
+
const image = editor.MakeImages()
|
| 218 |
+
const result = Object.fromEntries(
|
| 219 |
+
Object.entries(image).map(([name, imgData]) => [
|
| 220 |
+
name,
|
| 221 |
+
{
|
| 222 |
+
src: imgData,
|
| 223 |
+
title: name + '_' + getCurrentTime(),
|
| 224 |
+
},
|
| 225 |
+
])
|
| 226 |
+
)
|
| 227 |
+
onScreenShot(result)
|
| 228 |
+
}}
|
| 229 |
+
></PreviewCanvas>
|
| 230 |
+
|
| 231 |
+
{Object.entries(imageData).map(([name, { src, title }]) => (
|
| 232 |
+
<img
|
| 233 |
+
key={name}
|
| 234 |
+
// avoid show error image
|
| 235 |
+
{...(src ? { src } : {})}
|
| 236 |
+
title={title}
|
| 237 |
+
onClick={(e) => {
|
| 238 |
+
const image = e.target as HTMLImageElement
|
| 239 |
+
const title = image?.getAttribute('title') ?? ''
|
| 240 |
+
const url = image?.getAttribute('src') ?? ''
|
| 241 |
+
download(url, title)
|
| 242 |
+
}}
|
| 243 |
+
></img>
|
| 244 |
+
))}
|
| 245 |
+
</div>
|
| 246 |
+
<div
|
| 247 |
+
className={app}
|
| 248 |
+
style={{
|
| 249 |
+
pointerEvents: 'none',
|
| 250 |
+
}}
|
| 251 |
+
>
|
| 252 |
+
<div
|
| 253 |
+
style={{
|
| 254 |
+
pointerEvents: 'initial',
|
| 255 |
+
marginTop: 10,
|
| 256 |
+
display: 'flex',
|
| 257 |
+
justifyContent: 'center',
|
| 258 |
+
}}
|
| 259 |
+
>
|
| 260 |
+
{editor ? (
|
| 261 |
+
<Menu
|
| 262 |
+
editor={editor}
|
| 263 |
+
onChangeBackground={onChangeBackground}
|
| 264 |
+
onScreenShot={onScreenShot}
|
| 265 |
+
/>
|
| 266 |
+
) : undefined}
|
| 267 |
+
</div>
|
| 268 |
+
</div>
|
| 269 |
+
{editor ? (
|
| 270 |
+
<PopupOver
|
| 271 |
+
editor={editor}
|
| 272 |
+
style={{
|
| 273 |
+
pointerEvents: 'initial',
|
| 274 |
+
position: 'fixed',
|
| 275 |
+
top: 10,
|
| 276 |
+
right: 10,
|
| 277 |
+
}}
|
| 278 |
+
></PopupOver>
|
| 279 |
+
) : undefined}
|
| 280 |
+
</div>
|
| 281 |
+
)
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
export default App
|
sd-webui-3d-open-pose-editor/src/environments/online/helper.ts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getImage } from '../../utils/image'
|
| 2 |
+
import {
|
| 3 |
+
CopyTextToClipboard,
|
| 4 |
+
uploadImage,
|
| 5 |
+
uploadJson,
|
| 6 |
+
} from '../../utils/transfer'
|
| 7 |
+
import { DetectPosefromImage } from '../../utils/detect'
|
| 8 |
+
|
| 9 |
+
import { BodyControlor } from '../../body'
|
| 10 |
+
|
| 11 |
+
import { GetLoading } from '../../components/Loading'
|
| 12 |
+
import { BodyEditor } from '../../editor'
|
| 13 |
+
import i18n from '../../i18n'
|
| 14 |
+
import { Oops } from '../../components/Oops'
|
| 15 |
+
import assets from '../../assets'
|
| 16 |
+
import { ShowToast } from '../../components/Toast'
|
| 17 |
+
import { GetRandomPose, LoadPosesLibrary } from '../../pose-library'
|
| 18 |
+
|
| 19 |
+
export class Helper {
|
| 20 |
+
editor: BodyEditor
|
| 21 |
+
constructor(editor: BodyEditor) {
|
| 22 |
+
this.editor = editor
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
async DetectFromImage(onChangeBackground: (url: string) => void) {
|
| 26 |
+
const body = await this.editor.GetBodyToSetPose()
|
| 27 |
+
if (!body) {
|
| 28 |
+
ShowToast({ title: i18n.t('Please select a skeleton!!') })
|
| 29 |
+
return
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
const loading = GetLoading(500)
|
| 33 |
+
|
| 34 |
+
try {
|
| 35 |
+
const dataUrl = await uploadImage()
|
| 36 |
+
|
| 37 |
+
if (!dataUrl) return
|
| 38 |
+
|
| 39 |
+
const image = await getImage(dataUrl)
|
| 40 |
+
onChangeBackground(dataUrl)
|
| 41 |
+
|
| 42 |
+
loading.show({ title: i18n.t('Downloading MediaPipe Pose Model') })
|
| 43 |
+
const result = await DetectPosefromImage(image)
|
| 44 |
+
loading.hide()
|
| 45 |
+
|
| 46 |
+
if (result) {
|
| 47 |
+
if (!result.poseWorldLandmarks)
|
| 48 |
+
throw new Error(JSON.stringify(result))
|
| 49 |
+
|
| 50 |
+
const positions: [number, number, number][] =
|
| 51 |
+
result.poseWorldLandmarks.map(({ x, y, z }) => [
|
| 52 |
+
x * 100,
|
| 53 |
+
-y * 100,
|
| 54 |
+
-z * 100,
|
| 55 |
+
])
|
| 56 |
+
|
| 57 |
+
// this.drawPoseData(
|
| 58 |
+
// result.poseWorldLandmarks.map(({ x, y, z }) =>
|
| 59 |
+
// new THREE.Vector3().fromArray([x * 100, -y * 100, -z * 100])
|
| 60 |
+
// )
|
| 61 |
+
// )
|
| 62 |
+
|
| 63 |
+
await this.editor.SetBlazePose(positions)
|
| 64 |
+
return
|
| 65 |
+
}
|
| 66 |
+
} catch (error) {
|
| 67 |
+
loading.hide()
|
| 68 |
+
|
| 69 |
+
Oops(
|
| 70 |
+
i18n.t(
|
| 71 |
+
'If you try to detect anime characters, you may get an error. Please try again with photos.'
|
| 72 |
+
) +
|
| 73 |
+
'\n' +
|
| 74 |
+
error
|
| 75 |
+
)
|
| 76 |
+
console.error(error)
|
| 77 |
+
return null
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
async CopyKeypointToClipboard() {
|
| 82 |
+
const body = await this.editor.GetBodyToSetPose()
|
| 83 |
+
if (!body) {
|
| 84 |
+
ShowToast({ title: i18n.t('Please select a skeleton!!') })
|
| 85 |
+
return
|
| 86 |
+
}
|
| 87 |
+
try {
|
| 88 |
+
const data = new BodyControlor(body).Get18keyPointsData()
|
| 89 |
+
await CopyTextToClipboard(JSON.stringify(data, null, 4))
|
| 90 |
+
ShowToast({ title: i18n.t('Copied to Clipboard') })
|
| 91 |
+
} catch (error) {
|
| 92 |
+
Oops(error)
|
| 93 |
+
console.error(error)
|
| 94 |
+
return null
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
async SaveGesture() {
|
| 99 |
+
const hand = await this.editor.getSelectedHand()
|
| 100 |
+
if (!hand) {
|
| 101 |
+
ShowToast({ title: i18n.t('Please select a hand!!') })
|
| 102 |
+
return
|
| 103 |
+
}
|
| 104 |
+
try {
|
| 105 |
+
this.editor.SaveGesture()
|
| 106 |
+
} catch (error) {
|
| 107 |
+
Oops(error)
|
| 108 |
+
console.error(error)
|
| 109 |
+
return null
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
async LoadGesture() {
|
| 114 |
+
const hand = await this.editor.getSelectedHand()
|
| 115 |
+
|
| 116 |
+
if (!hand) {
|
| 117 |
+
ShowToast({ title: i18n.t('Please select a hand!!') })
|
| 118 |
+
return
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
const rawData = await uploadJson()
|
| 122 |
+
if (!rawData) return
|
| 123 |
+
|
| 124 |
+
try {
|
| 125 |
+
this.editor.RestoreGesture(rawData)
|
| 126 |
+
} catch (error) {
|
| 127 |
+
Oops(error)
|
| 128 |
+
console.error(error)
|
| 129 |
+
return null
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
async GenerateSceneURL() {
|
| 134 |
+
try {
|
| 135 |
+
const d = encodeURIComponent(
|
| 136 |
+
JSON.stringify(this.editor.GetSceneData())
|
| 137 |
+
)
|
| 138 |
+
const url_base = location.href.replace(/#$/, '')
|
| 139 |
+
const url = `${url_base}#${d}`
|
| 140 |
+
await CopyTextToClipboard(url)
|
| 141 |
+
ShowToast({ title: i18n.t('Copied to Clipboard') })
|
| 142 |
+
} catch (error) {
|
| 143 |
+
Oops(error)
|
| 144 |
+
console.error(error)
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
async SetRandomPose() {
|
| 149 |
+
const body = await this.editor.GetBodyToSetPose()
|
| 150 |
+
if (!body) {
|
| 151 |
+
ShowToast({ title: i18n.t('Please select a skeleton!!') })
|
| 152 |
+
return
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
const loading = GetLoading(500)
|
| 156 |
+
|
| 157 |
+
try {
|
| 158 |
+
let poseData = GetRandomPose()
|
| 159 |
+
if (poseData) {
|
| 160 |
+
await this.editor.SetPose(poseData)
|
| 161 |
+
return
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
loading.show({ title: i18n.t('Downloading Poses Library') })
|
| 165 |
+
|
| 166 |
+
await LoadPosesLibrary(assets['src/poses/data.bin'])
|
| 167 |
+
loading.hide()
|
| 168 |
+
|
| 169 |
+
poseData = GetRandomPose()
|
| 170 |
+
if (poseData) {
|
| 171 |
+
await this.editor.SetPose(poseData)
|
| 172 |
+
return
|
| 173 |
+
}
|
| 174 |
+
} catch (error) {
|
| 175 |
+
loading.hide()
|
| 176 |
+
|
| 177 |
+
Oops(error)
|
| 178 |
+
console.error(error)
|
| 179 |
+
return
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
async CopySkeleton() {
|
| 183 |
+
const body = this.editor.getSelectedBody()
|
| 184 |
+
if (!body) {
|
| 185 |
+
ShowToast({ title: i18n.t('Please select a skeleton!!') })
|
| 186 |
+
return
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
this.editor.CopySelectedBody()
|
| 190 |
+
}
|
| 191 |
+
async RemoveSkeleton() {
|
| 192 |
+
const body = this.editor.getSelectedBody()
|
| 193 |
+
if (!body) {
|
| 194 |
+
ShowToast({ title: i18n.t('Please select a skeleton!!') })
|
| 195 |
+
return
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
this.editor.RemoveBody()
|
| 199 |
+
}
|
| 200 |
+
FeedbackByQQ() {
|
| 201 |
+
window.open('https://jq.qq.com/?_wv=1027&k=N6j4nigd')
|
| 202 |
+
}
|
| 203 |
+
FeedbackByGithub() {
|
| 204 |
+
window.open(
|
| 205 |
+
'https://github.com/nonnonstop/sd-webui-3d-open-pose-editor/issues/new/choose'
|
| 206 |
+
)
|
| 207 |
+
}
|
| 208 |
+
}
|
sd-webui-3d-open-pose-editor/src/environments/online/index.css
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--openpose-editor-font-family: -apple-system, BlinkMacSystemFont, Tahoma,
|
| 3 |
+
Arial, 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
* {
|
| 7 |
+
margin: 0;
|
| 8 |
+
padding: 0;
|
| 9 |
+
outline: none;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
body,
|
| 13 |
+
#root {
|
| 14 |
+
width: 100vw;
|
| 15 |
+
height: 100vh;
|
| 16 |
+
position: relative;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
#root * {
|
| 20 |
+
font-family: var(--openpose-editor-font-family);
|
| 21 |
+
}
|
sd-webui-3d-open-pose-editor/src/environments/online/init.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import dayjs from 'dayjs'
|
| 2 |
+
console.log(
|
| 3 |
+
__APP_VERSION__ +
|
| 4 |
+
' ' +
|
| 5 |
+
dayjs(__APP_BUILD_TIME__).format('YYYY-MM-DD HH:mm:ss')
|
| 6 |
+
)
|
| 7 |
+
import { PWACheck } from './update'
|
| 8 |
+
PWACheck()
|
sd-webui-3d-open-pose-editor/src/environments/online/main.tsx
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import './init'
|
| 2 |
+
import './index.css'
|
| 3 |
+
|
| 4 |
+
import React from 'react'
|
| 5 |
+
import ReactDOM from 'react-dom/client'
|
| 6 |
+
import NiceModal from '@ebay/nice-modal-react'
|
| 7 |
+
|
| 8 |
+
import App from './App'
|
| 9 |
+
|
| 10 |
+
export function Main() {
|
| 11 |
+
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
| 12 |
+
<React.StrictMode>
|
| 13 |
+
<NiceModal.Provider>
|
| 14 |
+
<App />
|
| 15 |
+
</NiceModal.Provider>
|
| 16 |
+
</React.StrictMode>
|
| 17 |
+
)
|
| 18 |
+
}
|
sd-webui-3d-open-pose-editor/src/environments/online/update.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// <reference types="vite-plugin-pwa/client" />
|
| 2 |
+
|
| 3 |
+
// #v-ifdef VITE_IS_ONLINE
|
| 4 |
+
import { registerSW } from 'virtual:pwa-register'
|
| 5 |
+
// #v-endif
|
| 6 |
+
|
| 7 |
+
import { ShowDialog } from '../../components/Dialog'
|
| 8 |
+
import i18n from '../../i18n'
|
| 9 |
+
|
| 10 |
+
async function PWAPopup(update: (reloadPage?: boolean) => Promise<void>) {
|
| 11 |
+
const result = await ShowDialog({
|
| 12 |
+
title: i18n.t('Updates are available, please confirm!!') ?? '',
|
| 13 |
+
button: i18n.t('Update') ?? '',
|
| 14 |
+
})
|
| 15 |
+
|
| 16 |
+
if (result === 'action') {
|
| 17 |
+
update(true)
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
export function PWACheck() {
|
| 21 |
+
if (import.meta.env.MODE !== 'online') return
|
| 22 |
+
const updateSW = registerSW({
|
| 23 |
+
onNeedRefresh() {
|
| 24 |
+
console.log('有更新,需要刷新!!')
|
| 25 |
+
PWAPopup(updateSW)
|
| 26 |
+
},
|
| 27 |
+
onOfflineReady() {
|
| 28 |
+
console.log('已经入离线模式!!')
|
| 29 |
+
},
|
| 30 |
+
})
|
| 31 |
+
}
|
sd-webui-3d-open-pose-editor/src/hooks/index.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { RefObject, useEffect, useState } from 'react'
|
| 2 |
+
import assets from '../assets'
|
| 3 |
+
import { CreateTemplateBody } from '../body'
|
| 4 |
+
import { GetLoading } from '../components/Loading'
|
| 5 |
+
import { BodyEditor } from '../editor'
|
| 6 |
+
import i18n, { LanguageMapping } from '../i18n'
|
| 7 |
+
import { LoadFoot, LoadHand } from '../models'
|
| 8 |
+
|
| 9 |
+
export async function LoadBodyData() {
|
| 10 |
+
const loading = GetLoading(500)
|
| 11 |
+
loading.show({ title: i18n.t('Downloading Hand Model') })
|
| 12 |
+
await LoadHand(assets['models/hand.fbx'])
|
| 13 |
+
loading.show({ title: i18n.t('Downloading Foot Model') })
|
| 14 |
+
await LoadFoot(assets['models/foot.fbx'])
|
| 15 |
+
loading.hide()
|
| 16 |
+
CreateTemplateBody()
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
export function useBodyEditor(
|
| 20 |
+
canvasRef: RefObject<HTMLCanvasElement>,
|
| 21 |
+
previewCanvasRef: RefObject<HTMLCanvasElement>,
|
| 22 |
+
parent?: RefObject<HTMLDivElement>
|
| 23 |
+
) {
|
| 24 |
+
const [editor, setEditor] = useState<BodyEditor>()
|
| 25 |
+
|
| 26 |
+
useEffect(() => {
|
| 27 |
+
const canvas = canvasRef.current
|
| 28 |
+
if (!canvas) return
|
| 29 |
+
const previewCanvas = previewCanvasRef.current
|
| 30 |
+
if (!previewCanvas) return
|
| 31 |
+
console.warn('create editor')
|
| 32 |
+
|
| 33 |
+
let editor: BodyEditor | null = new BodyEditor({
|
| 34 |
+
canvas,
|
| 35 |
+
previewCanvas,
|
| 36 |
+
parentElem: parent?.current ?? (document as any),
|
| 37 |
+
statsElem: import.meta.env.DEV ? document.body : undefined,
|
| 38 |
+
})
|
| 39 |
+
|
| 40 |
+
setEditor(editor)
|
| 41 |
+
|
| 42 |
+
const init = async () => {
|
| 43 |
+
// StrictMode will render twice
|
| 44 |
+
// we have to check if the editor is null to avoid meaningless operations
|
| 45 |
+
if (editor) {
|
| 46 |
+
await LoadBodyData()
|
| 47 |
+
editor?.ResetScene()
|
| 48 |
+
if (editor?.RestoreScene && location.hash) {
|
| 49 |
+
const rawData = decodeURIComponent(
|
| 50 |
+
location.hash.replace(/^#/, '')
|
| 51 |
+
)
|
| 52 |
+
editor?.RestoreScene(rawData)
|
| 53 |
+
location.hash = ''
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
init()
|
| 58 |
+
|
| 59 |
+
return () => {
|
| 60 |
+
console.warn('disponse')
|
| 61 |
+
editor?.disponse()
|
| 62 |
+
editor = null
|
| 63 |
+
}
|
| 64 |
+
}, [])
|
| 65 |
+
|
| 66 |
+
return editor
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
export function useLanguageSelect() {
|
| 70 |
+
const [current] = useState(
|
| 71 |
+
() => LanguageMapping[i18n.language] ?? 'English'
|
| 72 |
+
)
|
| 73 |
+
const [R_LanguageMapping] = useState(() =>
|
| 74 |
+
Object.fromEntries(
|
| 75 |
+
Object.entries(LanguageMapping).map(([k, v]) => [v, k])
|
| 76 |
+
)
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
return {
|
| 80 |
+
current,
|
| 81 |
+
languagList: [...Object.values(LanguageMapping)],
|
| 82 |
+
changeLanguage: (value: string) => {
|
| 83 |
+
const lng = R_LanguageMapping[value]
|
| 84 |
+
const url = new URL(window.location.href)
|
| 85 |
+
|
| 86 |
+
url.searchParams.set('lng', lng)
|
| 87 |
+
window.location.assign(url)
|
| 88 |
+
},
|
| 89 |
+
}
|
| 90 |
+
}
|
sd-webui-3d-open-pose-editor/src/hooks/useEventCall.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// https://github.com/Volune/use-event-callback/blob/master/src/index.ts
|
| 2 |
+
|
| 3 |
+
import { useLayoutEffect, useMemo, useRef } from 'react'
|
| 4 |
+
|
| 5 |
+
type Fn<ARGS extends any[], R> = (...args: ARGS) => R
|
| 6 |
+
|
| 7 |
+
const useEventCallback = <A extends any[], R>(fn: Fn<A, R>): Fn<A, R> => {
|
| 8 |
+
const ref = useRef<Fn<A, R>>(fn)
|
| 9 |
+
useLayoutEffect(() => {
|
| 10 |
+
ref.current = fn
|
| 11 |
+
})
|
| 12 |
+
return useMemo(
|
| 13 |
+
() =>
|
| 14 |
+
(...args: A): R => {
|
| 15 |
+
const { current } = ref
|
| 16 |
+
return current(...args)
|
| 17 |
+
},
|
| 18 |
+
[]
|
| 19 |
+
)
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
export default useEventCallback
|
sd-webui-3d-open-pose-editor/src/hooks/useFoceUpdate.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useCallback, useState } from 'react'
|
| 2 |
+
|
| 3 |
+
const createNewObject = (): Record<string, never> => ({})
|
| 4 |
+
|
| 5 |
+
export default function useForceUpdate(): VoidFunction {
|
| 6 |
+
const [, setValue] = useState<Record<string, never>>(createNewObject)
|
| 7 |
+
|
| 8 |
+
return useCallback((): void => {
|
| 9 |
+
setValue(createNewObject())
|
| 10 |
+
}, [])
|
| 11 |
+
}
|
sd-webui-3d-open-pose-editor/src/hooks/useMessageDispatch.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// https://github.com/rottitime/react-hook-window-message-event/blob/main/src/useMessage.ts
|
| 2 |
+
|
| 3 |
+
import { useEffect, useRef } from 'react'
|
| 4 |
+
import useEventCallback from './useEventCall'
|
| 5 |
+
|
| 6 |
+
let SEND_TO_SENDER = false // Avoid duplicate event
|
| 7 |
+
|
| 8 |
+
export type IPostMessage = {
|
| 9 |
+
method: string
|
| 10 |
+
type: 'call' | 'return' | 'event'
|
| 11 |
+
payload: any
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
export type EventHandler = (
|
| 15 |
+
callback: (data: IPostMessage) => unknown,
|
| 16 |
+
payload: IPostMessage['payload']
|
| 17 |
+
) => void
|
| 18 |
+
|
| 19 |
+
const postMessage = (
|
| 20 |
+
data: IPostMessage & {
|
| 21 |
+
cmd?: string // Just avoid webui exception
|
| 22 |
+
},
|
| 23 |
+
target: MessageEvent['source'],
|
| 24 |
+
origin = '*'
|
| 25 |
+
) => {
|
| 26 |
+
console.log('return', { target, origin, data })
|
| 27 |
+
target?.postMessage(
|
| 28 |
+
{ cmd: 'openpose-3d', ...data },
|
| 29 |
+
{ targetOrigin: origin }
|
| 30 |
+
)
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
export const sendToParent = (data: IPostMessage) => {
|
| 34 |
+
if (SEND_TO_SENDER) return
|
| 35 |
+
const { parent } = window
|
| 36 |
+
if (!parent) throw new Error('Parent window has closed')
|
| 37 |
+
postMessage(data, parent)
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
export const sendToParentDirectly = (data: IPostMessage) => {
|
| 41 |
+
const { parent } = window
|
| 42 |
+
if (!parent) throw new Error('Parent window has closed')
|
| 43 |
+
postMessage(data, parent)
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
export const sendToOpener = (data: IPostMessage) => {
|
| 47 |
+
const { opener } = window
|
| 48 |
+
if (!opener) throw new Error('Opener window has closed')
|
| 49 |
+
postMessage(data, opener)
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
export const sendToAll = (data: IPostMessage) => {
|
| 53 |
+
if (SEND_TO_SENDER) return
|
| 54 |
+
const { opener, parent } = window
|
| 55 |
+
if (!opener && !parent) {
|
| 56 |
+
throw new Error('window has closed')
|
| 57 |
+
}
|
| 58 |
+
if (parent) sendToParent(data)
|
| 59 |
+
if (opener) sendToOpener(data)
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
export default function useMessageDispatch(
|
| 63 |
+
dispatch: Record<string, (...args: any[]) => any>
|
| 64 |
+
) {
|
| 65 |
+
const originRef = useRef<string>()
|
| 66 |
+
const sourceRef = useRef<MessageEvent['source']>(null)
|
| 67 |
+
|
| 68 |
+
originRef.current = ''
|
| 69 |
+
sourceRef.current = null as MessageEvent['source']
|
| 70 |
+
|
| 71 |
+
const sendToSender = (data: IPostMessage) =>
|
| 72 |
+
postMessage(data, sourceRef.current, originRef.current)
|
| 73 |
+
|
| 74 |
+
const onWatchEventHandler = useEventCallback(
|
| 75 |
+
// tslint:disable-next-line: no-shadowed-variable
|
| 76 |
+
async ({ origin, source, data }: MessageEvent) => {
|
| 77 |
+
if (!data) return
|
| 78 |
+
|
| 79 |
+
const { method, payload, type } = data as IPostMessage
|
| 80 |
+
// It is invalid message, not from webui extension.
|
| 81 |
+
if (type != 'call') return
|
| 82 |
+
|
| 83 |
+
console.log('method', method, payload)
|
| 84 |
+
|
| 85 |
+
if (payload && Array.isArray(payload) === false) {
|
| 86 |
+
console.error('payload is not array')
|
| 87 |
+
return
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
sourceRef.current = source
|
| 91 |
+
originRef.current = origin
|
| 92 |
+
|
| 93 |
+
if (method in dispatch) {
|
| 94 |
+
const eventHandler = dispatch[method]
|
| 95 |
+
if (typeof eventHandler === 'function') {
|
| 96 |
+
SEND_TO_SENDER = true
|
| 97 |
+
const ret = eventHandler(...(payload ?? []))
|
| 98 |
+
const value = ret instanceof Promise ? await ret : ret
|
| 99 |
+
try {
|
| 100 |
+
sendToSender({
|
| 101 |
+
method,
|
| 102 |
+
type: 'return',
|
| 103 |
+
payload: value,
|
| 104 |
+
})
|
| 105 |
+
} catch (error) {
|
| 106 |
+
console.log(error)
|
| 107 |
+
}
|
| 108 |
+
SEND_TO_SENDER = false
|
| 109 |
+
}
|
| 110 |
+
} else if (method === 'GetAPIs') {
|
| 111 |
+
sendToSender({
|
| 112 |
+
method,
|
| 113 |
+
type: 'return',
|
| 114 |
+
payload: Object.keys(dispatch),
|
| 115 |
+
})
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
useEffect(() => {
|
| 121 |
+
window.addEventListener('message', onWatchEventHandler)
|
| 122 |
+
return () => window.removeEventListener('message', onWatchEventHandler)
|
| 123 |
+
}, [onWatchEventHandler])
|
| 124 |
+
}
|