From fb8070bd2bb3d7c83603cd74bec4e0c13cbd4455 Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:05:55 +1030 Subject: [PATCH 1/6] initial inkscape integration --- example/basic/manifest.yaml | 9 ++++ .../setup/asset/design/iconA/iconA_design.svg | 5 +++ integration/inkscape/conduct | 1 + integration/inkscape/conduct_create_setup.inx | 15 +++++++ integration/inkscape/conduct_create_setup.py | 45 +++++++++++++++++++ integration/inkscape/shell.nix | 12 +++++ 6 files changed, 87 insertions(+) create mode 100644 example/basic/setup/asset/design/iconA/iconA_design.svg create mode 120000 integration/inkscape/conduct create mode 100644 integration/inkscape/conduct_create_setup.inx create mode 100644 integration/inkscape/conduct_create_setup.py create mode 100644 integration/inkscape/shell.nix diff --git a/example/basic/manifest.yaml b/example/basic/manifest.yaml index b2101bb..72b95c9 100644 --- a/example/basic/manifest.yaml +++ b/example/basic/manifest.yaml @@ -49,6 +49,9 @@ departments: blender: exports: - .mesh.blend + design: + programs: + inkscape: {} assets: 3d: @@ -68,6 +71,12 @@ assets: departments: layout: - !depends(defaultCubeA;defaultCubeB) cubeInstancer + 2d: + icons: + - iconA: + departments: + design: + - vector shots: '103': diff --git a/example/basic/setup/asset/design/iconA/iconA_design.svg b/example/basic/setup/asset/design/iconA/iconA_design.svg new file mode 100644 index 0000000..9bad70b --- /dev/null +++ b/example/basic/setup/asset/design/iconA/iconA_design.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/integration/inkscape/conduct b/integration/inkscape/conduct new file mode 120000 index 0000000..8332399 --- /dev/null +++ b/integration/inkscape/conduct @@ -0,0 +1 @@ +../common/ \ No newline at end of file diff --git a/integration/inkscape/conduct_create_setup.inx b/integration/inkscape/conduct_create_setup.inx new file mode 100644 index 0000000..fbd9261 --- /dev/null +++ b/integration/inkscape/conduct_create_setup.inx @@ -0,0 +1,15 @@ + + + Create Setup + com.lagmachine.conduct.create_setup + + + all + + + + + + \ No newline at end of file diff --git a/integration/inkscape/conduct_create_setup.py b/integration/inkscape/conduct_create_setup.py new file mode 100644 index 0000000..9358aab --- /dev/null +++ b/integration/inkscape/conduct_create_setup.py @@ -0,0 +1,45 @@ +import inkex +from inkex import command +import os +from conduct import conduct +import subprocess + +class ConductCreateSetup(inkex.EffectExtension): + + def add_arguments(self, pars): + pars.add_argument("-m", "--manifest", default="", help="Manifest File Path") + + def effect(self): + path = self.options.manifest + c = conduct.get_from_manifest_path(path, "inkscape") + result = c.setup() + + if result['result'] != 'ok': + return + + dialog_data = result['data'] + path = os.path.join(dialog_data['path'], dialog_data['file_name'] + ".svg") + + self.svg.set("com.lagmachine.conduct.asset", dialog_data['asset']) + self.svg.set("com.lagmachine.conduct.department", dialog_data['department']) + if dialog_data['shot'] is not None: + self.svg.set("com.lagmachine.conduct.shot", dialog_data['shot']) + + # write to new file, and open it in a new instance of inkscape + # this is the best i can do, there is no function for changing the current file to a different location + data = self.svg.tostring().decode('utf-8') + + with open(path, mode='w') as f: + f.write(data) + + exe = inkex.command.which('inkscape') + + #if we could find a good way to kill the original inkscape instance after starting the new one, that would be ideal + if os.name == 'nt': + creation_flags = subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS + proc = subprocess.Popen([exe, path], creationflags=creation_flags, start_new_session=True) + else: + proc = subprocess.Popen([exe, path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) + +if __name__ == '__main__': + ConductCreateSetup().run() \ No newline at end of file diff --git a/integration/inkscape/shell.nix b/integration/inkscape/shell.nix new file mode 100644 index 0000000..8019118 --- /dev/null +++ b/integration/inkscape/shell.nix @@ -0,0 +1,12 @@ + +let pkgs = import {}; + +shell = pkgs.mkShell { + buildInputs = with pkgs; [ + inkscape + ]; + + WEBKIT_DISABLE_COMPOSITING_MODE=1; +}; + +in shell From ae8d4368ce917a5ef0137a57a033c5e4c6a72e78 Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:35:21 +1030 Subject: [PATCH 2/6] Update conduct_create_setup.py --- integration/inkscape/conduct_create_setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration/inkscape/conduct_create_setup.py b/integration/inkscape/conduct_create_setup.py index 9358aab..58aeb7b 100644 --- a/integration/inkscape/conduct_create_setup.py +++ b/integration/inkscape/conduct_create_setup.py @@ -10,15 +10,14 @@ def add_arguments(self, pars): pars.add_argument("-m", "--manifest", default="", help="Manifest File Path") def effect(self): - path = self.options.manifest - c = conduct.get_from_manifest_path(path, "inkscape") - result = c.setup() + manifest_path = self.options.manifest + c = conduct.get_from_manifest_path(manifest_path, "inkscape") + result = c.setup('.svg') if result['result'] != 'ok': return dialog_data = result['data'] - path = os.path.join(dialog_data['path'], dialog_data['file_name'] + ".svg") self.svg.set("com.lagmachine.conduct.asset", dialog_data['asset']) self.svg.set("com.lagmachine.conduct.department", dialog_data['department']) @@ -29,6 +28,7 @@ def effect(self): # this is the best i can do, there is no function for changing the current file to a different location data = self.svg.tostring().decode('utf-8') + path = dialog_data['path'] with open(path, mode='w') as f: f.write(data) From 59788eeacc68d585d032588bcd2c5b8f843bb3c7 Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Mon, 3 Feb 2025 23:06:35 +1030 Subject: [PATCH 3/6] improve logging --- integration/common/conduct.py | 16 ++++++++++------ integration/inkscape/conduct_create_setup.py | 13 +++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/integration/common/conduct.py b/integration/common/conduct.py index 47d6df8..f672f5d 100644 --- a/integration/common/conduct.py +++ b/integration/common/conduct.py @@ -4,6 +4,9 @@ import subprocess import json +def log(info): + print(info) + class Conduct: conduct_exe = "" current_program = "" @@ -24,12 +27,13 @@ def run_process(self, args): startupinfo.dwFlags = subprocess.CREATE_NO_WINDOW creation_flags = subprocess.CREATE_NO_WINDOW - print("Executing: " + str(args)) + log("Executing: " + str(args)) - process=subprocess.Popen(args, cwd=os.path.dirname(self.conduct_exe), startupinfo=startupinfo, stdout=subprocess.PIPE, encoding='utf-8', creationflags=creation_flags) + process=subprocess.Popen(args, cwd=os.path.dirname(self.conduct_exe), startupinfo=startupinfo, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, encoding='utf-8', creationflags=creation_flags) data = process.communicate()[0] - print(data) + + log(data) return json.loads(data) @@ -65,7 +69,7 @@ def export(self, department, format, asset, element, shot=None): return self.run_process(args) def get_from_manifest_path(manifest_path, current_program): - print("Getting exe from manifest path: " + manifest_path) + log("Getting exe from manifest path: " + manifest_path) dir_path = os.path.dirname(manifest_path) exe = "conduct" @@ -76,7 +80,7 @@ def get_from_manifest_path(manifest_path, current_program): return Conduct(path, current_program) def find_from_current_path(current_file, current_program): - print("Looking for conduct path for file: " + current_file) + log("Looking for conduct path for file: " + current_file) path = os.path.dirname(current_file) while path != "": checks = [ @@ -86,7 +90,7 @@ def find_from_current_path(current_file, current_program): for check in checks: if os.path.isfile(check): - print("found:" + check) + log("found:" + check) return get_from_manifest_path(check, current_program) path = os.path.dirname(path) diff --git a/integration/inkscape/conduct_create_setup.py b/integration/inkscape/conduct_create_setup.py index 58aeb7b..277e1df 100644 --- a/integration/inkscape/conduct_create_setup.py +++ b/integration/inkscape/conduct_create_setup.py @@ -4,13 +4,21 @@ from conduct import conduct import subprocess +def log_stub(info): + pass + +def log(info): + inkex.utils.debug(info) + class ConductCreateSetup(inkex.EffectExtension): def add_arguments(self, pars): pars.add_argument("-m", "--manifest", default="", help="Manifest File Path") + def effect(self): manifest_path = self.options.manifest + conduct.log = log_stub c = conduct.get_from_manifest_path(manifest_path, "inkscape") result = c.setup('.svg') @@ -34,6 +42,11 @@ def effect(self): exe = inkex.command.which('inkscape') + if(exe.startswith('/tmp/.mount')): + log("Detected running in an AppImage, this process will hang until the new instance is closed!") + inkex.command.inkscape(path) + return + #if we could find a good way to kill the original inkscape instance after starting the new one, that would be ideal if os.name == 'nt': creation_flags = subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS From 9e8dc7e2260ca82db668578d7cfddc8c287444b6 Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Mon, 3 Feb 2025 23:14:41 +1030 Subject: [PATCH 4/6] start working on export script --- example/basic/manifest.yaml | 7 ++- .../setup/asset/design/iconA/iconA_design.svg | 56 +++++++++++++++++-- integration/inkscape/conduct_export.inx | 14 +++++ integration/inkscape/conduct_export.py | 33 +++++++++++ 4 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 integration/inkscape/conduct_export.inx create mode 100644 integration/inkscape/conduct_export.py diff --git a/example/basic/manifest.yaml b/example/basic/manifest.yaml index 72b95c9..175d217 100644 --- a/example/basic/manifest.yaml +++ b/example/basic/manifest.yaml @@ -12,6 +12,9 @@ programs: .glb: import_glb_mesh.py .shadergraph.blend: import_blend_shadergraph.py .mesh.blend: import_blend_library_mesh.py + inkscape: + exports: + .png: export_png.py departments: anim: @@ -51,7 +54,9 @@ departments: - .mesh.blend design: programs: - inkscape: {} + inkscape: + exports: + - .png assets: 3d: diff --git a/example/basic/setup/asset/design/iconA/iconA_design.svg b/example/basic/setup/asset/design/iconA/iconA_design.svg index 9bad70b..e806e21 100644 --- a/example/basic/setup/asset/design/iconA/iconA_design.svg +++ b/example/basic/setup/asset/design/iconA/iconA_design.svg @@ -1,5 +1,51 @@ - - - - - \ No newline at end of file + + + + + + + + + + diff --git a/integration/inkscape/conduct_export.inx b/integration/inkscape/conduct_export.inx new file mode 100644 index 0000000..8f1a0f6 --- /dev/null +++ b/integration/inkscape/conduct_export.inx @@ -0,0 +1,14 @@ + + + Export + com.lagmachine.conduct.export + + all + + + + + + \ No newline at end of file diff --git a/integration/inkscape/conduct_export.py b/integration/inkscape/conduct_export.py new file mode 100644 index 0000000..d45988b --- /dev/null +++ b/integration/inkscape/conduct_export.py @@ -0,0 +1,33 @@ +import inkex +from inkex import command +import os +from conduct import conduct +import subprocess + +def log_stub(info): + pass + +def log(info): + inkex.utils.debug(info) + +class SvgReader(inkex.extensions.InputExtension): + pass + +class ConductCreateSetup(inkex.EffectExtension): + + def effect(self): + conduct.log = log_stub + + file_path = os.path.join(self.svg_path(), self.svg.name) + department = self.svg.get("com.lagmachine.conduct.department") + + c = conduct.find_from_current_path(file_path, "inkscape") + formats = c.list_export_formats(department) + + log(file_path) + log(formats) + return + + +if __name__ == '__main__': + ConductCreateSetup().run() \ No newline at end of file From cd12969cebaf4466dabdb8071900ea81b3f1d4ac Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Tue, 4 Feb 2025 19:18:46 +1030 Subject: [PATCH 5/6] implement export dialog --- example/basic/manifest.yaml | 5 +- .../basic/scripts/inkscape/export_png_1024.py | 33 +++++ .../basic/scripts/inkscape/export_png_512.py | 33 +++++ .../setup/asset/design/iconA/iconA_design.svg | 130 ++++++++++++++++-- integration/common/conduct.py | 12 ++ integration/inkscape/conduct_export.py | 54 +++++++- 6 files changed, 253 insertions(+), 14 deletions(-) create mode 100644 example/basic/scripts/inkscape/export_png_1024.py create mode 100644 example/basic/scripts/inkscape/export_png_512.py diff --git a/example/basic/manifest.yaml b/example/basic/manifest.yaml index 175d217..c035b74 100644 --- a/example/basic/manifest.yaml +++ b/example/basic/manifest.yaml @@ -14,7 +14,8 @@ programs: .mesh.blend: import_blend_library_mesh.py inkscape: exports: - .png: export_png.py + .png: export_png_512.py + .2x.png: export_png_1024.py departments: anim: @@ -57,6 +58,7 @@ departments: inkscape: exports: - .png + - .2x.png assets: 3d: @@ -82,6 +84,7 @@ assets: departments: design: - vector + - 2x shots: '103': diff --git a/example/basic/scripts/inkscape/export_png_1024.py b/example/basic/scripts/inkscape/export_png_1024.py new file mode 100644 index 0000000..f4f613a --- /dev/null +++ b/example/basic/scripts/inkscape/export_png_1024.py @@ -0,0 +1,33 @@ + +class InkscapeDataExport(): + + def log(self, object): + import inkex + inkex.utils.debug(object) + + def export(self, effect_context, directory=None, file_name=None, extension=None, items=None, ): + from tempfile import TemporaryDirectory + import inkex + import os + + with TemporaryDirectory(prefix='inkscape-command-') as tmpdir: + + svg_file = inkex.command.write_svg(effect_context.svg, tmpdir, 'input.svg') + output = os.path.join(directory, file_name + extension) + + pages = effect_context.svg.namedview.get_pages() + index = pages.index(items[0]) + + page = items[0] + + height = 1024 + ratio = page.width / page.height + width = round(ratio * height) + + out = inkex.command.inkscape(svg_file, + "--export-filename=%s" % output, + '--export-type=png', + '--export-width=%d' % width, + '--export-height=%d' % height, + '--export-page=%d' % (index + 1)) + \ No newline at end of file diff --git a/example/basic/scripts/inkscape/export_png_512.py b/example/basic/scripts/inkscape/export_png_512.py new file mode 100644 index 0000000..4a6c8fc --- /dev/null +++ b/example/basic/scripts/inkscape/export_png_512.py @@ -0,0 +1,33 @@ + +class InkscapeDataExport(): + + def log(self, object): + import inkex + inkex.utils.debug(object) + + def export(self, effect_context, directory=None, file_name=None, extension=None, items=None, ): + from tempfile import TemporaryDirectory + import inkex + import os + + with TemporaryDirectory(prefix='inkscape-command-') as tmpdir: + + svg_file = inkex.command.write_svg(effect_context.svg, tmpdir, 'input.svg') + output = os.path.join(directory, file_name + extension) + + pages = effect_context.svg.namedview.get_pages() + index = pages.index(items[0]) + + page = items[0] + + height = 512 + ratio = page.width / page.height + width = round(ratio * height) + + out = inkex.command.inkscape(svg_file, + "--export-filename=%s" % output, + '--export-type=png', + '--export-width=%d' % width, + '--export-height=%d' % height, + '--export-page=%d' % (index + 1)) + \ No newline at end of file diff --git a/example/basic/setup/asset/design/iconA/iconA_design.svg b/example/basic/setup/asset/design/iconA/iconA_design.svg index e806e21..351257e 100644 --- a/example/basic/setup/asset/design/iconA/iconA_design.svg +++ b/example/basic/setup/asset/design/iconA/iconA_design.svg @@ -11,6 +11,7 @@ com.lagmachine.conduct.department="design" sodipodi:docname="iconA_design.svg" inkscape:version="1.4 (e7c3feb, 2024-10-09)" + com.lagmachine.conduct.export_save_state=""{\"vector\":{\"fileFormat\":\".png\",\"enabled\":true,\"items\":[\"pageA\"]},\"alt\":{\"fileFormat\":\".png\",\"enabled\":false,\"items\":[\"pageB\"]},\"alt2\":{\"fileFormat\":\".png\",\"enabled\":false,\"items\":[\"pageC\"]},\"2x\":{\"fileFormat\":\".2x.png\",\"enabled\":true,\"items\":[\"pageA\"]}}"" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" @@ -25,15 +26,39 @@ inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" - inkscape:zoom="1.0324986" - inkscape:cx="397.09497" - inkscape:cy="561.7441" - inkscape:window-width="1239" - inkscape:window-height="1217" - inkscape:window-x="1784" - inkscape:window-y="107" + inkscape:zoom="0.42588675" + inkscape:cx="463.73831" + inkscape:cy="717.32685" + inkscape:window-width="1725" + inkscape:window-height="1006" + inkscape:window-x="193" + inkscape:window-y="400" inkscape:window-maximized="0" - inkscape:current-layer="layer1" /> + inkscape:current-layer="layer1"> + + + + + + + + + + + + + + + + diff --git a/integration/common/conduct.py b/integration/common/conduct.py index f672f5d..6fed68e 100644 --- a/integration/common/conduct.py +++ b/integration/common/conduct.py @@ -57,6 +57,18 @@ def dialog_load_asset(self, department, shot=None, asset=None ): return self.run_process(args) + def dialog_export(self, department, asset, items, prev_state=None, shot=None, ): + args = ["dialog", "export", "--", "--program", self.current_program, "--department", department, "--asset", asset, "--items", items ] + if shot != None and shot != "": + args.append("--shot") + args.append(shot) + + if prev_state != None and prev_state != "": + args.append("--prev-state") + args.append(prev_state) + + return self.run_process(args) + def list_export_formats(self, department): return self.run_process(["list-export-formats", "--from", self.current_program, "--department", department]) diff --git a/integration/inkscape/conduct_export.py b/integration/inkscape/conduct_export.py index d45988b..cfda649 100644 --- a/integration/inkscape/conduct_export.py +++ b/integration/inkscape/conduct_export.py @@ -3,6 +3,8 @@ import os from conduct import conduct import subprocess +import json +import inspect def log_stub(info): pass @@ -13,21 +15,63 @@ def log(info): class SvgReader(inkex.extensions.InputExtension): pass -class ConductCreateSetup(inkex.EffectExtension): +class ConductExportEffect(inkex.EffectExtension): def effect(self): conduct.log = log_stub file_path = os.path.join(self.svg_path(), self.svg.name) department = self.svg.get("com.lagmachine.conduct.department") + asset = self.svg.get("com.lagmachine.conduct.asset") + prev_state = self.svg.get("com.lagmachine.conduct.export_save_state") + prev_state = json.loads(prev_state) + c = conduct.find_from_current_path(file_path, "inkscape") - formats = c.list_export_formats(department) - log(file_path) - log(formats) + pages = self.svg.namedview.get_pages() + items = ','.join([page.label for page in pages]) + + + result = c.dialog_export(department, asset, items, prev_state=prev_state) + + if result['result'] != 'ok': + return + + save_state = result['data']['save_state'] + self.svg.set("com.lagmachine.conduct.export_save_state", save_state) + + for export in result['data']['exports']: + items = export['items'] + export_data = export['result'] + if 'error' in export_data: + log(export_data['error']) + return + export_pages = [page for page in pages if page.label in items] + + locals = {} + globals = {} + script = export_data['script'] + exec(script, locals, globals) + + for item in globals: + instance = globals[item] + + if not inspect.isclass(instance): + continue + + exporter_instance = instance() + + exporter_instance.export( + self, + directory=export_data['directory'], + file_name=export_data['recommended_file_name'], + extension=export_data['file_format'], + items = export_pages + ) + return if __name__ == '__main__': - ConductCreateSetup().run() \ No newline at end of file + ConductExportEffect().run() From ac8bb1873ae6fa59c6d345d03e323f8d3c379b8c Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Sat, 8 Feb 2025 17:51:48 +1030 Subject: [PATCH 6/6] Update release.yml --- .github/workflows/release.yml | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9f50a9f..df827c2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -87,7 +87,7 @@ jobs: asset_name: conduct.exe asset_content_type: application/octet-stream - release-integrations: + release-blender-integration: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -107,4 +107,26 @@ jobs: upload_url: ${{ github.event.release.upload_url }} asset_path: integration/conduct-blender.zip asset_name: conduct-blender.zip - asset_content_type: application/zip \ No newline at end of file + asset_content_type: application/zip + + release-inkscape-integration: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Zip Inkscape Integration + run: | + cd integration + mv inkscape conduct-inkscape + zip -r conduct-inkscape.zip conduct-inkscape + + - name: Upload to release + if: github.event_name == 'release' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: integration/conduct-inkscape.zip + asset_name: conduct-inkscape.zip + asset_content_type: application/zip