init commit

This commit is contained in:
BennyKok
2023-09-01 19:32:49 +08:00
commit ccb67dd80c
36 changed files with 2987 additions and 0 deletions
+328
View File
@@ -0,0 +1,328 @@
# Created by https://www.toptal.com/developers/gitignore/api/node,python,react
# Edit at https://www.toptal.com/developers/gitignore?templates=node,python,react
js/output.css
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
### react ###
.DS_*
**/*.backup.*
**/*.back.*
node_modules
*.sublime*
psd
thumb
sketch
# End of https://www.toptal.com/developers/gitignore/api/node,python,react
+30
View File
@@ -0,0 +1,30 @@
# avatar-graph-comfyui
This introduce a custom workflow on top of comfyui built for avatar-graph.
# Installation
1. `cd custom_nodes`
2. `git clone https://github.com/avatechgg/avatar-graph-comfyui.git`
3. Install deps `python -m pip install -r requirements.txt`
4. Navigate to in your comfyui installation folder, open `comfyui/main.py` and add the following lines:
```py
def prompt_worker(q, server):
e = execution.PromptExecutor(server)
# Add this line here
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), "custom_nodes", "avatar-graph-comfyui"))
import global_bpy
while True:
item, item_id = q.get()
# Add this line here
global_bpy.reset_bpy()
```
4. Restart comfyui
+55
View File
@@ -0,0 +1,55 @@
"""
@author: Avatech Limited
@title: Avatar Graph
@nickname: Avatar Graph
@description: Include nodes for sam + bpy operation, that allows workflow creations for generative 2d character rig.
"""
import os
import sys
import importlib
import subprocess
ag_path = os.path.join(os.path.dirname(__file__))
# pip_install = [sys.executable, '-m', 'pip', 'install']
# def ensure_pip_packages():
# try:
# # import bpy
# import segment_anything
# except Exception:
# my_path = os.path.dirname(__file__)
# requirements_path = os.path.join(my_path, "requirements.txt")
# subprocess.check_call(pip_install + ['-r', requirements_path])
# ensure_pip_packages()
def get_python_files(path):
return [f[:-3] for f in os.listdir(path) if f.endswith('.py')]
def append_to_sys_path(path):
if path not in sys.path:
sys.path.append(path)
paths = ['blender', 'sam', 'common']
files = []
for path in paths:
full_path = os.path.join(ag_path, path)
append_to_sys_path(full_path)
files.extend(get_python_files(full_path))
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
# Import all the modules and append their mappings
for file in files:
module = importlib.import_module(file)
if hasattr(module, 'NODE_CLASS_MAPPINGS'):
NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS)
if hasattr(module, 'NODE_DISPLAY_NAME_MAPPINGS'):
NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS)
WEB_DIRECTORY = "js"
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
@@ -0,0 +1,98 @@
class ApplyMeshTransformAsShapeKey:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objs": ("BPY_OBJS",),
"shape_key_name": ("STRING", {
# True if you want the field to look like the one on the ClipTextEncode node
"multiline": False,
"default": "EyeBlinkLeft", # default value
}),
"target_vertex_group": ("STRING", {
"multiline": False,
"default": "Group"
}),
"scale_x": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01, "display": "number"}),
"scale_y": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01, "display": "number"}),
"offset_x": ("FLOAT", {"default": 0.0, "min": -10.0, "max": 10.0, "step": 0.01, "display": "number"}),
"offset_y": ("FLOAT", {"default": 0.0, "min": -10.0, "max": 10.0, "step": 0.01, "display": "number"}),
},
}
RETURN_TYPES = ("BPY_OBJS", )
RETURN_NAMES = ("bpy_objs", )
FUNCTION = "process"
# OUTPUT_NODE = False
CATEGORY = "mesh"
def process(self, bpy_objs, shape_key_name, target_vertex_group, scale_x, scale_y, offset_x, offset_y):
from mathutils import Vector
import global_bpy
bpy = global_bpy.get_bpy()
obj = bpy_objs[0]
# Create new shape key
sk = obj.shape_key_add(name=shape_key_name)
sk.interpolation = 'KEY_LINEAR'
verts = obj.data.vertices
# get vertex group
vertex_group = obj.vertex_groups[target_vertex_group]
# get the verts in the group
verts_in_group = [v for v in verts if vertex_group.index in [vg.group for vg in v.groups]]
# position each vert
# for i in range(len(verts)):
# Calculate center of mesh
# center = sum((vert.co for vert in verts), Vector()) / len(verts)
center = sum((vert.co for vert in verts_in_group), Vector()) / len(verts_in_group)
# apply scale relative to object center
# Position each vert
for i, vert in enumerate(verts):
# return if vert is not in group
if vertex_group.index not in [vg.group for vg in vert.groups]:
continue
# Get vector from center to vertex
vec = vert.co - center
# Scale vector
# if scale_x != 0:
vec.x *= scale_x
# if scale_x != 0:
vec.y *= scale_y
# # Offset vector
vec.x += offset_x
vec.y += offset_y
# Calculate new position
new_pos = center + vec
# Apply new position
# vert.co = new_pos
sk.data[i].co = new_pos
return (bpy_objs, )
# A dictionary that contains all nodes you want to export with their names
# NOTE: names should be globally unique
NODE_CLASS_MAPPINGS = {
"ApplyMeshTransformAsShapeKey": ApplyMeshTransformAsShapeKey
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
"ApplyMeshTransformAsShapeKey": "Transform Shape Key"
}
+43
View File
@@ -0,0 +1,43 @@
class CleanFace():
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objs_target": ("BPY_OBJS",),
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
CATEGORY = "mesh"
def process(self, bpy_objs_target):
import global_bpy
bpy = global_bpy.get_bpy()
target_object = bpy_objs_target[0]
bpy.context.view_layer.objects.active = target_object
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
# bpy.ops.mesh.delete(type='EDGE_FACE')
bpy.ops.mesh.remove_doubles()
# bpy.ops.mesh.delete(type='FACE')
# bpy.ops.mesh.select_all(action='SELECT')
# bpy.ops.mesh.edge_face_add()
bpy.ops.object.mode_set(mode='OBJECT')
return ([target_object],)
NODE_CLASS_MAPPINGS = {
"CleanFace": CleanFace
}
NODE_DISPLAY_NAME_MAPPINGS = {
"CleanFace": "Clean Face"
}
+35
View File
@@ -0,0 +1,35 @@
class CombineMesh:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"obj_1": ("BPY_OBJS",),
"obj_2": ("BPY_OBJS",),
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
CATEGORY = "mesh"
def process(self, obj_1, obj_2):
import global_bpy
bpy = global_bpy.get_bpy()
override = bpy.context.copy()
override["active_object"] = obj_1[0]
override["selected_editable_objects"] = list([*obj_1, *obj_2])
bpy.ops.object.join(override)
joined_object = override["active_object"]
return ([joined_object], )
NODE_CLASS_MAPPINGS = {
"CombineMesh": CombineMesh
}
NODE_DISPLAY_NAME_MAPPINGS = {
"CombineMesh": "Combine Mesh"
}
+63
View File
@@ -0,0 +1,63 @@
class CreateVertexBounds():
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objs_target": ("BPY_OBJS",),
"name": ("STRING", {
"multiline": False,
"default": "Group"
}),
"extrude": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01, "display": "number"}),
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
CATEGORY = "mesh"
def process(self, bpy_objs_target, name, extrude):
import global_bpy
bpy = global_bpy.get_bpy()
if len(bpy_objs_target) == 0:
# throw error
return
target_object = bpy_objs_target[0]
# deselect all objects
bpy.ops.object.select_all(action='DESELECT')
# select only the target object
bpy.context.view_layer.objects.active = target_object
# enter enter edit mode and select all faces of the object to fill
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.extrude_region_move(MESH_OT_extrude_region={
"use_normal_flip": False, "use_dissolve_ortho_edges": False, "mirror": False})
bpy.ops.transform.resize(
value=(extrude, extrude, 1), constraint_axis=(False, False, False))
# remove the extruded vertex from the vertex group "name"
bpy.ops.object.vertex_group_remove_from()
# bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode='OBJECT')
return ([target_object],)
NODE_CLASS_MAPPINGS = {
"CreateVertexBounds": CreateVertexBounds
}
NODE_DISPLAY_NAME_MAPPINGS = {
"CreateVertexBounds": "Vertex Bounds"
}
+56
View File
@@ -0,0 +1,56 @@
class CreateVertexGroup():
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objs_target": ("BPY_OBJS",),
"name": ("STRING", {
"multiline": False,
"default": "Group"
}),
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
CATEGORY = "mesh"
def process(self, bpy_objs_target, name):
import global_bpy
bpy = global_bpy.get_bpy()
if len(bpy_objs_target) == 0:
# throw error
return
target_object = bpy_objs_target[0]
# deselect all objects
bpy.ops.object.select_all(action='DESELECT')
# select only the target object
bpy.context.view_layer.objects.active = target_object
# enter enter edit mode and select all faces of the object to fill
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
# create vertex group and assign all selected faces to it (the name of the vertex group is the same as the name of the object)
bpy.context.object.vertex_groups.new(name=name)
bpy.ops.object.vertex_group_assign()
# bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode='OBJECT')
return ([target_object],)
NODE_CLASS_MAPPINGS = {
"CreateVertexGroup": CreateVertexGroup
}
NODE_DISPLAY_NAME_MAPPINGS = {
"CreateVertexGroup": "Vertex Group"
}
+48
View File
@@ -0,0 +1,48 @@
class DissolveFaces():
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objs_target": ("BPY_OBJS",),
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
CATEGORY = "mesh"
def process(self, bpy_objs_target):
import global_bpy
bpy = global_bpy.get_bpy()
target_object = bpy_objs_target[0]
# deselect all objects
bpy.ops.object.select_all(action='DESELECT')
# select only the target object
bpy.context.view_layer.objects.active = target_object
# enter enter edit mode and select all faces of the object to fill
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
# dissolve the faces
# bpy.ops.mesh.dissolve_faces()
bpy.ops.mesh.delete(type='ONLY_FACE')
# bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode='OBJECT')
return ([target_object],)
NODE_CLASS_MAPPINGS = {
"DissolveFaces": DissolveFaces
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DissolveFaces": "Dissolve Faces"
}
+74
View File
@@ -0,0 +1,74 @@
import folder_paths
import os
class ExportGLTF():
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = ""
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objects": ("BPY_OBJS",),
"filename": ("STRING", {
"multiline": False,
"default": "out"
}),
"model_type": (["GLB", "GLTF_EMBEDDED"],),
"write_mode": (["Overwrite", "Increment"],),
},
}
RETURN_TYPES = ()
FUNCTION = "process"
OUTPUT_NODE = True
CATEGORY = "mesh"
def process(self, bpy_objects, filename, model_type, write_mode):
import global_bpy
bpy = global_bpy.get_bpy()
# print(bpy, bpy_objects)
# deselect all objects
override = bpy.context.copy()
# print(override)
# override["selected_objects"] = list(bpy.context.scene.objects)
print(bpy_objects)
override["selected_objects"] = list(bpy_objects)
# override["active_object"] = list(context.scene.objects)[0]
override["active_object"] = list(bpy_objects)[0]
# override["active_object"] = None
# override["window"] = {}
# set count to the file count
filepath = self.output_dir + "/" + filename + '.' + ("glb" if model_type == "GLB" else "gltf")
if write_mode == "Increment":
count = 0
# while file exists, increment count
while os.path.exists(self.output_dir + "/" + filename + '_' + str(count) + '.' + ("glb" if model_type == "GLB" else "gltf")):
count += 1
filepath = self.output_dir + "/" + filename + '_' + str(count) + '.' + ("glb" if model_type == "GLB" else "gltf")
with bpy.context.temp_override(**override):
bpy.ops.export_scene.gltf(filepath=filepath, export_format=model_type)
print(filepath)
return { "ui" : { "gltfFilename": { filepath.replace(f"{self.output_dir}/", "") } } }
# A dictionary that contains all nodes you want to export with their names
# NOTE: names should be globally unique
NODE_CLASS_MAPPINGS = {
"ExportGLTF": ExportGLTF
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
"ExportGLTF": "ExportGLTF"
}
+46
View File
@@ -0,0 +1,46 @@
class FillFace:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objs_target": ("BPY_OBJS",),
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
CATEGORY = "mesh"
def process(self, bpy_objs_target):
import global_bpy
bpy = global_bpy.get_bpy()
target_object = bpy_objs_target[0]
# deselect all objects
bpy.ops.object.select_all(action='DESELECT')
# select only the target object
bpy.context.view_layer.objects.active = target_object
# enter enter edit mode and select all faces of the object to fill
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
# fill the faces
bpy.ops.mesh.fill()
bpy.ops.object.mode_set(mode='OBJECT')
return ([target_object],)
NODE_CLASS_MAPPINGS = {
"FillFace": FillFace
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FillFace": "Fill Face"
}
+35
View File
@@ -0,0 +1,35 @@
class GroupObject():
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objs_target": ("BPY_OBJS",),
"bpy_objs_target_2": ("BPY_OBJS",),
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
CATEGORY = "mesh"
def process(self, bpy_objs_target, bpy_objs_target_2):
import global_bpy
bpy = global_bpy.get_bpy()
target_object = bpy_objs_target[0]
target_object2 = bpy_objs_target_2[0]
return ([target_object],)
NODE_CLASS_MAPPINGS = {
"GroupObject": GroupObject
}
NODE_DISPLAY_NAME_MAPPINGS = {
"GroupObject": "Group Object"
}
+108
View File
@@ -0,0 +1,108 @@
class GroupVertexInside():
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy": ("BPY",),
"bpy_objs_target": ("BPY_OBJS",),
"name": ("STRING", {
"multiline": False,
"default": "Group"
}),
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
CATEGORY = "mesh"
def process(self, bpy_objs_target, name):
import global_bpy
bpy = global_bpy.get_bpy()
target_object = bpy_objs_target[0]
bpy.ops.object.mode_set(mode='OBJECT')
# deselect all objects
bpy.ops.object.select_all(action='DESELECT')
# select only the target object
bpy.context.view_layer.objects.active = target_object
# enter enter edit mode and select all faces of the object to fill
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
# select the vetex group's vertices
bpy.ops.object.vertex_group_select()
# get vertex group
vertex_group = target_object.vertex_groups[name]
verts = target_object.data.vertices
# all_vertices = [(v.co.x, v.co.y, i) for i, v in target_object.data.vertices]
# get the verts in the group
verts_in_group = [v for v in verts if vertex_group.index in [vg.group for vg in v.groups]]
# get all selected vertices's xy
# get the vertices of the vertex group
vertex_group_vertices = [(v.co.x, v.co.y) for v in verts_in_group]
# get all vertices
# all_vertices = [(v.co.x, v.co.y) for v in target_object.data.vertices]
all_vertices_without_vertex_group = [(v.co.x, v.co.y, i) for i, v in enumerate(target_object.data.vertices) if v not in verts_in_group]
from scipy.spatial import ConvexHull, Delaunay
# create a convex hull of the vertex group's vertices
hull = ConvexHull(vertex_group_vertices)
# create a Delaunay triangulation of the convex hull
tri = Delaunay(hull.points[hull.vertices])
# get the indices of the vertices that are inside the convex hull
inside_vertices_indices = [i for (x,y,i) in all_vertices_without_vertex_group if tri.find_simplex((x,y))>=0]
print(inside_vertices_indices)
print(len(vertex_group_vertices))
bpy.ops.mesh.select_all(action='DESELECT')
# select the vertices that are inside the convex hull
# for i in inside_vertices_indices:
# target_object.data.vertices[i].select = True
# # select vertex from 1 to 10
# for i in range(1, 100):
# target_object.data.vertices[i].select = True
# create a new vertex group
# bpy.context.object.vertex_groups.new(name="fuck")
# bpy.ops.object.vertex_group_assign()
bpy.ops.object.mode_set(mode='OBJECT')
# create a new vertex group
# new_group = bpy.context.object.vertex_groups.new(name="new_group")
vertex_group.add(inside_vertices_indices, weight=1.0, type='REPLACE')
# assign the selected vertices to the new vertex group
# new_group.add([v.index for v in target_object.data.vertices if v.select], weight=1.0, type='REPLACE')
# new_group.add([20, 100], weight=1.0, type='REPLACE')
# assign the selected vertices to the active vertex group
bpy.ops.object.mode_set(mode='OBJECT')
return ([target_object],)
NODE_CLASS_MAPPINGS = {
"GroupVertexInside": GroupVertexInside
}
NODE_DISPLAY_NAME_MAPPINGS = {
"GroupVertexInside": "Group Vertex Inside"
}
+121
View File
@@ -0,0 +1,121 @@
class KnifeProjection:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objs_knife": ("BPY_OBJS",),
"bpy_objs_target": ("BPY_OBJS",),
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
CATEGORY = "mesh"
def process(self, bpy_objs_knife, bpy_objs_target):
import mathutils
import math
import global_bpy
bpy = global_bpy.get_bpy()
knife_object = bpy_objs_knife[0]
target_object = bpy_objs_target[0]
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = target_object
# knife_object.select_set(True)
# target_object.select_set(True)
# bpy.ops.object.mode_set(mode='EDIT')
area = next(
area for area in bpy.context.screen.areas if area.type == 'VIEW_3D')
space = next(space for space in area.spaces if space.type == 'VIEW_3D')
region = next(
region for region in area.regions if region.type == 'WINDOW')
# space.region_3d.view_perspective = 'ORTHO'
# space.region_3d.view_rotation = mathutils.Euler(
# (0, 0, 0)).to_quaternion()
# set blender to orthographic view without using numpad
# bpy.ops.view3d.view_axis(type='TOP')
override = bpy.context.copy()
# override["active_object"] = target_object
# override["edit_object"] = target_object
# override["selected_objects"] = [knife_object, target_object]
# override["selected_editable_objects"] = [knife_object, target_object]
override["region"] = region
override["area"] = area
override["space"] = space
override["active_object"] = target_object
override["edit_object"] = target_object
override["selected_objects"] = [knife_object, target_object]
override["selected_editable_objects"] = [knife_object, target_object]
knife_object.select_set(False)
target_object.select_set(True)
# move the kinfe object up a bit
bpy.ops.transform.translate(value=(0, 0, -0.1))
bpy.ops.object.mode_set(mode='EDIT')
knife_object.select_set(False)
# Get the viewport rotation
viewport_rotation = space.region_3d.view_rotation
with bpy.context.temp_override(**override):
# # Create a rotation matrix representing a top-down view
# rotation_matrix = mathutils.Matrix.Rotation(math.radians(90.0), 4, 'X')
# # Create a rotation matrix to make the object face the user
# rotation_matrix_face_user = mathutils.Matrix.Rotation(math.radians(180.0), 4, 'Z')
# # Create a rotation matrix for the viewport rotation
# rotation_matrix_viewport = viewport_rotation.to_matrix().to_4x4()
# # Remember the original locations
# original_location_knife = knife_object.location.copy()
# original_location_target = target_object.location.copy()
# # Move the objects to the origin
# knife_object.location = mathutils.Vector((0, 0, 0))
# target_object.location = mathutils.Vector((0, 0, 0))
# # Rotate the objects
# knife_object.matrix_world = rotation_matrix @ rotation_matrix_face_user @ rotation_matrix_viewport @ knife_object.matrix_world
# target_object.matrix_world = rotation_matrix @ rotation_matrix_face_user @ rotation_matrix_viewport @ target_object.matrix_world
bpy.ops.view3d.view_axis(type='TOP', align_active=False)
bpy.ops.view3d.view_persportho()
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
bpy.ops.mesh.knife_project()
# # Rotate the objects back
# knife_object.matrix_world = rotation_matrix.inverted() @ knife_object.matrix_world
# target_object.matrix_world = rotation_matrix.inverted() @ target_object.matrix_world
# # Move the objects back to their original locations
# knife_object.location = original_location_knife
# target_object.location = original_location_target
bpy.ops.object.mode_set(mode='OBJECT')
return ([target_object],)
NODE_CLASS_MAPPINGS = {
"KnifeProjection": KnifeProjection
}
NODE_DISPLAY_NAME_MAPPINGS = {
"KnifeProjection": "Knife Projection"
}
+41
View File
@@ -0,0 +1,41 @@
class LoadBPY:
def __init__(self):
import bpy
self.bpy = bpy
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), # For disabling cache
},
}
RETURN_TYPES = ("BPY",)
RETURN_NAMES = ("bpy",)
FUNCTION = "process"
# OUTPUT_NODE = False
CATEGORY = "mesh"
def process(self, seed):
self.bpy.ops.wm.read_factory_settings(use_empty=True)
return (self.bpy,)
# A dictionary that contains all nodes you want to export with their names
# NOTE: names should be globally unique
NODE_CLASS_MAPPINGS = {
"LoadBPY": LoadBPY
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
"LoadBPY": "Load BPY"
}
+101
View File
@@ -0,0 +1,101 @@
class MeshFromTexture:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
# "min_threshold": ("INT", {"default": 50, "min": 0, "max": 1024}),
# "max_threshold": ("INT", {"default": 150, "min": 0, "max": 1024}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), # For disabling cache
},
}
RETURN_TYPES = ("IMAGE","BPY_OBJS")
RETURN_NAMES = ("image","bpy_objs")
FUNCTION = "process"
#OUTPUT_NODE = False
CATEGORY = "mesh"
def process(self, image, seed):
import torch
import cv2
import numpy as np
import global_bpy
bpy = global_bpy.get_bpy()
image = np.copy(image[0].numpy())
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
gray = (gray * 255).astype(np.uint8)
# edges = cv2.Canny(gray, min_threshold, max_threshold)
# Find contours
contours, _ = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
def normalize_vertices(vertices, max_value):
return vertices / float(max_value) * 2 - 1
# Get the image width and height
height, width = image.shape[:2]
# Normalize the vertices
normalized_contours = []
for contour in contours:
normalized_contour = []
for vertex in contour:
normalized_vertex = [normalize_vertices(vertex[0][0], width), normalize_vertices(vertex[0][1], height) * -1]
normalized_contour.append(normalized_vertex)
normalized_contours.append(np.array(normalized_contour, dtype=np.float32))
meshes = []
# print(len(normalized_contours))
for i, contour in enumerate(normalized_contours):
mesh = bpy.data.meshes.new(name=f"NewMesh{i}") # Create a new mesh for each contour
obj = bpy.data.objects.new(f"NewObject{i}", mesh) # Create a new object for each mesh
bpy.context.collection.objects.link(obj) # Link the object to the current collection
ordered_vertices = [(*vertex, 0) for vertex in contour] # Add a z coordinate to each vertex
face = list(range(len(ordered_vertices))) # Create a face from the vertices
mesh.from_pydata(ordered_vertices, [], [face]) # Create the mesh from the vertices and face
# Create a default shape key for the mesh
sk_basis = obj.shape_key_add(name='Basis')
meshes.append(obj) # Add the object to the list of meshes
# Convert edges back to an image
# image_from_edges = cv2.cvtColor(gray, cv2.COLOR_GRAY2RGB)
# image_from_edges = image_from_edges.astype(np.float32)
# image_from_edges = [torch.from_numpy(image_from_edges)]
# Draw contours on the original image
if not image.flags['C_CONTIGUOUS']:
image = np.ascontiguousarray(image)
cv2.drawContours(image, contours, -1, (0, 255, 0), 3)
# print(image, meshes)
# Convert image back to tensor
image = [torch.from_numpy(image)]
return (image,meshes)
# A dictionary that contains all nodes you want to export with their names
# NOTE: names should be globally unique
NODE_CLASS_MAPPINGS = {
"MeshFromTexture": MeshFromTexture
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
"MeshFromTexture": "Mesh from texture"
}
+80
View File
@@ -0,0 +1,80 @@
import subprocess
import folder_paths
import os
blender_process_global = []
class OpenInBlender:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objs": ("BPY_OBJS",),
"blender_path": ("STRING", {
"multiline": False,
"default": "blender"
}),
"shading": (["Material", "Solid", "Rendered", "Wireframe"],),
"camera_location": ("VECTOR3D",),
"camera_rotation": ("VECTOR3D",),
},
}
RETURN_TYPES = ()
FUNCTION = "process"
OUTPUT_NODE = True
CATEGORY = "mesh"
def process(self, bpy_objs, blender_path, shading, camera_location, camera_rotation):
import mathutils
import global_bpy
bpy = global_bpy.get_bpy()
# Change shading mode and viewport
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
for space in area.spaces:
if space.type == 'VIEW_3D':
space.shading.type = shading.upper()
rv3d = space.region_3d
rv3d.view_location = camera_location
rv3d.view_rotation = mathutils.Euler(camera_rotation).to_quaternion()
# Open blender
if hasattr(self, 'blender_process'):
self.blender_process.kill()
# remove from global list so it doesn't get garbage collected
blender_process_global.remove(self.blender_process)
# Save as .blend
output_file = self.output_dir + '/tmp.blend'
if os.path.exists(output_file):
os.remove(output_file)
bpy.ops.wm.save_as_mainfile(filepath=output_file)
self.blender_process = subprocess.Popen([blender_path, output_file])
# append to global list so it doesn't get garbage collected
blender_process_global.append(self.blender_process)
return { "ui" : { } }
NODE_CLASS_MAPPINGS = {
"OpenInBlender": OpenInBlender
}
NODE_DISPLAY_NAME_MAPPINGS = {
"OpenInBlender": "Open in Blender"
}
# detects when the python process is killed, and kills the blender process
import atexit
@atexit.register
def kill_blender_process():
print('blender_process_global', blender_process_global)
for process in blender_process_global:
process.kill()
+72
View File
@@ -0,0 +1,72 @@
class Plane:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"division": ("INT", {
"default": 0,
"min": 0,
"max": 256,
"step": 1,
"display": "number"
}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), # For disabling cache
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
CATEGORY = "mesh"
def process(self, division, seed):
import global_bpy
bpy = global_bpy.get_bpy()
# Define coordinates for the plane to object mode
if bpy.context.active_object is not None:
bpy.ops.object.mode_set(mode='OBJECT')
coords = [(-1, -1, 0), (1, -1, 0), (-1, 1, 0), (1, 1, 0)]
# Create a mesh object
mesh = bpy.data.meshes.new(name="PlaneMesh")
mesh.from_pydata(coords,[],[(0,1,3,2)])
# Create a new object with the mesh
object = bpy.data.objects.new(name="PlaneObject", object_data=mesh)
bpy.ops.object.select_all(action='DESELECT')
bpy.context.collection.objects.link(object)
bpy.context.view_layer.objects.active = object
object.select_set(True)
# Invert the Y axis of the UV to fix the vertical inversion
bpy.ops.transform.resize(value=(-1, -1, 1))
# Create a new UV map
uv_map = mesh.uv_layers.new(name="newUV")
mesh.uv_layers.active = uv_map
# Unwrap the mesh
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.uv.unwrap()
bpy.ops.object.mode_set(mode='OBJECT')
# Subdivide the plane
if division > 0:
original_mode = bpy.context.object.mode
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.subdivide(number_cuts=division)
bpy.ops.object.mode_set(mode=original_mode)
return ([object],)
NODE_CLASS_MAPPINGS = {
"Plane": Plane
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Plane": "Plane"
}
+55
View File
@@ -0,0 +1,55 @@
class ScaleVertex():
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objs_target": ("BPY_OBJS",),
"extrude": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01, "display": "number"}),
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
CATEGORY = "mesh"
def process(self, bpy_objs_target, extrude):
import global_bpy
bpy = global_bpy.get_bpy()
if len(bpy_objs_target) == 0:
# throw error
return
target_object = bpy_objs_target[0]
# deselect all objects
bpy.ops.object.select_all(action='DESELECT')
# select only the target object
bpy.context.view_layer.objects.active = target_object
# enter enter edit mode and select all faces of the object to fill
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.transform.resize(
value=(extrude, extrude, 1), constraint_axis=(False, False, False))
# remove the extruded vertex from the vertex group "name"
# bpy.ops.object.vertex_group_remove_from()
# bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode='OBJECT')
return ([target_object],)
NODE_CLASS_MAPPINGS = {
"ScaleVertex": ScaleVertex
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ScaleVertex": "Scale Vertex"
}
+88
View File
@@ -0,0 +1,88 @@
class Texture:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objs": ("BPY_OBJS",),
"texture": ("IMAGE",)
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
# OUTPUT_NODE = False
CATEGORY = "mesh"
def process(self, bpy_objs, texture):
import numpy as np
import global_bpy
bpy = global_bpy.get_bpy()
# Convert image to numpy
texture = texture[0].numpy()
# Flip the image vertically
texture = np.flipud(texture)
# Create an image with the required dimensions
img = bpy.data.images.new('my_image', width=texture.shape[1], height=texture.shape[0])
# If there is no alpha channel, append one full of 1's
if texture.shape[2] == 3:
alpha_channel = np.ones((*texture.shape[:2], 1))
texture = np.concatenate((texture, alpha_channel), axis=2)
# Flatten image data and rearrange color channels for blender
img.pixels = texture.ravel()
# Pack image to store it within .blend file
img.pack()
# Save image to a file
# img.filepath_raw = 'test.png'
# img.file_format = 'PNG'
# img.save()
# Get the active object
obj = bpy_objs[0]
# Create a material
mat = bpy.data.materials.new("MaterialName")
mat.use_nodes = True
nodes = mat.node_tree.nodes
for node in nodes:
nodes.remove(node)
# Add a new texture node
texture_node = nodes.new(type='ShaderNodeTexImage')
texture_node.image = img
# Add a new BSDF node
bsdf_node = nodes.new(type='ShaderNodeBsdfPrincipled')
# Add a new output node
output_node = nodes.new(type='ShaderNodeOutputMaterial')
# Link nodes together
links = mat.node_tree.links
links.new(bsdf_node.inputs['Base Color'], texture_node.outputs['Color'])
links.new(output_node.inputs['Surface'], bsdf_node.outputs['BSDF'])
# Assign the material to the active object
if obj.data.materials:
obj.data.materials[0] = mat
else:
obj.data.materials.append(mat)
return (bpy_objs,)
NODE_CLASS_MAPPINGS = {
"Texture": Texture
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Texture": "Texture"
}
+50
View File
@@ -0,0 +1,50 @@
class UV_Unwrap():
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"bpy_objs_target": ("BPY_OBJS",),
},
}
RETURN_TYPES = ("BPY_OBJS",)
RETURN_NAMES = ("bpy_objs",)
FUNCTION = "process"
CATEGORY = "mesh"
def process(self, bpy_objs_target):
import global_bpy
bpy = global_bpy.get_bpy()
target_object = bpy_objs_target[0]
bpy.context.view_layer.objects.active = target_object
bpy.ops.object.mode_set(mode='OBJECT')
# deselect all objects
bpy.ops.object.select_all(action='DESELECT')
# select only the target object
bpy.context.view_layer.objects.active = target_object
# enter enter edit mode and select all faces of the object to fill
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
# perform a cube projection unwrap
bpy.ops.uv.cube_project(cube_size=1.0, correct_aspect=True, clip_to_bounds=False, scale_to_bounds=True)
# bpy.ops.mesh.delete(type='FACE')
bpy.ops.object.mode_set(mode='OBJECT')
return ([target_object],)
NODE_CLASS_MAPPINGS = {
"UV_Unwrap": UV_Unwrap
}
NODE_DISPLAY_NAME_MAPPINGS = {
"UV_Unwrap": "UV Unwrap"
}
+52
View File
@@ -0,0 +1,52 @@
import torch
from einops import rearrange
from nodes import PreviewImage
class PointVisualizer(PreviewImage):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE",),
"point": ("VECTOR3D",),
"point_size": ("INT", {
"default": 1,
"min": 10,
"max": 20,
"step": 1,
"display": "number"
}),
},
}
FUNCTION = "process"
CATEGORY = "image"
def process(self, images, point, point_size):
x, y, z = point
h, w, c = images[0].shape
point_image = torch.zeros(images[0].shape)
center_x, center_y, _ = point
top_left_x = center_x - point_size // 2
top_left_y = center_y - point_size // 2
# Make sure the square is within the image boundaries
top_left_x = max(0, min(w - point_size, top_left_x))
top_left_y = max(0, min(h - point_size, top_left_y))
point_image[top_left_y:top_left_y+point_size, top_left_x:top_left_x+point_size] = 1
point_image = rearrange(point_image, 'h w c -> 1 h w c')
# Blending
images = images * 0.2 + point_image * 0.8
return self.save_images(images)
NODE_CLASS_MAPPINGS = {
"PointVisualizer": PointVisualizer
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PointVisualizer": "Point Visualizer"
}
+45
View File
@@ -0,0 +1,45 @@
class VECTOR3D:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"x": ("INT", {
"default": 0,
"min": -1024,
"max": 1024,
"step": 1,
"display": "number"
}),
"y": ("INT", {
"default": 0,
"min": -1024,
"max": 1024,
"step": 1,
"display": "number"
}),
"z": ("INT", {
"default": 0,
"min": -1024,
"max": 1024,
"step": 1,
"display": "number"
}),
},
}
RETURN_TYPES = ("VECTOR3D",)
FUNCTION = "run"
CATEGORY = "utils"
def run(self, x, y, z):
return ([x, y, z],)
NODE_CLASS_MAPPINGS = {
"VECTOR3D": VECTOR3D
}
NODE_DISPLAY_NAME_MAPPINGS = {
"VECTOR3D": "Vector 3D"
}
+29
View File
@@ -0,0 +1,29 @@
import bpy
global_bpy = bpy
def get_bpy():
global global_bpy
return global_bpy
def reset_bpy():
global global_bpy
bpy = global_bpy
# not using this cause this cause seg fault frequently
# global_bpy.ops.wm.read_factory_settings(use_empty=True)
# Delete all objects
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
# Delete all meshes
for mesh in bpy.data.meshes:
bpy.data.meshes.remove(mesh)
# Delete all materials
for material in bpy.data.materials:
bpy.data.materials.remove(material)
# Delete all textures
for texture in bpy.data.textures:
bpy.data.textures.remove(texture)
+112
View File
@@ -0,0 +1,112 @@
import { app } from '../../scripts/app.js';
import { api } from '../../scripts/api.js';
import van from 'https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.1.3.min.js';
function addMenuHandler(nodeType, cb) {
const getOpts = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function () {
const r = getOpts.apply(this, arguments);
cb.apply(this, arguments);
return r;
};
}
const iframeSrc = van.state('https://editor.avatech.ai');
const showEditor = van.state(false);
function openInAvatechEditor(url, fileName) {
let editor = document.getElementById('avatech-editor-iframe');
iframeSrc.val = url;
showEditor.val = true;
editor.contentWindow.postMessage(
{
key: 'key',
value: fileName,
method: 'store',
},
'*',
);
}
app.registerExtension({
name: 'Avatech.Avatar.BlendshapeEditor',
init(app) {},
async setup() {
const { button, iframe, div } = van.tags;
const AvatarEditor = () => {
return div(
{
class: 'fixed left-0 top-0 w-full h-full z-[200] pointer-events-none',
id: 'avatech-editor',
},
button(
{
class: () =>
'absolute left-0 top-0 z-[100] pointer-events-auto ' + (showEditor.val ? '' : 'hidden'),
onclick: () => {
console.log('close');
showEditor.val = false;
},
},
'back',
),
iframe({
id: 'avatech-editor-iframe',
title: 'avatech-editor-iframe',
name: 'avatech-editor-iframe',
class: () =>
'w-full h-full pointer-events-auto ' +
(showEditor.val ? '' : 'hidden'),
src: iframeSrc,
}),
);
};
van.add(document.body, AvatarEditor());
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === 'ExportGLTF') {
addMenuHandler(nodeType, function (_, options) {
const output = app.nodeOutputs[this.id + ""];
if (!output.gltfFilename) return;
const gltfFilename = window.location.protocol + '//' + api.api_host + api.api_base + (`/view?filename=${output.gltfFilename[0]}`)
options.unshift({
content: 'Save file',
callback: () => {
const a = document.createElement('a');
let url = new URL(this.gltfFilename);
url.searchParams.delete('preview');
a.href = url;
a.setAttribute(
'download',
new URLSearchParams(url.search).get('filename'),
);
document.body.append(a);
a.click();
requestAnimationFrame(() => a.remove());
},
});
options.unshift({
content: 'Open In Avatech Editor (Local)',
callback: () => {
openInAvatechEditor('http://localhost:3006', gltfFilename);
},
});
options.unshift({
content: 'Open In Avatech Editor',
callback: () => {
openInAvatechEditor('https://editor.avatech.ai', gltfFilename);
},
});
});
}
},
});
+3
View File
@@ -0,0 +1,3 @@
let style = document.createElement('style');
style.innerHTML = `.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.fixed{position:fixed}.absolute{position:absolute}.left-0{left:0}.top-0{top:0}.z-\\[100\\]{z-index:100}.z-\\[200\\]{z-index:200}.inline{display:inline}.hidden{display:none}.h-full{height:100%}.w-full{width:100%}`;
document.head.appendChild(style);
+3
View File
@@ -0,0 +1,3 @@
/* @tailwind base; */
@tailwind components;
@tailwind utilities;
+17
View File
@@ -0,0 +1,17 @@
{
"name": "avatar-graph-comfyui",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "concurrently \"tailwindcss -i ./js/style.css -o ./js/output.css --watch -m\" \"node ./style-watcher.js\""
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"chokidar": "^3.5.3",
"concurrently": "^8.2.1",
"tailwindcss": "^3.3.3"
}
}
+756
View File
@@ -0,0 +1,756 @@
lockfileVersion: '6.1'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
devDependencies:
chokidar:
specifier: ^3.5.3
version: 3.5.3
concurrently:
specifier: ^8.2.1
version: 8.2.1
tailwindcss:
specifier: ^3.3.3
version: 3.3.3
packages:
/@alloc/quick-lru@5.2.0:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
dev: true
/@babel/runtime@7.22.11:
resolution: {integrity: sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.14.0
dev: true
/@jridgewell/gen-mapping@0.3.3:
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/set-array': 1.1.2
'@jridgewell/sourcemap-codec': 1.4.15
'@jridgewell/trace-mapping': 0.3.19
dev: true
/@jridgewell/resolve-uri@3.1.1:
resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/set-array@1.1.2:
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec@1.4.15:
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
dev: true
/@jridgewell/trace-mapping@0.3.19:
resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==}
dependencies:
'@jridgewell/resolve-uri': 3.1.1
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@nodelib/fs.scandir@2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
dependencies:
'@nodelib/fs.stat': 2.0.5
run-parallel: 1.2.0
dev: true
/@nodelib/fs.stat@2.0.5:
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
engines: {node: '>= 8'}
dev: true
/@nodelib/fs.walk@1.2.8:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
dependencies:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.15.0
dev: true
/ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
dev: true
/ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
dependencies:
color-convert: 2.0.1
dev: true
/any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
dev: true
/anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
dev: true
/arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
dev: true
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
dev: true
/brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: true
/braces@3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
dependencies:
fill-range: 7.0.1
dev: true
/camelcase-css@2.0.1:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
dev: true
/chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
dev: true
/chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.3
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.3
dev: true
/cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
dev: true
/color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
dependencies:
color-name: 1.1.4
dev: true
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
dev: true
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
/concurrently@8.2.1:
resolution: {integrity: sha512-nVraf3aXOpIcNud5pB9M82p1tynmZkrSGQ1p6X/VY8cJ+2LMVqAgXsJxYYefACSHbTYlm92O1xuhdGTjwoEvbQ==}
engines: {node: ^14.13.0 || >=16.0.0}
hasBin: true
dependencies:
chalk: 4.1.2
date-fns: 2.30.0
lodash: 4.17.21
rxjs: 7.8.1
shell-quote: 1.8.1
spawn-command: 0.0.2
supports-color: 8.1.1
tree-kill: 1.2.2
yargs: 17.7.2
dev: true
/cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
hasBin: true
dev: true
/date-fns@2.30.0:
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
engines: {node: '>=0.11'}
dependencies:
'@babel/runtime': 7.22.11
dev: true
/didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
dev: true
/dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
dev: true
/emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
dev: true
/escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
dev: true
/fast-glob@3.3.1:
resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
engines: {node: '>=8.6.0'}
dependencies:
'@nodelib/fs.stat': 2.0.5
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.5
dev: true
/fastq@1.15.0:
resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
dependencies:
reusify: 1.0.4
dev: true
/fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
dev: true
/fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true
/fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/function-bind@1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true
/get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
dev: true
/glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
dependencies:
is-glob: 4.0.3
dev: true
/glob-parent@6.0.2:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
dependencies:
is-glob: 4.0.3
dev: true
/glob@7.1.6:
resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
dev: true
/has@1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
dependencies:
function-bind: 1.1.1
dev: true
/inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: true
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
/is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.2.0
dev: true
/is-core-module@2.13.0:
resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==}
dependencies:
has: 1.0.3
dev: true
/is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
dev: true
/is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
dev: true
/is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
dependencies:
is-extglob: 2.1.1
dev: true
/is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
dev: true
/jiti@1.19.3:
resolution: {integrity: sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w==}
hasBin: true
dev: true
/lilconfig@2.1.0:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'}
dev: true
/lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: true
/lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: true
/merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
dev: true
/micromatch@4.0.5:
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
engines: {node: '>=8.6'}
dependencies:
braces: 3.0.2
picomatch: 2.3.1
dev: true
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.11
dev: true
/mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
dependencies:
any-promise: 1.3.0
object-assign: 4.1.1
thenify-all: 1.6.0
dev: true
/nanoid@3.3.6:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
dev: true
/object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
dev: true
/object-hash@3.0.0:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
dev: true
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
wrappy: 1.0.2
dev: true
/path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
dev: true
/path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
/picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
dev: true
/pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
dev: true
/pirates@4.0.6:
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
engines: {node: '>= 6'}
dev: true
/postcss-import@15.1.0(postcss@8.4.29):
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
engines: {node: '>=14.0.0'}
peerDependencies:
postcss: ^8.0.0
dependencies:
postcss: 8.4.29
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.4
dev: true
/postcss-js@4.0.1(postcss@8.4.29):
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
engines: {node: ^12 || ^14 || >= 16}
peerDependencies:
postcss: ^8.4.21
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.29
dev: true
/postcss-load-config@4.0.1(postcss@8.4.29):
resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==}
engines: {node: '>= 14'}
peerDependencies:
postcss: '>=8.0.9'
ts-node: '>=9.0.0'
peerDependenciesMeta:
postcss:
optional: true
ts-node:
optional: true
dependencies:
lilconfig: 2.1.0
postcss: 8.4.29
yaml: 2.3.2
dev: true
/postcss-nested@6.0.1(postcss@8.4.29):
resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.2.14
dependencies:
postcss: 8.4.29
postcss-selector-parser: 6.0.13
dev: true
/postcss-selector-parser@6.0.13:
resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==}
engines: {node: '>=4'}
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
dev: true
/postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
dev: true
/postcss@8.4.29:
resolution: {integrity: sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.6
picocolors: 1.0.0
source-map-js: 1.0.2
dev: true
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true
/read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
dependencies:
pify: 2.3.0
dev: true
/readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.1
dev: true
/regenerator-runtime@0.14.0:
resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==}
dev: true
/require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
dev: true
/resolve@1.22.4:
resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==}
hasBin: true
dependencies:
is-core-module: 2.13.0
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
dev: true
/reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
dev: true
/run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
dependencies:
queue-microtask: 1.2.3
dev: true
/rxjs@7.8.1:
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
dependencies:
tslib: 2.6.2
dev: true
/shell-quote@1.8.1:
resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
dev: true
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
dev: true
/spawn-command@0.0.2:
resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
dev: true
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
dev: true
/strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
dependencies:
ansi-regex: 5.0.1
dev: true
/sucrase@3.34.0:
resolution: {integrity: sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==}
engines: {node: '>=8'}
hasBin: true
dependencies:
'@jridgewell/gen-mapping': 0.3.3
commander: 4.1.1
glob: 7.1.6
lines-and-columns: 1.2.4
mz: 2.7.0
pirates: 4.0.6
ts-interface-checker: 0.1.13
dev: true
/supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
dependencies:
has-flag: 4.0.0
dev: true
/supports-color@8.1.1:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
dependencies:
has-flag: 4.0.0
dev: true
/supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
dev: true
/tailwindcss@3.3.3:
resolution: {integrity: sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==}
engines: {node: '>=14.0.0'}
hasBin: true
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
chokidar: 3.5.3
didyoumean: 1.2.2
dlv: 1.1.3
fast-glob: 3.3.1
glob-parent: 6.0.2
is-glob: 4.0.3
jiti: 1.19.3
lilconfig: 2.1.0
micromatch: 4.0.5
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.0.0
postcss: 8.4.29
postcss-import: 15.1.0(postcss@8.4.29)
postcss-js: 4.0.1(postcss@8.4.29)
postcss-load-config: 4.0.1(postcss@8.4.29)
postcss-nested: 6.0.1(postcss@8.4.29)
postcss-selector-parser: 6.0.13
resolve: 1.22.4
sucrase: 3.34.0
transitivePeerDependencies:
- ts-node
dev: true
/thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
dependencies:
thenify: 3.3.1
dev: true
/thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
dependencies:
any-promise: 1.3.0
dev: true
/to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
dependencies:
is-number: 7.0.0
dev: true
/tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
dev: true
/ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
dev: true
/tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
dev: true
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: true
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true
/y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
dev: true
/yaml@2.3.2:
resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==}
engines: {node: '>= 14'}
dev: true
/yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
dev: true
/yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
dependencies:
cliui: 8.0.1
escalade: 3.1.1
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1
dev: true
+8
View File
@@ -0,0 +1,8 @@
torchvision
numpy
opencv-python
opencv-contrib-python
einops
bpy
segment-anything
# -e git+https://github.com/facebookresearch/segment-anything.git#egg=segment_anything
+42
View File
@@ -0,0 +1,42 @@
import folder_paths
import torch
from segment_anything import SamAutomaticMaskGenerator, sam_model_registry
from einops import rearrange, repeat
import requests
from PIL import Image
import io
import numpy as np
from segment_anything import SamAutomaticMaskGenerator, sam_model_registry, SamPredictor
from einops import rearrange, repeat
class SAM_Remote_Emb:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model_type": (["vit_h", "vit_l", "vit_b"],),
"ckpt": (folder_paths.get_filename_list("sam"),),
},
}
RETURN_TYPES = ("SAM", "SAMPREDICTOR")
RETURN_NAMES = ("sam", "predictor")
FUNCTION = "segment"
CATEGORY = "image"
def segment(self, model_type, ckpt):
ckpt = folder_paths.get_full_path("sam", ckpt)
sam = sam_model_registry[model_type](checkpoint=ckpt)
predictor = SamPredictor(sam)
return (sam, predictor)
NODE_CLASS_MAPPINGS = {
"SAM_Loader": SAM_Remote_Emb
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SAM_Loader": "SAM Loader"
}
+81
View File
@@ -0,0 +1,81 @@
import folder_paths
import torch
from segment_anything import SamAutomaticMaskGenerator, sam_model_registry
from einops import rearrange, repeat
class SAM:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"point_1": ("VECTOR3D",),
"model_type": (["vit_h", "vit_l", "vit_b"],),
"ckpt": (folder_paths.get_filename_list("sam"),),
},
"optional": {
"point_2": ("VECTOR3D",),
"point_3": ("VECTOR3D",),
"point_4": ("VECTOR3D",),
"point_5": ("VECTOR3D",),
}
}
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE")
RETURN_NAMES = ("mask_1", "image_1", "mask_2", "image_2", "mask_3", "image_3", "mask_4", "image_4", "mask_5", "image_5",)
FUNCTION = "segment"
CATEGORY = "image"
def is_point_in_bbox(self, point, mask):
x, y, z = point
in_bbox = mask[y, x].item() if y < mask.shape[0] and x < mask.shape[1] else None
return in_bbox
def find_largest_bbox(self, point, masks):
largest_bbox = None
largest_area = 0
for mask in masks:
area = mask['area']
if self.is_point_in_bbox(point, mask['segmentation']) and area > largest_area:
largest_bbox = mask
largest_area = area
return largest_bbox
def segment(self, image, point_1, model_type, ckpt, point_2=None, point_3=None, point_4=None, point_5=None):
image = (image[0] * 255).to(torch.uint8).numpy()
H, W, C = image.shape
ckpt = folder_paths.get_full_path("sam", ckpt)
sam = sam_model_registry[model_type](checkpoint=ckpt) #.to("mps")
mask_generator = SamAutomaticMaskGenerator(sam)
masks = mask_generator.generate(image)
points = [point_1, point_2, point_3, point_4, point_5]
outputs = []
for point in points:
if point is not None:
bbox = self.find_largest_bbox(point, masks)
mask = torch.from_numpy(bbox["segmentation"]) if bbox is not None else torch.zeros(H, W)
mask = repeat(mask, 'h w -> h w c', c=3)
out_image = torch.from_numpy(image) * mask
mask = rearrange(mask, 'h w c -> 1 h w c')
out_image = rearrange(out_image, 'h w c -> 1 h w c') / 255
outputs.append(mask)
outputs.append(out_image)
else:
outputs.append(None)
outputs.append(None)
return outputs
NODE_CLASS_MAPPINGS = {
"Segmentation": SAM
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Segmentation": "Segmentation Node"
}
+99
View File
@@ -0,0 +1,99 @@
import torch
from einops import rearrange, repeat
import numpy as np
import cv2
class SAM_Predict:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"embeddings": ("EMBEDDINGS",),
"predictor": ("SAMPREDICTOR",)
},
"optional": {
"point_1": ("VECTOR3D",),
"mask": ("IMAGE",),
}
}
RETURN_TYPES = ("IMAGE","IMAGE", "IMAGE")
RETURN_NAMES = ("image","out_image", "mask")
FUNCTION = "segment"
CATEGORY = "image"
def segment(self, image, embeddings, predictor, point_1=None, mask=None):
image_embedding_list = embeddings['image_embedding']
shape = tuple(embeddings['shape'])
input_size = tuple(embeddings['input_size'])
# Convert the list back to a numpy array with the original shape
image_embedding_np = np.array(image_embedding_list, dtype=np.single).reshape(shape)
# Convert the numpy array to a PyTorch tensor
image_embedding_tensor = torch.from_numpy(image_embedding_np)
# Set the image embeddings for the model
# predictor.set_torch_image(image_embedding_tensor, image.shape[:2])
# print(image[0].shape[:2])
predictor.input_size = input_size
predictor.features = image_embedding_tensor
predictor.is_image_set = True
predictor.original_size = image[0].shape[:2]
if point_1 != None:
x, y, z = point_1
point_coords = np.array([[x, y]])
point_labels = np.array([1])
masks, iou_predictions, low_res_masks = predictor.predict(
point_coords=point_coords,
point_labels=point_labels,
)
elif mask != None:
# scale the mask to 256x256
cv2_mask = cv2.resize(np.array(mask[0]), (256, 256))
cv2_mask = cv2_mask[np.newaxis, :, :]
cv2_mask = (cv2_mask * 255).astype(int)
true_locations = np.array(np.where(cv2_mask[0] == 255))
if true_locations.shape[1] > 0:
# Randomly select a point in the mask
rand_index = np.random.randint(true_locations.shape[1])
y, x = true_locations[:, rand_index]
point_coords = np.array([[x, y]])
point_labels = np.array([1])
masks, iou_predictions, low_res_masks = predictor.predict(
point_coords=point_coords,
point_labels=point_labels,
mask_input=cv2_mask
)
else:
# No detected mask
h, w, c = image[0].shape
masks = np.zeros((1, h, w))
masks = torch.from_numpy(masks)
masks = rearrange(masks[0], 'h w -> 1 h w')
# masks = rearrange(masks, 'c h w -> 1 c h w')
out_image = repeat(masks, '1 h w -> 1 h w c', c=3) * image
print(masks.shape, torch.max(masks), torch.min(masks))
print(image.shape, torch.max(image), torch.min(image))
# print(emb)
# print(masks, out_image)
return (image, out_image, masks)
NODE_CLASS_MAPPINGS = {
"SAM_Predict": SAM_Predict
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SAM_Predict": "SAM Predict"
}
+79
View File
@@ -0,0 +1,79 @@
import requests
from PIL import Image
import io
import numpy as np
class SAM_Embedding:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
},
"optional": {
"predictor": ("SAMPREDICTOR",)
}
}
RETURN_TYPES = ("EMBEDDINGS", )
RETURN_NAMES = ("embeddings", )
FUNCTION = "segment"
CATEGORY = "image"
def segment(self, image, predictor=None):
# Convert PyTorch tensor to numpy array
image_np = (image[0].numpy() * 255).astype(np.uint8)
if predictor != None:
predictor.set_image(image_np)
emb = predictor.get_image_embedding().cpu().numpy()
output = {
"image_embedding": emb,
"shape": emb.shape,
"input_size": predictor.input_size
}
else:
# Convert numpy array to PIL Image
img = Image.fromarray(image_np)
# Create an in-memory bytes buffer
img_byte_arr = io.BytesIO()
# Save the PIL Image to the bytes buffer in PNG format
img.save(img_byte_arr, format='PNG')
# Get the bytes value of the buffer
img_byte_arr = img_byte_arr.getvalue()
# Create a dictionary with the image bytes
files = {'image': ('image.png', img_byte_arr)}
# Send the POST request
response = requests.post('https://avatechgg--segment-anything-entrypoint.modal.run', files=files)
# Check if the request was successful
if response.status_code == 200:
# Parse the JSON response
output = response.json()
else:
print(f"Request failed with status code {response.status_code}")
output = None
# sam = sam_model_registry[model_type](checkpoint=ckpt)
# predictor = SamPredictor(sam)
# masks = predictor.set_torch_image
# masks = predictor.predict
# print(output)
return (output, )
NODE_CLASS_MAPPINGS = {
"SAM_Embedding": SAM_Embedding
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SAM_Embedding": "SAM Embedding"
}
+24
View File
@@ -0,0 +1,24 @@
const fs = require('fs');
const chokidar = require('chokidar');
const path = require('path');
// Initialize watcher.
const watcher = chokidar.watch(path.resolve(__dirname, './js/output.css'), {
persistent: true
});
// Add event listeners.
watcher
.on('change', filePath => {
console.log(`File ${filePath} has been changed`);
let cssContent = fs.readFileSync(filePath, 'utf8');
let jsContent = `let style = document.createElement('style');
style.innerHTML = \`${cssContent.replace(/\\/g, '\\\\')}\`;
document.head.appendChild(style);`;
fs.writeFileSync(path.resolve(__dirname, './js/style-inject.js'), jsContent);
});
// On script exit, close the watcher
process.on('exit', (code) => {
watcher.close();
});
+10
View File
@@ -0,0 +1,10 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
// content: ['./js/**/*.{html,js}'],
content: ['./js/blendshape_editor.js'],
theme: {
extend: {},
},
plugins: [],
}