Merge pull request #13 from AlekPet/model_select_list

3d Model select list
This commit is contained in:
lo-th
2024-12-06 18:27:19 +01:00
committed by GitHub
5 changed files with 263 additions and 27 deletions
+86 -4
View File
@@ -56,7 +56,10 @@ export class ThreeCanvas {
this.VIEWS3 = views3
// Fix camers
this.fixCamers = fixCamers;
this.fixCamers = fixCamers;
// List models
this.listModels = []
}
getDom(idx=0) {
@@ -111,7 +114,54 @@ export class ThreeCanvas {
return wrapperCanvas
}
createPanel(){
// get models function
async getModelRequest (pWrapper) {
// Create options elements
const addOptionsEl = (result) => {
const threeCanvasList = pWrapper.querySelector(".threeCanvasListModels")
const models_options = []
models_options.push($el("option", {textContent: "Select model", disabled: true, selected: true}))
threeCanvasList.innerHTML = ""
for(let type in result.models){
models_options.push($el("option", {textContent: `[${type[0].toUpperCase()+type.slice(1)}]`, disabled: true}))
models_options.push(...result.models[type].map((m)=> {
let path = m.path
const [subfolder, name] = path.split("/")
if(path.indexOf("ThreeViewModels/") === -1){
path = new URL(path, import.meta.url).href
} else {
path = `/view?filename=${name}&type=input&subfolder=${subfolder}`
}
this.listModels.push(name)
return $el("option", {value: path, textContent: m.name, dataset: {path: m.path}})
}))
}
threeCanvasList.append(...models_options)
}
// Get models
return fetch("/lth/models", { cache: "no-store" }).then((resp)=> resp?.json()).then((result)=>{
if(result?.models){
addOptionsEl(result)
}
}).catch((err)=>{
console.log("Error loading list, loading default list!")
fetch("./models_default.json", { cache: "no-store" }).then((resp)=>resp.json()).then((result)=>{
if(result){
addOptionsEl(result)
}
})
});
}
createPanel(){
// Add panel widget
const panelWrapper = $el("div.threeCanvasPanelWrapper", {}, [
$el(
@@ -127,6 +177,32 @@ export class ThreeCanvas {
textContent: "X",
onclick: (e) => this.clear(true),
}),
$el("select.threeCanvasListModels", {
onchange: (e) => {
const target = e.target
this.loaderGltf.load(target.value, ( glb ) => {
this.addModel( glb );
// Save currentModel path
this.savedData.currentModel = target.options[target.selectedIndex].dataset?.path
panelWrapper.parentElement.dispatchEvent( new CustomEvent("threeview_model_added", { bubbles: true, detail: {currentModel: this.savedData.currentModel}}) )
},
( data ) => {/* console.log( `Loaded data: ${data.loaded}/${data.total}` */},
( err ) => {
console.log( err );
this.addHeadTest();
})
},
}),
$el("button.threeCanvasListModelsRefresh", {
textContent: "↺",
onclick: (e)=>{
e.target.style.disabled = true
this.getModelRequest(panelWrapper).then(()=> {e.target.style.disabled = false})
}
}),
$el("button.threeCanvasButton.threeCanvasSize", {
textContent: "Canvas size",
onclick: (e) => {
@@ -173,7 +249,9 @@ export class ThreeCanvas {
})]),
]
),
]);
]);
this.getModelRequest(panelWrapper)
return panelWrapper
}
@@ -445,6 +523,11 @@ export class ThreeCanvas {
this.directGlb( content, fname, params );
if(this.listModels.includes(fname)){
console.log(`Model name ${fname} already exist in the list loaded models!`)
return;
}
// Load file to server
const body = new FormData();
body.append("image", new Blob([content]), fname);
@@ -580,7 +663,6 @@ export class ThreeCanvas {
// update camera and render
this.setCamera( pos, center, near, far );
}
/*addObjectToScene(type, parameters = {}) {
+17 -13
View File
@@ -1,11 +1,15 @@
.threeWrapper {
display: grid;
grid-template-columns: 1fr;
gap: 5px;
display: grid;
grid-template-columns: 1fr;
gap: 5px;
}
.threeWrapper3Views{
grid-template-columns: 1fr 1fr;
.threeCanvasListModels {
max-width: 100px;
}
.threeWrapper3Views {
grid-template-columns: 1fr 1fr;
}
.threeCanvasPanelWrapper {
@@ -41,7 +45,7 @@
}
.wrapperThreeViewCanvas {
position: relative;
position: relative;
margin: 0px;
padding: 0px;
display: block;
@@ -49,7 +53,7 @@
border-width: 1px;
}
.lil-gui {
.lil-gui {
--padding: 2px;
--spacing: 2px;
--widget-height: 14px;
@@ -58,14 +62,14 @@
--widget-border-radius: 0px;
--slider-input-min-width: 30px;
--color-input-min-width: 30px;
--width:150px;
--name-width:30%;
--background-color:none;
--title-background-color:none;
--width: 150px;
--name-width: 30%;
--background-color: none;
--title-background-color: none;
--text-color: #ebebeb;
--title-text-color: #ebebeb;
--widget-color: rgba(20,20,30,0.6);
--hover-color: rgba(30,30,50,0.8);
--widget-color: rgba(20, 20, 30, 0.6);
--hover-color: rgba(30, 30, 50, 0.8);
--focus-color: #595959;
--number-color: #2cc9ff;
--string-color: #a2db3c;
+56
View File
@@ -0,0 +1,56 @@
{
"models": {
"default": [
{
"name": "column.glb",
"size": 35128,
"path": "./assets/column.glb"
},
{
"name": "dog.glb",
"size": 37312,
"path": "./assets/dog.glb"
},
{
"name": "eva00.glb",
"size": 450112,
"path": "./assets/eva00.glb"
},
{
"name": "eva01.glb",
"size": 446184,
"path": "./assets/eva01.glb"
},
{
"name": "eva02.glb",
"size": 450816,
"path": "./assets/eva02.glb"
},
{
"name": "head.glb",
"size": 2346688,
"path": "./assets/head.glb"
},
{
"name": "head2.glb",
"size": 125672,
"path": "./assets/head2.glb"
},
{
"name": "lee.glb",
"size": 107424,
"path": "./assets/lee.glb"
},
{
"name": "man.glb",
"size": 462112,
"path": "./assets/man.glb"
},
{
"name": "woman.glb",
"size": 465272,
"path": "./assets/woman.glb"
}
]
}
}
+18 -7
View File
@@ -61,7 +61,7 @@ async function widgetThreeJS(node, nodeData, inputData, app, params = {}) {
widget = node.addDOMWidget("threeCanvas", "custom_widget", threeCanvas.threeWrapper, {
getValue() {
return threeCanvas.getSavedOptions();
},
},
});
const origDraw = widget.draw;
@@ -85,8 +85,13 @@ async function widgetThreeJS(node, nodeData, inputData, app, params = {}) {
widget.threeCanvas = threeCanvas;
widget.threeWrapper = threeCanvas.threeWrapper;
document.body.addEventListener("threeview_model_added", ({detail})=>{
// console.log("Detail:", detail?.currentModel)
})
widget.callback = () => {};
// Note: If the node is collapsed, the draw method does not work and the canvas will not update.
// const animator = () => {
// threeCanvas.update(node.size[0]);
@@ -240,12 +245,18 @@ app.registerExtension({
// Loading last model added
if(currentModel){
const [subfolder, name] = currentModel.split("/")
const urlData = await api.apiURL(
`/view?filename=${encodeURIComponent(
name
)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
);
let urlData = ""
if(currentModel.indexOf("assets/") === -1){
const [subfolder, name] = currentModel.split("/")
urlData = await api.apiURL(
`/view?filename=${encodeURIComponent(
name
)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
);
} else {
urlData = new URL(currentModel, import.meta.url).href
}
await threeCnavasWidget.loaderGltf.load(urlData, ( glb ) => {
threeCnavasWidget.addModel( glb );
+86 -3
View File
@@ -12,6 +12,7 @@ from server import PromptServer
import time
from aiohttp import web
import folder_paths
import json
# Message Handling
@@ -42,14 +43,96 @@ import folder_paths
DEBUG = True
THREEVIEW_DICT = {} # ThreeView dict instances
# Directory threejs
extension_threejs = os.path.dirname(os.path.realpath(__file__))
# Support extension models
support_ext = ("glb",)
def getModelsInDir(folder, path):
list_models = []
for model in os.scandir(folder):
if not model.is_file():
continue
model_file_size = model.stat().st_size
if model_file_size == 0:
continue
model_file = model.name.split(".")
ext = model_file[-1]
if ext not in support_ext:
continue
pathFile = os.path.join(path, model.name)
# model_file_name = ".".join(model_file[:-1])
list_models.append({"name": model.name, "size": model_file_size, "path": pathFile})
return list_models
def createJsonFileStartup():
models_js_dir = os.path.join(extension_threejs, "js")
models_assets_dir = os.path.join(models_js_dir, "assets")
# Assets directory
if not os.path.exists(models_assets_dir):
return
models = {
"models": {
"default": getModelsInDir(models_assets_dir, "./assets/"),
}
}
saveFilePath = os.path.join(models_js_dir, "models_default.json")
with open(saveFilePath, "w+", encoding="utf-8") as file:
file.write(json.dumps(models, indent=4))
# Create on startup default models json file in assets directory
createJsonFileStartup()
@PromptServer.instance.routes.get("/lth/models")
async def getModels(request):
try:
models_assets_dir = os.path.join(extension_threejs, "js", "assets")
models_input_dir = os.path.join(folder_paths.get_input_directory(), "ThreeViewModels")
models = {
"default": [],
"loaded": []
}
# Assets directory
if os.path.exists(models_assets_dir):
models["default"] = getModelsInDir(models_assets_dir, "assets/")
else:
print(f"ThreeView: Path {models_assets_dir} not exists!")
# Input > ThreeViewModels directory
if os.path.exists(models_input_dir):
models["loaded"] = getModelsInDir(models_input_dir, "ThreeViewModels/")
else:
print(f"ThreeView: Path {models_input_dir} not exists!")
return web.json_response({"status":"ok", "models": models}, status=200)
except Exception as err:
print("ThreeView: Get models error", err)
return web.json_response({"status":"error"}, status=500)
@PromptServer.instance.routes.get("/api/lth/")
async def indexPage(request):
pathIndex = os.path.join(os.path.dirname(os.path.realpath(__file__)), "js", "index.html")
pathIndex = os.path.join(extension_threejs, "js", "index.html")
response = web.FileResponse(pathIndex)
response.headers['Cache-Control'] = 'no-cache'
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "0"
return response
return response
@PromptServer.instance.routes.post("/lth/save_complete")
async def checkSaveImage(request):
@@ -61,7 +144,7 @@ async def checkSaveImage(request):
return web.json_response({"status": "Complete"}, status=200)
return web.json_response({"status": "Error invalid unique_id"}, status=400)
def waitSaveImage(unique_id, time_out=100):
for _ in range(time_out):