diff --git a/Cargo.lock b/Cargo.lock index a222702..8062d1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,6 +66,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "argmap" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c007f456524f3f1e06e8929963425b5dadf8616d9110ea0809840c16997994e9" + [[package]] name = "atk" version = "0.18.0" @@ -325,6 +331,7 @@ dependencies = [ name = "conduct" version = "0.1.0" dependencies = [ + "argmap", "clap", "colored", "dunce", diff --git a/Cargo.toml b/Cargo.toml index 97caa34..e77bac3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ opt-level = "s" # Optimize for binary size strip = true # Remove debug symbols [dependencies] +argmap = "1.1.2" clap = { version = "4.5.20", features=["derive"] } dunce = "1.0.5" enum_dispatch = "0.3.13" diff --git a/example/basic/manifest.yaml b/example/basic/manifest.yaml index b2101bb..148a08a 100644 --- a/example/basic/manifest.yaml +++ b/example/basic/manifest.yaml @@ -49,6 +49,8 @@ departments: blender: exports: - .mesh.blend + - .shadergraph.blend + - .animbake.abc assets: 3d: @@ -56,7 +58,9 @@ assets: - $template: departments: model: - - mesh + - !shot_local mesh + - lod + - test lookdev: - shadergraph - defaultCubeA: diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..2b8bd46 --- /dev/null +++ b/flake.lock @@ -0,0 +1,93 @@ +{ + "nodes": { + "blender-bin": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "dir": "blender", + "lastModified": 1734963688, + "narHash": "sha256-FTbHXdo1O5avRbOkSzGWI52V0sRX+B9Ka2trEiuaewk=", + "owner": "edolstra", + "repo": "nix-warez", + "rev": "91297beb20864e611af6017c5eeef1b4564d7157", + "type": "github" + }, + "original": { + "id": "blender-bin", + "type": "indirect" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1733808091, + "narHash": "sha256-KWwINTQelKOoQgrXftxoqxmKFZb9pLVfnRvK270nkVk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a0f3e10d94359665dba45b71b4227b0aeb851f8e", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-24.11", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1738422722, + "narHash": "sha256-Q4vhtbLYWBUnjWD4iQb003Lt+N5PuURDad1BngGKdUs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "102a39bfee444533e6b4e8611d7e92aa39b7bec1", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "blender-bin": "blender-bin", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ab2f3f8 --- /dev/null +++ b/flake.nix @@ -0,0 +1,48 @@ +{ + description = "my project description"; + + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, blender-bin, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: let + pkgs = import nixpkgs { + system = system; + }; + + libraries = with pkgs; [ + rustc + cargo + rustfmt + rust-analyzer + clippy + nodejs + webkitgtk_4_1 + pkg-config + libsoup_3 + gtk3 + webkitgtk_4_1 + cairo + gdk-pixbuf + libsoup_3 + gsettings-desktop-schemas + glib + ]; + + in + { + devShell = pkgs.mkShell { + buildInputs = libraries; + LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath libraries}"; + SHELL = "${pkgs.bashInteractive}/bin/bash"; + WEBKIT_DISABLE_COMPOSITING_MODE=1; + + # see: https://github.com/tauri-apps/tauri/issues/7354 + shellHook = with pkgs; '' + export XDG_DATA_DIRS=${gsettings-desktop-schemas}/share/gsettings-schemas/${gsettings-desktop-schemas.name}:${gtk3}/share/gsettings-schemas/${gtk3.name}:$XDG_DATA_DIRS; + ''; + }; + } + ); +} diff --git a/integration/common/conduct.py b/integration/common/conduct.py index 47d6df8..15d67ac 100644 --- a/integration/common/conduct.py +++ b/integration/common/conduct.py @@ -38,11 +38,11 @@ def get_summary(self): return summary def setup(self, file_format): - args = ["dialog", "create_setup", "--file-format", file_format] + args = ["dialog", "create_setup", "--", "--file-format", file_format] return self.run_process(args) def dialog_load_asset(self, department, shot=None, asset=None ): - args = ["dialog", "load_asset", "--program", self.current_program, "--department", department] + args = ["dialog", "load_asset", "--", "--program", self.current_program, "--department", department] if shot != None and shot != "": args.append("--shot") args.append(shot) diff --git a/src/core/commands/command_dialog.rs b/src/core/commands/command_dialog.rs index 89d9958..08e1e16 100644 --- a/src/core/commands/command_dialog.rs +++ b/src/core/commands/command_dialog.rs @@ -6,21 +6,13 @@ use query_string_builder::QueryString; use crate::{core::project::Project, gui}; use serde::{Deserialize, Serialize}; -use super::{args::CommonArgs, error::CommandError, Command}; +use super::{error::CommandError, Command}; #[derive(Debug, Args, Serialize, Deserialize)] pub struct DialogArgs { - #[command(flatten)] - #[serde(flatten)] - pub common: CommonArgs, - kind: String, - #[arg(short, long)] - program: Option, - - #[arg(short, long)] - file_format: Option, + extras: Vec, } pub struct DialogOptions { @@ -35,12 +27,14 @@ impl Command for DialogArgs { self, project: &RwLock, ) -> Result, CommandError> { - let args = QueryString::dynamic() - .with_opt_value("department", self.common.department) - .with_opt_value("asset", self.common.asset) - .with_opt_value("shot", self.common.shot) - .with_opt_value("program", self.program) - .with_opt_value("file_format", self.file_format); + let (_, argv) = argmap::parse(self.extras.iter()); + log::debug!("Got extras: {:?}", argv); + + let mut args = QueryString::dynamic(); + for pair in argv.iter() { + let value = pair.1.get(0).unwrap(); + _ = args.push(pair.0.clone(), value.clone()) + } gui::gui( project.read().unwrap().clone(), diff --git a/ui/src/api.ts b/ui/src/api.ts index aa7ca6a..acbaa6e 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -1,4 +1,4 @@ -import { AssetTreeCategory, ListAssetsResult, ListShotsResult, SetupResult, SummaryResponse } from "./bindings/bindings_gen"; +import { AssetTreeCategory, ListAssetsResult, ListElementsResult, ListExportFormatsResult, ListShotsResult, SetupResult, SummaryResponse } from "./bindings/bindings_gen"; declare global { @@ -45,8 +45,15 @@ export async function getSummary(): Promise { return await result.json() as SummaryResponse } -export async function doExport(): Promise { - let result = await get("api/v1/command/export?asset=suzanneA&department=model") +export async function doExport(department: string, asset: string, element: string, shot: string | null | undefined, from: string, file_format: string): Promise { + let result = await get("api/v1/command/export", { + "department": department, + "asset": asset, + "element": element, + "shot": shot, + "from": from, + "file_format": file_format + }) return await result.json() as SummaryResponse } @@ -72,6 +79,29 @@ export async function listAssets(department_filter: null | string = null): Promi return await result.json() as ListAssetsResult } +export async function listElements(asset: string, department: null | string = null, load: boolean = false): Promise { + let result = await get("api/v1/command/list_elements", { + "department": department, + "asset": asset, + "load": load, + }) + + let data = await result.json() + console.log(data) + return data as ListElementsResult +} + +export async function listExportFormats(department: string, program: string,): Promise { + let result = await get("api/v1/command/list_export_formats", { + "department": department, + "from": program + }) + + let data = await result.json() + console.log(data) + return data as ListExportFormatsResult +} + export async function getAssetTree(department_filter: null | string = null): Promise { let result = await get("api/v1/command/get_asset_tree", { "department": department_filter diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 3dde3fa..9b4c997 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -8,6 +8,7 @@ import DialogCreateSetup from './pages/dialogs/create_setup'; import DialogLoadAsset from './pages/dialogs/load_asset'; import { ColorModeProvider } from '@kobalte/core/color-mode'; import { Component } from 'solid-js'; +import DialogExport from './pages/dialogs/export'; const root = document.getElementById('root'); @@ -29,4 +30,5 @@ render(() => ( + ), root!); diff --git a/ui/src/pages/dialogs/create_setup.tsx b/ui/src/pages/dialogs/create_setup.tsx index 6877abd..027ad0f 100644 --- a/ui/src/pages/dialogs/create_setup.tsx +++ b/ui/src/pages/dialogs/create_setup.tsx @@ -21,7 +21,7 @@ const DialogCreateSetup: Component = () => { const [shots] = createResource(listShots); const [searchParams, setSearchParams] = useSearchParams(); - const fileFormat = () => searchParams.file_format; + const fileFormat = () => searchParams['file-format']; const derivedState = () => { return { department: selectedDepartment(), asset: selectedAsset(), shot: selectedShot() } diff --git a/ui/src/pages/dialogs/export.tsx b/ui/src/pages/dialogs/export.tsx new file mode 100644 index 0000000..e4fc17c --- /dev/null +++ b/ui/src/pages/dialogs/export.tsx @@ -0,0 +1,271 @@ +import { createResource, type Component, Show, Switch, Match, createSignal, For } from 'solid-js'; + +import { cancelDialog, doExport, exitDialog, listElements, listExportFormats } from '../../api'; + +import { Button } from '../../components/ui/button'; +import { ColorModeProvider } from '@kobalte/core/color-mode'; +import { Separator } from '../../components/ui/separator'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '~/components/ui/select'; +import { Combobox, ComboboxContent, ComboboxControl, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxItemLabel, ComboboxSection, ComboboxTrigger } from '~/components/ui/combobox'; +import { Callout, CalloutContent, CalloutTitle } from '~/components/ui/callout'; +import { SetupResult } from '~/bindings/bindings_gen'; +import { useSearchParams } from '@solidjs/router'; +import { Label } from '~/components/ui/label'; +import { Checkbox } from '~/components/ui/checkbox'; +import { ToggleGroup } from '~/components/ui/toggle-group'; +import { createStore } from 'solid-js/store'; + +type ElementState = { + enabled: boolean, + fileFormat: string | null, + items: string[] +} + +const DialogExport: Component = () => { + const [searchParams, setSearchParams] = useSearchParams(); + const department = () => searchParams.department; + const shot = () => searchParams.shot; + const program = () => searchParams.program; + const asset = () => searchParams.asset; + const items = () => (searchParams.items as string).split(',') + + var defaultState = new Map() + let prev = searchParams['prev-state'] + if (prev != undefined) { + console.log(searchParams['prev-state']) + let stateObject = JSON.parse(searchParams['prev-state'] as string) + console.log(stateObject) + + defaultState = new Map(Object.entries(stateObject)); + } + + const [data, setData] = createSignal>(defaultState) + + const [elements] = createResource(() => listElements(asset() as string, department() as any)); + const [formats] = createResource(() => listExportFormats(department() as string, program() as string)); + + const contextLabel = () => { + return [shot(), department(), asset()].filter((x) => !!x).join(' / ') + } + + async function done() { + let export_dept = department() as string + let export_shot = shot() + let export_asset = asset() as string + let export_program = program() as string + + let export_results = [] + + for (let [key, value] of data()) { + if (!value.enabled) { + continue; + } + console.log(value) + let export_result = await doExport( + export_dept, + export_asset, + key, + export_shot != undefined ? export_shot as string : null, + export_program, + value.fileFormat! + ) + + export_results.push({ + items: value.items, + result: export_result + }) + console.log(export_result) + + } + + const obj = Object.fromEntries(data()); + const json = JSON.stringify(obj); + const save_state = JSON.stringify(json) + + + let response = { + exports: export_results, + save_state: save_state + }; + + await exitDialog(response); + } + + var elementEntry: Component<{ element: string, }> = (props) => { + let s = data() + let state = s.get(props.element) + + let item: ElementState = state ?? { + fileFormat: null, + enabled: false, + items: [] + } + + return ( + <> + +
+ { + let elementState = { + ...item, + enabled: selected + } + + let result = new Map(s) + result.set(props.element, elementState); + + setData( + result + ) + + }} id={props.element} /> + +
+ + + + + +
+ + { + item.items.map((val, index) => { + return <> + + + }) + } + + +
+ +
+ + ) + } + + + + return ( +
+ +
+
+ Export +
+ + +
+ + + { + Object.entries(elements()!.elements).map(item => { + return elementEntry({ element: item[1] }) + }) + } + + + +
+ +
+ +
+ +
+ + +
+
+ +
+ ); +}; + + +export default DialogExport;