Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f373433dca | ||
|
|
c65b730020 | ||
|
|
2ec7144af4 | ||
|
|
886cc41f1c | ||
|
|
14de4af7fe | ||
|
|
a55cbec6b4 | ||
|
|
6e696359ab | ||
|
|
d6f1c82c07 | ||
|
|
3122d22e32 |
@@ -1,6 +1,6 @@
|
||||
import platform
|
||||
import blender_node
|
||||
from mesh_utils import assign_texture, open_in_blender as open_blender, export_gltf
|
||||
from mesh_utils import upload_avatar_file, open_in_blender as open_blender, export_gltf
|
||||
import folder_paths
|
||||
|
||||
global_blender_path = ''
|
||||
@@ -47,14 +47,16 @@ class AvatarMainOutput(blender_node.ObjectOps):
|
||||
}),
|
||||
"model_type": (["AVA","GLB", "GLTF_EMBEDDED"],),
|
||||
"write_mode": (["Overwrite", "Increment"],),
|
||||
|
||||
"upload_to_cloud": ("BOOLEAN", {
|
||||
"default": False
|
||||
}),
|
||||
"SHAPE_FLOW": ("SHAPE_FLOW",),
|
||||
}
|
||||
|
||||
OUTPUT_NODE = True
|
||||
RETURN_TYPES = ()
|
||||
|
||||
def blender_process(self, bpy, BPY_OBJ=None, BPY_OBJS=None, open_in_blender=False, auto_save=False, blender_path_override='', filename='', model_type='', write_mode='', SHAPE_FLOW=''):
|
||||
def blender_process(self, bpy, BPY_OBJ=None, BPY_OBJS=None, open_in_blender=False, auto_save=False, blender_path_override='', filename='', model_type='', write_mode='', upload_to_cloud=False, SHAPE_FLOW=''):
|
||||
if open_in_blender:
|
||||
p = blender_path_override if blender_path_override else global_blender_path
|
||||
output_file = self.output_dir + '/tmp.blend'
|
||||
@@ -72,4 +74,23 @@ class AvatarMainOutput(blender_node.ObjectOps):
|
||||
import global_bpy
|
||||
global_bpy.set_should_reset_scene(True)
|
||||
|
||||
return {"ui": {"gltfFilename": {filepath.replace(f"{self.output_dir}/", "")}, "SHAPE_FLOW": {SHAPE_FLOW}, "auto_save": {'true' if auto_save else 'false'},}}
|
||||
outputs = {
|
||||
"gltfFilename": [filepath.replace(f"{self.output_dir}/", "")],
|
||||
"files": [{
|
||||
"filename": filepath.replace(f"{self.output_dir}/", ""),
|
||||
"content_type": "model/gltf+json",
|
||||
},],
|
||||
"SHAPE_FLOW": {SHAPE_FLOW},
|
||||
"auto_save": {'true' if auto_save else 'false'},
|
||||
}
|
||||
if upload_to_cloud:
|
||||
avatarId = upload_avatar_file(outputs)
|
||||
return {
|
||||
"ui": {
|
||||
**outputs,
|
||||
"avatarId": [avatarId],
|
||||
},
|
||||
}
|
||||
return {
|
||||
"ui": outputs
|
||||
}
|
||||
|
||||
@@ -260,7 +260,8 @@ def create_primitive_shape_class(cls, path, name=None, name_prefix=''):
|
||||
|
||||
|
||||
def assign_and_return(BPY_OBJ, name, value):
|
||||
BPY_OBJ[name] = value
|
||||
setattr(BPY_OBJ, name, value)
|
||||
# BPY_OBJ[name] = value
|
||||
# print(BPY_OBJ,name, BPY_OBJ[name])
|
||||
return None
|
||||
|
||||
|
||||
+39
-1
@@ -1,6 +1,9 @@
|
||||
import atexit
|
||||
import subprocess
|
||||
import os
|
||||
import folder_paths
|
||||
import requests
|
||||
import json
|
||||
|
||||
def genreate_mesh_from_texture(bpy, image):
|
||||
import torch
|
||||
@@ -245,4 +248,39 @@ def export_gltf(output_dir, bpy_objects, filename, model_type, write_mode, metad
|
||||
os.replace(filepath, new_filepath)
|
||||
filepath = new_filepath
|
||||
|
||||
return filepath
|
||||
return filepath
|
||||
|
||||
def get_avatar_file(output):
|
||||
avatar_filename = output["gltfFilename"][0]
|
||||
with open(
|
||||
f"{folder_paths.get_output_directory()}/{avatar_filename}", "rb"
|
||||
) as f:
|
||||
return f.read()
|
||||
|
||||
def upload_avatar_file(output):
|
||||
file = get_avatar_file(output)
|
||||
response = requests.get("https://labs.avatech.ai/api/share")
|
||||
labData = response.json()
|
||||
modelId = labData["modelId"]
|
||||
|
||||
# upload model
|
||||
headers = {
|
||||
"x-amz-acl": "public-read",
|
||||
"Content-Type": "model/gltf-binary",
|
||||
"Content-Length": str(len(file)),
|
||||
}
|
||||
requests.put(labData["url"], headers=headers, data=file)
|
||||
|
||||
# send notification
|
||||
webhook_url = os.getenv("DISCORD_WEBHOOK_URL")
|
||||
data = {
|
||||
"username": "Avabot",
|
||||
"avatar_url": "https://avatech-avatar-dev1.nyc3.cdn.digitaloceanspaces.com/avatechai.png",
|
||||
"content": "[API Call] New register!",
|
||||
}
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
response = requests.post(webhook_url, headers=headers, data=json.dumps(data))
|
||||
|
||||
return modelId
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import { combinePointsNode, samPrompts } from "./state.js";
|
||||
import { van } from "./van.js";
|
||||
const { div, dialog, form, button, h3, input, span } = van.tags;
|
||||
|
||||
van.derive(() => {
|
||||
if (
|
||||
combinePointsNode.val != undefined &&
|
||||
combinePointsNode.val.type === "Combine Points"
|
||||
) {
|
||||
const inputNames =
|
||||
combinePointsNode.val.inputs?.map((x) => x.name).slice(1) || [];
|
||||
const record = Object.keys(samPrompts.val);
|
||||
|
||||
const missingDiff = record.filter((x) => !inputNames.includes(x));
|
||||
|
||||
if (missingDiff.length > 0) {
|
||||
missingDiff.forEach((x) => {
|
||||
combinePointsNode.val.addInput(x, "POINTS");
|
||||
});
|
||||
combinePointsNode.val.graph.change();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export function CombinePointsDialog() {
|
||||
const showAddLayer = van.state(false);
|
||||
|
||||
return div(
|
||||
{
|
||||
class: () =>
|
||||
"absolute z-[100] top-0 left-0 flex justify-center w-full h-full ",
|
||||
},
|
||||
() =>
|
||||
dialog(
|
||||
{ id: "combine_points_dialog", class: "modal" },
|
||||
div(
|
||||
{ class: "modal-box text-base-content" },
|
||||
form(
|
||||
{
|
||||
class: "gap-2 flex flex-col",
|
||||
method: "dialog",
|
||||
onsubmit: (e) => {
|
||||
e.preventDefault();
|
||||
combine_points_dialog.close();
|
||||
},
|
||||
},
|
||||
button(
|
||||
{
|
||||
type: "button",
|
||||
class: "btn btn-sm btn-circle btn-ghost absolute right-2 top-2",
|
||||
onclick: (e) => {
|
||||
e.stopPropagation();
|
||||
combine_points_dialog.close();
|
||||
},
|
||||
},
|
||||
"✕"
|
||||
),
|
||||
h3({ class: "font-bold text-lg text-base-content" }, "Edit points"),
|
||||
() =>
|
||||
div(
|
||||
{ class: "flex flex-col gap-2 mb-2" },
|
||||
...Object.keys(samPrompts.val).map((key) => {
|
||||
return span(key);
|
||||
})
|
||||
),
|
||||
|
||||
() =>
|
||||
showAddLayer.val
|
||||
? div(
|
||||
{
|
||||
class:
|
||||
"flex flex-row justify-center items-center border rounded-md pr-2",
|
||||
},
|
||||
input({
|
||||
type: "text",
|
||||
placeholder: "Type here",
|
||||
id: "layerName",
|
||||
class:
|
||||
"input input-ghost w-full focus:ring-0 focus:border-none focus:outline-none",
|
||||
autofocus: true,
|
||||
}),
|
||||
button(
|
||||
{
|
||||
onclick: (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
showAddLayer.val = false;
|
||||
},
|
||||
},
|
||||
span({
|
||||
class: "iconify text-2xl",
|
||||
"data-icon": "iconoir:cancel",
|
||||
})
|
||||
),
|
||||
button(
|
||||
{
|
||||
onclick: (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
showAddLayer.val = false;
|
||||
const inputText =
|
||||
document.getElementById("layerName").value;
|
||||
samPrompts.val = {
|
||||
...samPrompts.val,
|
||||
[inputText]: [],
|
||||
};
|
||||
},
|
||||
},
|
||||
span({
|
||||
class: "iconify text-2xl",
|
||||
"data-icon": "mdi:tick",
|
||||
})
|
||||
)
|
||||
)
|
||||
: button(
|
||||
{
|
||||
class: "btn btn-outline btn",
|
||||
onclick: (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
showAddLayer.val = true;
|
||||
},
|
||||
},
|
||||
"Add new layer"
|
||||
),
|
||||
button(
|
||||
{
|
||||
type: "submit",
|
||||
class: "btn btn-sm btn-ghost place-self-end",
|
||||
},
|
||||
"Confirm"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
+11
-9
@@ -1,18 +1,20 @@
|
||||
import { LayerEditor } from './LayerEditor.js';
|
||||
import { ShapeFlowEditor } from './ShapeFlowEditor.js';
|
||||
import { van } from './van.js';
|
||||
import { AvatarPreview } from './AvatarPreview.js';
|
||||
import { Loading } from './Loading.js';
|
||||
import { Alert } from './Alert.js';
|
||||
import { AppHeader } from './AppHeader.js';
|
||||
import { LayerEditor } from "./LayerEditor.js";
|
||||
import { ShapeFlowEditor } from "./ShapeFlowEditor.js";
|
||||
import { van } from "./van.js";
|
||||
import { AvatarPreview } from "./AvatarPreview.js";
|
||||
import { Loading } from "./Loading.js";
|
||||
import { Alert } from "./Alert.js";
|
||||
import { AppHeader } from "./AppHeader.js";
|
||||
import { CombinePointsDialog } from "./CombinePointsDialog.js";
|
||||
const { button, iframe, div, img } = van.tags;
|
||||
|
||||
export function Container() {
|
||||
return div(
|
||||
{
|
||||
class: 'fixed left-0 top-0 w-full h-full z-[1000] pointer-events-none',
|
||||
id: 'avatech-editor',
|
||||
class: "fixed left-0 top-0 w-full h-full z-[1000] pointer-events-none",
|
||||
id: "avatech-editor",
|
||||
},
|
||||
CombinePointsDialog(),
|
||||
ShapeFlowEditor(),
|
||||
LayerEditor(),
|
||||
AvatarPreview(),
|
||||
|
||||
+35
-4
@@ -5,6 +5,7 @@ import {
|
||||
imageUrl,
|
||||
imagePrompts,
|
||||
targetNode,
|
||||
combinePointsNode,
|
||||
fileName,
|
||||
embeddings,
|
||||
imagePromptsMulti,
|
||||
@@ -389,6 +390,16 @@ const ext = {
|
||||
widget: btn,
|
||||
};
|
||||
},
|
||||
COMBINE_POINTS(node, inputName, inputData, app) {
|
||||
const btn = node.addWidget("button", "Edit points", "", () => {
|
||||
console.log("Edit points");
|
||||
combine_points_dialog.showModal();
|
||||
combinePointsNode.val = node;
|
||||
});
|
||||
return {
|
||||
widget: btn,
|
||||
};
|
||||
},
|
||||
BLENDSHAPES_CONFIG(node, inputName, inputData, app) {
|
||||
const btn = node.addWidget("button", "Edit Shape Flow", "", () => {
|
||||
targetNode.val = node;
|
||||
@@ -675,6 +686,18 @@ const ext = {
|
||||
nodeData.input.required.sam = ["SAM_PROMPTS"];
|
||||
// nodeData.input.required.upload = ['IMAGEUPLOAD'];
|
||||
// nodeData.input.required.prompts_points = ["IMAGEUPLOAD"];
|
||||
addMenuHandler(nodeType, function (_, options) {
|
||||
options.unshift({
|
||||
content: "Open In Points Editor (Local)",
|
||||
callback: () => {
|
||||
showMyImageEditor(this);
|
||||
},
|
||||
});
|
||||
});
|
||||
break;
|
||||
case "Combine Points":
|
||||
nodeData.input.required.sam = ["COMBINE_POINTS"];
|
||||
|
||||
addMenuHandler(nodeType, function (_, options) {
|
||||
options.unshift({
|
||||
content: "Open In Points Editor (Local)",
|
||||
@@ -746,11 +769,19 @@ function injectUIComponentToComfyuimenu() {
|
||||
if (!filename.toLowerCase().endsWith(".json")) {
|
||||
filename += ".json";
|
||||
}
|
||||
app.graphToPrompt().then(p=>{
|
||||
console.log('fkfk');
|
||||
app.graphToPrompt().then((p) => {
|
||||
let json = JSON.stringify(p.output, null, 2); // convert the data to a JSON string
|
||||
json = json.replace(/"seed": (\d+)/g, `"seed": "SEED"`).replace(/"image": "(?!.*mask.*\.png).*"/g, '"image": "reference_image_avatech"').replace(/"embedding_id": ".*"/g, '"embedding_id": "embedding_id_avatech"');
|
||||
const blob = new Blob([json], {type: "application/json"});
|
||||
json = json
|
||||
.replace(/"seed": (\d+)/g, `"seed": "SEED"`)
|
||||
.replace(
|
||||
/"image": "(?!.*mask.*\.png).*"/g,
|
||||
'"image": "reference_image_avatech"'
|
||||
)
|
||||
.replace(
|
||||
/"embedding_id": ".*"/g,
|
||||
'"embedding_id": "embedding_id_avatech"'
|
||||
);
|
||||
const blob = new Blob([json], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
|
||||
+4
-1
@@ -63,7 +63,6 @@ export const imagePrompts = van.state([]);
|
||||
|
||||
export const allImagePrompts = van.state([{}]);
|
||||
|
||||
|
||||
/** @type {State<Record<string, Point[]>>} */
|
||||
export const imagePromptsMulti = van.state({});
|
||||
|
||||
@@ -76,3 +75,7 @@ export const targetNode = van.state();
|
||||
export const imageSize = van.state({ width: 0, height: 0, samScale: 0 });
|
||||
export const embeddings = van.state();
|
||||
export const embeddingID = van.state("Test");
|
||||
|
||||
/** @type {State<LGraphNode>} */
|
||||
export const combinePointsNode = van.state();
|
||||
export const samPrompts = van.state({});
|
||||
|
||||
+187
-8
@@ -995,6 +995,64 @@ html{
|
||||
}
|
||||
}
|
||||
|
||||
.btn-outline:hover{
|
||||
--tw-border-opacity: 1;
|
||||
border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.btn-outline.btn-primary:hover{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-secondary:hover{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-accent:hover{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-success:hover{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-info:hover{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-warning:hover{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-error:hover{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-disabled:hover,
|
||||
.btn[disabled]:hover,
|
||||
.btn:disabled:hover{
|
||||
@@ -1382,6 +1440,109 @@ html{
|
||||
background-color: var(--fallback-bc,oklch(var(--bc)/0.2));
|
||||
}
|
||||
|
||||
.btn-outline{
|
||||
border-color: currentColor;
|
||||
background-color: transparent;
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.btn-outline.btn-active{
|
||||
--tw-border-opacity: 1;
|
||||
border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.btn-outline.btn-primary{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.btn-outline.btn-primary.btn-active{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-secondary{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-s,oklch(var(--s)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.btn-outline.btn-secondary.btn-active{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-accent{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.btn-outline.btn-accent.btn-active{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-success{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-su,oklch(var(--su)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.btn-outline.btn-success.btn-active{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-info{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-in,oklch(var(--in)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.btn-outline.btn-info.btn-active{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-warning{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.btn-outline.btn-warning.btn-active{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn-outline.btn-error{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity)));
|
||||
}
|
||||
|
||||
.btn-outline.btn-error.btn-active{
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));
|
||||
background-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black);
|
||||
border-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black);
|
||||
}
|
||||
|
||||
.btn.btn-disabled,
|
||||
.btn[disabled],
|
||||
.btn:disabled{
|
||||
@@ -1554,6 +1715,18 @@ details.collapse summary::-webkit-details-marker{
|
||||
outline-color: var(--fallback-bc,oklch(var(--bc)/0.2));
|
||||
}
|
||||
|
||||
.input-ghost{
|
||||
--tw-bg-opacity: 0.05;
|
||||
}
|
||||
|
||||
.input-ghost:focus,
|
||||
.input-ghost:focus-within{
|
||||
--tw-bg-opacity: 1;
|
||||
--tw-text-opacity: 1;
|
||||
color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.input-disabled,
|
||||
.input:disabled,
|
||||
.input[disabled]{
|
||||
@@ -2174,10 +2347,6 @@ details.collapse summary::-webkit-details-marker{
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.left-28{
|
||||
left: 7rem;
|
||||
}
|
||||
|
||||
.left-4{
|
||||
left: 1rem;
|
||||
}
|
||||
@@ -2268,10 +2437,6 @@ details.collapse summary::-webkit-details-marker{
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.mt-4{
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mt-8{
|
||||
margin-top: 2rem;
|
||||
}
|
||||
@@ -2702,6 +2867,10 @@ details.collapse summary::-webkit-details-marker{
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pr-2{
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.text-start{
|
||||
text-align: start;
|
||||
}
|
||||
@@ -3026,11 +3195,21 @@ img[src] {
|
||||
color: rgb(239 68 68 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.focus\:border-none:focus{
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.focus\:outline-none:focus{
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.focus\:ring-0:focus{
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
}
|
||||
|
||||
@media (min-width: 640px){
|
||||
.sm\:flex{
|
||||
display: flex;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ numpy
|
||||
opencv-python
|
||||
opencv-contrib-python
|
||||
einops
|
||||
bpy
|
||||
bpy==3.6.0
|
||||
segment-anything
|
||||
tqdm
|
||||
python-dotenv
|
||||
|
||||
@@ -2,6 +2,7 @@ from aiohttp import web
|
||||
from segment_anything import sam_model_registry, SamPredictor
|
||||
from PIL import Image, ImageOps
|
||||
from dotenv import load_dotenv
|
||||
from blender.mesh_utils import upload_avatar_file
|
||||
import os
|
||||
import requests
|
||||
import folder_paths
|
||||
@@ -205,45 +206,6 @@ def post_prompt(json_data):
|
||||
return web.json_response({"error": "no prompt", "node_errors": []}, status=400)
|
||||
|
||||
|
||||
def get_avatar_file(outputs):
|
||||
for node_id, output in outputs.items():
|
||||
if "gltfFilename" in output:
|
||||
avatar_filename = output["gltfFilename"][0]
|
||||
with open(
|
||||
f"{folder_paths.get_output_directory()}/{avatar_filename}", "rb"
|
||||
) as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def upload_avatar_file(outputs):
|
||||
file = get_avatar_file(outputs)
|
||||
response = requests.get("https://labs.avatech.ai/api/share")
|
||||
labData = response.json()
|
||||
modelId = labData["modelId"]
|
||||
|
||||
# upload model
|
||||
headers = {
|
||||
"x-amz-acl": "public-read",
|
||||
"Content-Type": "model/gltf-binary",
|
||||
"Content-Length": str(len(file)),
|
||||
}
|
||||
requests.put(labData["url"], headers=headers, data=file)
|
||||
|
||||
# send notification
|
||||
webhook_url = os.getenv("DISCORD_WEBHOOK_URL")
|
||||
data = {
|
||||
"username": "Avabot",
|
||||
"avatar_url": "https://avatech-avatar-dev1.nyc3.cdn.digitaloceanspaces.com/avatechai.png",
|
||||
"content": "[API Call] New register!",
|
||||
}
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
response = requests.post(webhook_url, headers=headers, data=json.dumps(data))
|
||||
|
||||
return modelId
|
||||
|
||||
|
||||
def randomSeed(num_digits=15):
|
||||
range_start = 10 ** (num_digits - 1)
|
||||
range_end = (10**num_digits) - 1
|
||||
@@ -292,7 +254,7 @@ async def post_prompt_block(request):
|
||||
if key.startswith("mask_"):
|
||||
mask_name = image_name + "_" + key.replace("mask_", "") + image_ext
|
||||
mask_path = save_image(value, save_name=mask_name)
|
||||
workflow = workflow.replace(key, mask_path)
|
||||
workflow = workflow.replace(f'"{key}"', f'"{mask_path}"')
|
||||
|
||||
workflow = workflow.replace("embedding_id_avatech", image_path)
|
||||
workflow = workflow.replace("SEED", str(randomSeed()))
|
||||
@@ -319,10 +281,12 @@ async def post_prompt_block(request):
|
||||
if history:
|
||||
# file = get_avatar_file(history[prompt_id]["outputs"])
|
||||
# return web.Response(body=file)
|
||||
|
||||
modelId = upload_avatar_file(history[prompt_id]["outputs"])
|
||||
print("model id", modelId)
|
||||
return web.json_response({"id": modelId}, status=200)
|
||||
outputs = history[prompt_id]["outputs"]
|
||||
for node_id, output in outputs.items():
|
||||
if "gltfFilename" in output:
|
||||
modelId = upload_avatar_file(output)
|
||||
print("model id", modelId)
|
||||
return web.json_response({"id": modelId}, status=200)
|
||||
time.sleep(0.5)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
class CombinePoints:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SAM_PROMPTS",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "image"
|
||||
|
||||
# OUTPUT_NODE = True
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
print("args", args)
|
||||
print("kwargs", kwargs)
|
||||
return ([])
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {"Combine Points": CombinePoints}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"Combine Points": "Combine Points"}
|
||||
@@ -0,0 +1,66 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
class ExtractBoundaryPoints:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"n_points": ("INT", {"default": -1, "min": -1, "max": 100}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("POINTS", "IMAGE")
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "image"
|
||||
|
||||
def find_main_contour(self, image, n_points):
|
||||
image = np.copy(image[0].numpy())
|
||||
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
|
||||
gray = (gray * 255).astype(np.uint8)
|
||||
# Find contours
|
||||
contours, _ = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
if len(contours) == 0:
|
||||
raise Exception(
|
||||
"No contours found. Please ensure that the image has the correct segments (e.g. when you click on the mouth, it should display a proper blue area over the mouth region)."
|
||||
)
|
||||
|
||||
# Get the largest contour
|
||||
areas = [cv2.contourArea(contour) for contour in contours]
|
||||
|
||||
max_area_index = areas.index(max(areas))
|
||||
largest_contour = contours[max_area_index]
|
||||
if n_points > 0:
|
||||
divided_by = int(largest_contour.shape[0] / n_points)
|
||||
divided_by = min(divided_by, largest_contour.shape[0])
|
||||
largest_contour = largest_contour[::divided_by].astype(int)
|
||||
contours = [largest_contour]
|
||||
|
||||
if not image.flags["C_CONTIGUOUS"]:
|
||||
image = np.ascontiguousarray(image)
|
||||
cv2.drawContours(image, contours, -1, (0, 255, 0), 3)
|
||||
|
||||
points = []
|
||||
for point in largest_contour:
|
||||
points.append(
|
||||
{"x": point[0][0], "y": point[0][1], "label": 1, "isAuto": True}
|
||||
)
|
||||
|
||||
return image, points
|
||||
|
||||
def run(self, image, n_points):
|
||||
contour_image, points = self.find_main_contour(image, n_points)
|
||||
contour_image = torch.from_numpy(np.expand_dims(contour_image, axis=0))
|
||||
print(points)
|
||||
return (points, contour_image)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {"Extract Boundary Points": ExtractBoundaryPoints}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"Extract Boundary Points": "Extract Boundary Points"}
|
||||
Reference in New Issue
Block a user