1 /* 2 Copyright © 2020, Inochi2D Project 3 Distributed under the 2-Clause BSD License, see LICENSE file. 4 5 Authors: Luna Nielsen 6 */ 7 module creator; 8 import inochi2d; 9 import inochi2d.core.dbg; 10 import inochi2d.core.nodes.common; 11 import creator.viewport; 12 import creator.viewport.model; 13 import creator.viewport.model.deform; 14 import creator.core; 15 import creator.core.actionstack; 16 import creator.windows; 17 import creator.atlas; 18 import creator.ext; 19 import creator.widgets.dialog; 20 21 public import creator.ver; 22 public import creator.atlas; 23 public import creator.io; 24 import creator.core.colorbleed; 25 26 import std.file; 27 import std.format; 28 import i18n; 29 30 /** 31 A project 32 */ 33 class Project { 34 /** 35 The puppet in the project 36 */ 37 Puppet puppet; 38 39 /** 40 Textures for use in the puppet 41 42 Can be rearranged 43 */ 44 Texture[] textures; 45 } 46 47 private { 48 Project activeProject; 49 Node[] selectedNodes; 50 Drawable[] drawables; 51 Parameter armedParam; 52 size_t armedParamIdx; 53 string currProjectPath; 54 string[] prevProjects; 55 } 56 57 /** 58 Edit modes 59 */ 60 enum EditMode { 61 /** 62 Model editing mode 63 */ 64 ModelEdit, 65 66 /** 67 Vertex Editing Mode 68 */ 69 VertexEdit, 70 71 /** 72 Animation Editing Mode 73 */ 74 AnimEdit, 75 76 /** 77 Model testing mode 78 */ 79 ModelTest 80 } 81 82 bool incShowVertices = true; /// Show vertices of selected parts 83 bool incShowBounds = true; /// Show bounds of selected parts 84 bool incShowOrientation = true; /// Show orientation gizmo of selected parts 85 86 /** 87 Current edit mode 88 */ 89 EditMode editMode_; 90 91 /** 92 Clears the imgui data 93 */ 94 void incClearImguiData() { 95 auto ctx = igGetCurrentContext(); 96 if (ctx) { 97 foreach(ImGuiWindow* window; ctx.Windows.Data[0..ctx.Windows.Size]) { 98 if (window) { 99 ImGuiStorage_Clear(&window.StateStorage); 100 } 101 } 102 } 103 } 104 105 /** 106 Returns the current project path 107 */ 108 string incProjectPath() { 109 return currProjectPath; 110 } 111 112 /** 113 Return a list of prior projects 114 */ 115 string[] incGetPrevProjects() { 116 return incSettingsGet!(string[])("prev_projects"); 117 } 118 119 void incAddPrevProject(string path) { 120 import std.algorithm.searching : countUntil; 121 import std.algorithm.mutation : remove; 122 string[] projects = incGetPrevProjects(); 123 124 ptrdiff_t idx = projects.countUntil(path); 125 if (idx >= 0) { 126 projects = projects.remove(idx); 127 } 128 129 // Put project to the start of the "previous" list and 130 // limit to 10 elements 131 projects = path.dup ~ projects; 132 if(projects.length > 10) projects.length = 10; 133 134 // Then save. 135 incSettingsSet("prev_projects", projects); 136 incSettingsSave(); 137 } 138 139 /** 140 Creates a new project 141 */ 142 void incNewProject() { 143 incClearImguiData(); 144 145 currProjectPath = ""; 146 editMode_ = EditMode.ModelEdit; 147 import creator.viewport : incViewportReset; 148 149 incPopWindowListAll(); 150 151 activeProject = new Project; 152 activeProject.puppet = new ExPuppet; 153 incFocusCamera(activeProject.puppet.root); 154 incSelectNode(null); 155 incDisarmParameter(); 156 157 inDbgDrawMeshVertexPoints = true; 158 inDbgDrawMeshOutlines = true; 159 inDbgDrawMeshOrientation = true; 160 161 incViewportReset(); 162 163 incActionClearHistory(); 164 incFreeMemory(); 165 166 incViewportPresentMode(editMode_); 167 } 168 169 void incResetRootNode(ref Puppet puppet) { 170 puppet.root.localTransform.translation = vec3(0, 0, 0); 171 puppet.root.localTransform.rotation = vec3(0, 0, 0); 172 puppet.root.localTransform.scale = vec2(1, 1); 173 } 174 175 void incOpenProject(string path) { 176 incClearImguiData(); 177 178 Puppet puppet; 179 180 // Load the puppet from file 181 try { 182 puppet = inLoadPuppet!ExPuppet(path); 183 } catch (Exception ex) { 184 incDialog(__("Error"), ex.msg); 185 return; 186 } 187 188 // Clear out stuff by creating a new project 189 incNewProject(); 190 191 // Set the path 192 currProjectPath = path; 193 incAddPrevProject(path); 194 195 incResetRootNode(puppet); 196 197 incActiveProject().puppet = puppet; 198 incFocusCamera(incActivePuppet().root); 199 incFreeMemory(); 200 201 incSetStatus(_("%s opened successfully.").format(currProjectPath)); 202 } 203 204 void incSaveProject(string path) { 205 import std.path : setExtension; 206 try { 207 string finalPath = path.setExtension(".inx"); 208 currProjectPath = path; 209 incAddPrevProject(finalPath); 210 211 // Remember to populate texture slots otherwise things will break real bad! 212 incActivePuppet().populateTextureSlots(); 213 214 // Write the puppet to file 215 inWriteINPPuppet(incActivePuppet(), finalPath); 216 217 incSetStatus(_("%s saved successfully.").format(currProjectPath)); 218 } catch(Exception ex) { 219 incSetStatus(_("Failed to save %s").format(currProjectPath)); 220 incDialog(__("Error"), ex.msg); 221 } 222 } 223 224 /** 225 Imports image files from a selected folder. 226 */ 227 void incImportFolder(string folder) { 228 incNewProject(); 229 230 import std.file : dirEntries, SpanMode; 231 import std.path : stripExtension, baseName; 232 233 string[] failedFiles; 234 // For each file find PNG, TGA and JPEG files and import them 235 Puppet puppet = new ExPuppet(); 236 size_t i; 237 foreach(file; dirEntries(folder, SpanMode.shallow, false)) { 238 try { 239 240 // TODO: Check for position.ini 241 242 auto tex = ShallowTexture(file); 243 inTexPremultiply(tex.data); 244 245 Part part = inCreateSimplePart(new Texture(tex), null, file.baseName.stripExtension); 246 part.zSort = -((cast(float)i++)/100); 247 puppet.root.addChild(part); 248 } catch(Exception ex) { 249 failedFiles ~= ex.msg; 250 } 251 } 252 253 if (failedFiles.length > 0) { 254 import std.array : join; 255 incDialog("ImgLoadError", format(_("The following errors occured during file loading\n%s"), failedFiles.join("\n"))); 256 } 257 258 puppet.rescanNodes(); 259 puppet.populateTextureSlots(); 260 incActiveProject().puppet = puppet; 261 incFocusCamera(incActivePuppet().root); 262 incFreeMemory(); 263 264 if (failedFiles.length > 0) incSetStatus(_("Folder import completed with errors...")); 265 else incSetStatus(_("Folder import completed...")); 266 267 } 268 269 /** 270 Imports an Inochi2D puppet 271 */ 272 void incImportINP(string file) { 273 incNewProject(); 274 Puppet puppet; 275 try { 276 277 puppet = inLoadPuppet(file); 278 incSetStatus(_("%s was imported...".format(file))); 279 } catch(Exception ex) { 280 281 incDialog(__("Error"), ex.msg); 282 incSetStatus(_("Import failed...")); 283 return; 284 } 285 incActiveProject().puppet = puppet; 286 incFocusCamera(incActivePuppet().root); 287 incFreeMemory(); 288 } 289 290 /** 291 Exports an Inochi2D Puppet 292 */ 293 void incExportINP(string file) { 294 import creator.windows.inpexport; 295 import std.path : setExtension; 296 string oFile = file.setExtension(".inp"); 297 incPushWindow(new ExportWindow(oFile)); 298 } 299 300 void incRegenerateMipmaps() { 301 302 // Allow for nice looking filtering 303 foreach(texture; incActiveProject().puppet.textureSlots) { 304 texture.genMipmap(); 305 texture.setFiltering(Filtering.Linear); 306 } 307 incSetStatus(_("Mipmap generation completed.")); 308 } 309 310 /** 311 Re-bleeds textures in a model 312 */ 313 void incRebleedTextures() { 314 incTaskAdd("Rebleed", () { 315 incTaskStatus("Bleeding textures..."); 316 foreach(i, Texture texture; activeProject.puppet.textureSlots) { 317 incTaskProgress(cast(float)i/activeProject.puppet.textureSlots.length); 318 incTaskYield(); 319 incColorBleedPixels(texture); 320 } 321 }); 322 incSetStatus(_("Texture bleeding completed.")); 323 } 324 325 /** 326 Force the garbage collector to collect model memory 327 */ 328 void incFreeMemory() { 329 import core.memory : GC; 330 GC.collect(); 331 GC.minimize(); 332 } 333 334 /** 335 Gets puppet in active project 336 */ 337 ref Puppet incActivePuppet() { 338 return activeProject.puppet; 339 } 340 341 /** 342 Gets active project 343 */ 344 ref Project incActiveProject() { 345 return activeProject; 346 } 347 348 /** 349 Gets the currently armed parameter 350 */ 351 Parameter incArmedParameter() { 352 return editMode_ == EditMode.ModelEdit ? armedParam : null; 353 } 354 355 /** 356 Gets the currently armed parameter index 357 */ 358 size_t incArmedParameterIdx() { 359 return editMode_ == EditMode.ModelEdit ? armedParamIdx : 0; 360 } 361 362 /** 363 Gets the currently selected node 364 */ 365 ref Node[] incSelectedNodes() { 366 return selectedNodes; 367 } 368 369 /** 370 Gets a list of the current drawables 371 */ 372 ref Drawable[] incDrawables() { 373 return drawables; 374 } 375 376 /** 377 Gets the currently selected root node 378 */ 379 ref Node incSelectedNode() { 380 return selectedNodes.length == 0 ? incActivePuppet.root : selectedNodes[0]; 381 } 382 383 /** 384 Arms a parameter 385 */ 386 void incArmParameter(size_t i, ref Parameter param) { 387 armedParam = param; 388 armedParamIdx = i; 389 incViewportNodeDeformNotifyParamValueChanged(); 390 incActivePuppet.enableDrivers = false; 391 incActivePuppet.resetDrivers(); 392 } 393 394 /** 395 Disarms parameter recording 396 */ 397 void incDisarmParameter() { 398 armedParam = null; 399 armedParamIdx = 0; 400 incViewportNodeDeformNotifyParamValueChanged(); 401 incActivePuppet.enableDrivers = true; 402 incActivePuppet.resetDrivers(); 403 } 404 405 /** 406 Selects a node 407 */ 408 void incSelectNode(Node n = null) { 409 if (n is null) selectedNodes.length = 0; 410 else selectedNodes = [n]; 411 incViewportModelNodeSelectionChanged(); 412 } 413 414 /** 415 Adds node to selection 416 */ 417 void incAddSelectNode(Node n) { 418 if (incArmedParameter()) return; 419 selectedNodes ~= n; 420 } 421 422 /** 423 Remove node from selection 424 */ 425 void incRemoveSelectNode(Node n) { 426 foreach(i, nn; selectedNodes) { 427 if (n.uuid == nn.uuid) { 428 import std.algorithm.mutation : remove; 429 selectedNodes = selectedNodes.remove(i); 430 } 431 } 432 } 433 434 private void incSelectAllRecurse(Node n) { 435 incAddSelectNode(n); 436 foreach(child; n.children) { 437 incSelectAllRecurse(child); 438 } 439 } 440 441 /** 442 Selects all nodes 443 */ 444 void incSelectAll() { 445 if (incArmedParameter()) return; 446 incSelectNode(); 447 foreach(child; incActivePuppet().root.children) { 448 incSelectAllRecurse(child); 449 } 450 } 451 452 /** 453 Gets whether the node is in the selection 454 */ 455 bool incNodeInSelection(Node n) { 456 foreach(i, nn; selectedNodes) { 457 if (nn is null) continue; 458 459 if (n.uuid == nn.uuid) return true; 460 } 461 462 return false; 463 } 464 465 /** 466 Focus camera at node 467 */ 468 void incFocusCamera(Node node) { 469 import creator.viewport : incViewportTargetZoom, incViewportTargetPosition; 470 if (node is null) return; 471 472 // Calculate actual center. 473 vec4 bounds = node.getCombinedBounds(); 474 if (auto drawable = cast(Drawable)node) { 475 drawable.updateBounds(); 476 bounds = drawable.bounds; 477 } 478 vec2 pos = bounds.xy+((bounds.zw - bounds.xy)*0.5); 479 480 // Focus camera to calculated center 481 incFocusCamera(node, vec2(-pos.x, -pos.y)); 482 } 483 484 /** 485 Focus camera at node 486 */ 487 void incFocusCamera(Node node, vec2 position) { 488 import creator.viewport : incViewportTargetZoom, incViewportTargetPosition; 489 if (node is null) return; 490 491 int width, height; 492 inGetViewport(width, height); 493 494 auto nt = node.transform; 495 496 vec4 bounds = node.getCombinedBounds(); 497 vec2 boundsSize = bounds.zw - bounds.xy; 498 if (auto drawable = cast(Drawable)node) { 499 boundsSize = drawable.bounds.zw - drawable.bounds.xy; 500 } else { 501 nt.translation = vec3(bounds.x + ((bounds.z-bounds.x)*0.5), bounds.y + ((bounds.w-bounds.y)*0.5), 0); 502 } 503 504 505 float largestViewport = min(width, height); 506 float largestBounds = max(boundsSize.x, boundsSize.y); 507 508 float factor = largestViewport/largestBounds; 509 incViewportTargetZoom = clamp(factor*0.90, 0.1, 2.5); 510 511 incViewportTargetPosition = vec2( 512 position.x, 513 position.y 514 ); 515 } 516 517 /** 518 Gets the current editing mode 519 */ 520 EditMode incEditMode() { 521 return editMode_; 522 } 523 524 /** 525 Sets the current editing mode 526 */ 527 void incSetEditMode(EditMode editMode, bool unselect = true) { 528 incViewportWithdrawMode(editMode_); 529 530 if (armedParam) { 531 armedParam.value = armedParam.getClosestKeypointValue(armedParam.value); 532 } 533 if (unselect) incSelectNode(null); 534 if (editMode != EditMode.ModelEdit) { 535 drawables = activeProject.puppet.findNodesType!Drawable(activeProject.puppet.root); 536 } 537 editMode_ = editMode; 538 539 incViewportPresentMode(editMode_); 540 }