shinestacker 1.5.4__tar.gz → 1.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of shinestacker might be problematic. Click here for more details.

Files changed (165) hide show
  1. {shinestacker-1.5.4 → shinestacker-1.6.1}/.github/workflows/release.yml +11 -1
  2. {shinestacker-1.5.4 → shinestacker-1.6.1}/CHANGELOG.md +34 -0
  3. {shinestacker-1.5.4/src/shinestacker.egg-info → shinestacker-1.6.1}/PKG-INFO +1 -1
  4. shinestacker-1.6.1/scripts/build_release.py +112 -0
  5. shinestacker-1.6.1/scripts/hooks/hook-IPython.py +10 -0
  6. shinestacker-1.6.1/scripts/hooks/hook-PySide6.py +53 -0
  7. shinestacker-1.6.1/scripts/hooks/hook-opencv.py +24 -0
  8. shinestacker-1.6.1/scripts/hooks/hook-tests.py +7 -0
  9. shinestacker-1.6.1/scripts/scan_imports.py +47 -0
  10. shinestacker-1.6.1/scripts/shinestacker-inno-setup.iss +68 -0
  11. shinestacker-1.6.1/shinestacker-inno-setup.iss +68 -0
  12. shinestacker-1.6.1/src/shinestacker/_version.py +1 -0
  13. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/multilayer.py +1 -1
  14. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/stack.py +17 -9
  15. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/app/args_parser_opts.py +4 -0
  16. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/app/gui_utils.py +10 -2
  17. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/app/main.py +8 -3
  18. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/app/project.py +7 -3
  19. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/app/retouch.py +8 -1
  20. shinestacker-1.6.1/src/shinestacker/app/settings_dialog.py +171 -0
  21. shinestacker-1.6.1/src/shinestacker/config/app_config.py +30 -0
  22. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/config/constants.py +3 -0
  23. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/config/gui_constants.py +4 -2
  24. shinestacker-1.6.1/src/shinestacker/config/settings.py +110 -0
  25. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/core/core_utils.py +3 -12
  26. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/core/logging.py +3 -2
  27. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/action_config.py +6 -5
  28. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/action_config_dialog.py +17 -74
  29. shinestacker-1.6.1/src/shinestacker/gui/config_dialog.py +78 -0
  30. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/main_window.py +6 -6
  31. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/menu_manager.py +2 -0
  32. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/new_project.py +2 -1
  33. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/project_controller.py +8 -6
  34. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/project_model.py +16 -1
  35. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/recent_file_manager.py +3 -21
  36. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/base_filter.py +1 -1
  37. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/display_manager.py +48 -7
  38. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/image_editor_ui.py +27 -36
  39. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/image_view_status.py +4 -1
  40. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/image_viewer.py +17 -9
  41. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/io_gui_handler.py +96 -44
  42. shinestacker-1.6.1/src/shinestacker/retouch/io_threads.py +78 -0
  43. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/layer_collection.py +12 -0
  44. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/overlaid_view.py +13 -5
  45. shinestacker-1.6.1/src/shinestacker/retouch/paint_area_manager.py +30 -0
  46. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/sidebyside_view.py +32 -16
  47. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/transformation_manager.py +1 -3
  48. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/undo_manager.py +15 -13
  49. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/view_strategy.py +79 -26
  50. {shinestacker-1.5.4 → shinestacker-1.6.1/src/shinestacker.egg-info}/PKG-INFO +1 -1
  51. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker.egg-info/SOURCES.txt +13 -1
  52. shinestacker-1.5.4/scripts/build_release.py +0 -54
  53. shinestacker-1.5.4/src/shinestacker/_version.py +0 -1
  54. shinestacker-1.5.4/src/shinestacker/retouch/io_manager.py +0 -69
  55. {shinestacker-1.5.4 → shinestacker-1.6.1}/.coveragerc +0 -0
  56. {shinestacker-1.5.4 → shinestacker-1.6.1}/.flake8 +0 -0
  57. {shinestacker-1.5.4 → shinestacker-1.6.1}/.github/workflows/ci-multiplatform.yml +0 -0
  58. {shinestacker-1.5.4 → shinestacker-1.6.1}/.github/workflows/pylint.yml +0 -0
  59. {shinestacker-1.5.4 → shinestacker-1.6.1}/.github/workflows/pypi-publish.yml +0 -0
  60. {shinestacker-1.5.4 → shinestacker-1.6.1}/.gitignore +0 -0
  61. {shinestacker-1.5.4 → shinestacker-1.6.1}/.pylintrc +0 -0
  62. {shinestacker-1.5.4 → shinestacker-1.6.1}/.readthedocs.yaml +0 -0
  63. {shinestacker-1.5.4 → shinestacker-1.6.1}/LICENSE +0 -0
  64. {shinestacker-1.5.4 → shinestacker-1.6.1}/MANIFEST.in +0 -0
  65. {shinestacker-1.5.4 → shinestacker-1.6.1}/README.md +0 -0
  66. {shinestacker-1.5.4 → shinestacker-1.6.1}/THIRD_PARTY_LICENSES.txt +0 -0
  67. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/alignment.md +0 -0
  68. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/api.md +0 -0
  69. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/balancing.md +0 -0
  70. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/conf.py +0 -0
  71. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/focus_stacking.md +0 -0
  72. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/gui.md +0 -0
  73. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/index.md +0 -0
  74. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/job.md +0 -0
  75. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/main.md +0 -0
  76. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/multilayer.md +0 -0
  77. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/noise.md +0 -0
  78. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/requirements.txt +0 -0
  79. {shinestacker-1.5.4 → shinestacker-1.6.1}/docs/vignetting.md +0 -0
  80. {shinestacker-1.5.4 → shinestacker-1.6.1}/img/coffee.gif +0 -0
  81. {shinestacker-1.5.4 → shinestacker-1.6.1}/img/coffee_stack.jpg +0 -0
  82. {shinestacker-1.5.4 → shinestacker-1.6.1}/img/extreme-vignetting.jpg +0 -0
  83. {shinestacker-1.5.4 → shinestacker-1.6.1}/img/flies.gif +0 -0
  84. {shinestacker-1.5.4 → shinestacker-1.6.1}/img/flies_stack.jpg +0 -0
  85. {shinestacker-1.5.4 → shinestacker-1.6.1}/img/flow-diagram.png +0 -0
  86. {shinestacker-1.5.4 → shinestacker-1.6.1}/img/gui-finder.png +0 -0
  87. {shinestacker-1.5.4 → shinestacker-1.6.1}/img/gui-project-new.png +0 -0
  88. {shinestacker-1.5.4 → shinestacker-1.6.1}/img/gui-project-run.png +0 -0
  89. {shinestacker-1.5.4 → shinestacker-1.6.1}/img/gui-retouch.png +0 -0
  90. {shinestacker-1.5.4 → shinestacker-1.6.1}/index.html +0 -0
  91. {shinestacker-1.5.4 → shinestacker-1.6.1}/pyproject.toml +0 -0
  92. {shinestacker-1.5.4 → shinestacker-1.6.1}/requirements.txt +0 -0
  93. {shinestacker-1.5.4 → shinestacker-1.6.1}/scripts/git-rev-list.sh +0 -0
  94. {shinestacker-1.5.4 → shinestacker-1.6.1}/scripts/validate-tomli.py +0 -0
  95. {shinestacker-1.5.4 → shinestacker-1.6.1}/setup.cfg +0 -0
  96. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/__init__.py +0 -0
  97. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/__init__.py +0 -0
  98. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/align.py +0 -0
  99. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/align_auto.py +0 -0
  100. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/align_parallel.py +0 -0
  101. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/balance.py +0 -0
  102. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
  103. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/denoise.py +0 -0
  104. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/depth_map.py +0 -0
  105. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/exif.py +0 -0
  106. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/noise_detection.py +0 -0
  107. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/pyramid.py +0 -0
  108. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
  109. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
  110. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/sharpen.py +0 -0
  111. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/stack_framework.py +0 -0
  112. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/utils.py +0 -0
  113. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/vignetting.py +0 -0
  114. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/algorithms/white_balance.py +0 -0
  115. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/app/__init__.py +0 -0
  116. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/app/about_dialog.py +0 -0
  117. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/app/help_menu.py +0 -0
  118. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/app/open_frames.py +0 -0
  119. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/config/__init__.py +0 -0
  120. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/config/config.py +0 -0
  121. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/core/__init__.py +0 -0
  122. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/core/colors.py +0 -0
  123. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/core/exceptions.py +0 -0
  124. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/core/framework.py +0 -0
  125. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/__init__.py +0 -0
  126. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/base_form_dialog.py +0 -0
  127. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/colors.py +0 -0
  128. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/flow_layout.py +0 -0
  129. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/folder_file_selection.py +0 -0
  130. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/gui_images.py +0 -0
  131. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/gui_logging.py +0 -0
  132. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/gui_run.py +0 -0
  133. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
  134. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
  135. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
  136. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/ico/shinestacker.png +0 -0
  137. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
  138. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
  139. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
  140. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
  141. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
  142. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/project_converter.py +0 -0
  143. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/project_editor.py +0 -0
  144. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/select_path_widget.py +0 -0
  145. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/sys_mon.py +0 -0
  146. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/tab_widget.py +0 -0
  147. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/gui/time_progress_bar.py +0 -0
  148. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/__init__.py +0 -0
  149. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/brush.py +0 -0
  150. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/brush_gradient.py +0 -0
  151. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/brush_preview.py +0 -0
  152. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/brush_tool.py +0 -0
  153. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/denoise_filter.py +0 -0
  154. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/exif_data.py +0 -0
  155. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/file_loader.py +0 -0
  156. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/filter_manager.py +0 -0
  157. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/icon_container.py +0 -0
  158. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/shortcuts_help.py +0 -0
  159. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
  160. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/vignetting_filter.py +0 -0
  161. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker/retouch/white_balance_filter.py +0 -0
  162. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker.egg-info/dependency_links.txt +0 -0
  163. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker.egg-info/entry_points.txt +0 -0
  164. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker.egg-info/requires.txt +0 -0
  165. {shinestacker-1.5.4 → shinestacker-1.6.1}/src/shinestacker.egg-info/top_level.txt +0 -0
@@ -29,6 +29,10 @@ jobs:
29
29
  with:
30
30
  python-version: "3.12"
31
31
 
32
+ - name: Install Inno Setup
33
+ if: runner.os == 'Windows'
34
+ run: choco install innosetup -y --no-progress --accept-license
35
+
32
36
  - name: Install dependencies
33
37
  run: |
34
38
  python -m pip install --upgrade pip
@@ -40,12 +44,14 @@ jobs:
40
44
  run: |
41
45
  python build_release.py
42
46
 
43
- - name: Upload artifact
47
+ - name: Upload artifacts
44
48
  uses: actions/upload-artifact@v4
45
49
  with:
46
50
  name: shinestacker-${{ matrix.os }}
47
51
  path: |
48
52
  ${{ matrix.os == 'windows-latest' && 'dist/shinestacker-release.zip' || 'dist/shinestacker-release.tar.gz' }}
53
+ ${{ matrix.os == 'windows-latest' && 'dist/*.exe' || '' }}
54
+ if-no-files-found: ignore
49
55
  create-release:
50
56
  needs: publish-release
51
57
  runs-on: ubuntu-latest
@@ -61,6 +67,9 @@ jobs:
61
67
  cp artifacts/shinestacker-ubuntu-latest/shinestacker-release.tar.gz release_assets/shinestacker-ubuntu.tar.gz
62
68
  cp artifacts/shinestacker-macos-latest/shinestacker-release.tar.gz release_assets/shinestacker-macos.tar.gz
63
69
  cp artifacts/shinestacker-windows-latest/shinestacker-release.zip release_assets/shinestacker-windows.zip
70
+ if ls artifacts/shinestacker-windows-latest/shinestacker-setup.exe 1> /dev/null 2>&1; then
71
+ cp artifacts/shinestacker-windows-latest/shinestacker-setup.exe release_assets/
72
+ fi
64
73
  ls -la release_assets/
65
74
 
66
75
  - name: Create release
@@ -71,3 +80,4 @@ jobs:
71
80
  release_assets/shinestacker-ubuntu.tar.gz
72
81
  release_assets/shinestacker-macos.tar.gz
73
82
  release_assets/shinestacker-windows.zip
83
+ release_assets/shinestacker-setup.exe
@@ -2,6 +2,40 @@
2
2
 
3
3
  This page reports the main releases only and the main changes therein.
4
4
 
5
+ ## [v1.6.1] - 2025-10-01
6
+ ** Unreleased updates **
7
+
8
+ ### Changed
9
+ - improved display update performance by refreshing only the painted area
10
+ - multiple frame import now runs in a separate thread, avoiding UI freezes
11
+ - reduced dependencies and code refactored for more robust architecture
12
+ - added windows installer
13
+ - dropped examples and test images reduces distribution file size
14
+
15
+ -----
16
+
17
+ ## [v1.6.0] - 2025-09-27
18
+ **Few more features and several fixes**
19
+
20
+ ### Added
21
+ - persistent settings dialog to configure app startup options
22
+ - command-line option ```-n``` to prevent opening the "new project" dialog
23
+ - zoom factor display in the status bar
24
+
25
+ ### Fixed
26
+ - ghost brush gradient no longer appears at cursor transitions
27
+ - action and job names are now correctly set in the input dialog
28
+ - image centering fixed in viewport for double-view modes
29
+ - frame highlight works correctly when clicking on a thumbnail
30
+ - exif data is now correctly inserted into stacked output files
31
+ - bug in the retouch undo has been fixed
32
+
33
+ ### Changed
34
+ - cursor updates are now throttled (~60 fps) to improve responsiveness
35
+ - new projects created via dialog save exif data by default
36
+
37
+ ----
38
+
5
39
  ## [v1.5.4] - 2025-09-23
6
40
  **Bug fixes**
7
41
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 1.5.4
3
+ Version: 1.6.1
4
4
  Summary: ShineStacker
5
5
  Author-email: Luca Lista <luka.lista@gmail.com>
6
6
  License-Expression: LGPL-3.0
@@ -0,0 +1,112 @@
1
+ import os
2
+ import shutil
3
+ import tarfile
4
+ import subprocess
5
+ from pathlib import Path
6
+ import platform
7
+
8
+ #
9
+ # assume the scripts runs under its directory, "scripts", as defined in release.yml
10
+ #
11
+ os.chdir("../")
12
+ project_root = Path(__file__).resolve().parent.parent
13
+ dist_dir = project_root / "dist"
14
+ project_name = "shinestacker"
15
+ app_name = "shinestacker"
16
+ package_dir = "shinestacker"
17
+
18
+ sys_name = platform.system().lower()
19
+
20
+ hooks_dir = "scripts/hooks"
21
+
22
+ print("=== USING HOOKS ===")
23
+ hook_files = list(Path(hooks_dir).glob("hook-*.py"))
24
+ for hook in hook_files:
25
+ print(f" - {hook.name}")
26
+
27
+ pyinstaller_cmd = [
28
+ "pyinstaller", "--onedir", f"--name={app_name}", "--paths=src",
29
+ f"--distpath=dist/{package_dir}", f"--collect-all={project_name}",
30
+ "--collect-data=imagecodecs", "--collect-submodules=imagecodecs",
31
+ "--copy-metadata=imagecodecs", f"--additional-hooks-dir={hooks_dir}"
32
+ ]
33
+ if sys_name == 'darwin':
34
+ pyinstaller_cmd += ["--windowed", "--icon=src/shinestacker/gui/ico/shinestacker.icns"]
35
+ elif sys_name == 'windows':
36
+ pyinstaller_cmd += ["--windowed", "--icon=src/shinestacker/gui/ico/shinestacker.ico"]
37
+ pyinstaller_cmd += ["src/shinestacker/app/main.py"]
38
+
39
+ print(" ".join(pyinstaller_cmd))
40
+ subprocess.run(pyinstaller_cmd, check=True)
41
+
42
+ # examples_dir = project_root / "examples"
43
+ # target_examples = dist_dir / package_dir / "examples"
44
+ # target_examples.mkdir(exist_ok=True)
45
+ # for project_file in ["complete-project.fsp", "stack-from-frames.fsp"]:
46
+ # shutil.copy(examples_dir / project_file, target_examples)
47
+ # shutil.copytree(examples_dir / 'input', target_examples / 'input', dirs_exist_ok=True)
48
+
49
+ if sys_name == 'windows':
50
+ shutil.make_archive(
51
+ base_name=str(dist_dir / "shinestacker-release"),
52
+ format="zip",
53
+ root_dir=dist_dir,
54
+ base_dir=package_dir
55
+ )
56
+ else:
57
+ archive_path = dist_dir / "shinestacker-release.tar.gz"
58
+ with tarfile.open(archive_path, "w:gz") as tar:
59
+ tar.add(
60
+ dist_dir / package_dir,
61
+ arcname=package_dir,
62
+ recursive=True,
63
+ filter=lambda info: info
64
+ )
65
+
66
+ if sys_name == 'windows':
67
+ print("=== CREATING WINDOWS INSTALLER ===")
68
+ inno_paths = [
69
+ r"C:\Program Files (x86)\Inno Setup 6\ISCC.exe",
70
+ r"C:\Program Files (x86)\Inno Setup 5\ISCC.exe",
71
+ r"C:\Program Files\Inno Setup 6\ISCC.exe",
72
+ r"C:\Program Files\Inno Setup 5\ISCC.exe"
73
+ ]
74
+ iscc_exe = None
75
+ for path in inno_paths:
76
+ if os.path.exists(path):
77
+ iscc_exe = path
78
+ print(f"Found Inno Setup at: {path}")
79
+ break
80
+ if not iscc_exe:
81
+ print("Inno Setup not found in standard locations. Checking for Chocolatey...")
82
+ try:
83
+ subprocess.run(["choco", "--version"], check=True, capture_output=True)
84
+ print("Installing Inno Setup via Chocolatey...")
85
+ subprocess.run(["choco", "install", "innosetup", "-y", "--no-progress", "--accept-license"], check=True)
86
+ for path in inno_paths:
87
+ if os.path.exists(path):
88
+ iscc_exe = path
89
+ print(f"Found Inno Setup at: {path}")
90
+ break
91
+ except (subprocess.CalledProcessError, FileNotFoundError):
92
+ print("Chocolatey not available or installation failed.")
93
+ if iscc_exe:
94
+ iss_script_source = project_root / "scripts" / "shinestacker-inno-setup.iss"
95
+ iss_script_temp = project_root / "shinestacker-inno-setup.iss"
96
+ if iss_script_source.exists():
97
+ print(f"Copying ISS script to project root: {iss_script_temp}")
98
+ shutil.copy2(iss_script_source, iss_script_temp)
99
+ print(f"Compiling installer with: {iscc_exe}")
100
+ subprocess.run([iscc_exe, str(iss_script_temp)], check=True)
101
+ print("Removing temporary ISS script")
102
+ iss_script_temp.unlink()
103
+ if dist_dir.exists():
104
+ installer_files = list(dist_dir.glob("*.exe"))
105
+ if installer_files:
106
+ print(f"Installer created: {installer_files[0].name}")
107
+ else:
108
+ print(f"ISS script not found at: {iss_script_source}")
109
+ else:
110
+ print("WARNING: Could not find or install Inno Setup. Skipping installer creation.")
111
+ print("You can manually install Inno Setup from: https://jrsoftware.org/isdl.php")
112
+ print("Or install Chocolatey and run: choco install innosetup -y")
@@ -0,0 +1,10 @@
1
+ excludedimports = [
2
+ 'IPython',
3
+ 'ipywidgets',
4
+ 'jedi',
5
+ 'parso',
6
+ 'prompt_toolkit',
7
+ 'tornado',
8
+ 'zmq',
9
+ 'nbformat',
10
+ ]
@@ -0,0 +1,53 @@
1
+ excludedimports = [
2
+ # Keep only these 5 modules actually used:
3
+ # PySide6.QtCore
4
+ # PySide6.QtGui
5
+ # PySide6.QtPdf
6
+ # PySide6.QtPdfWidgets
7
+ # PySide6.QtWidgets
8
+
9
+ # Exclude everything else:
10
+ 'PySide6.Qt3DAnimation',
11
+ 'PySide6.Qt3DCore',
12
+ 'PySide6.Qt3DExtras',
13
+ 'PySide6.Qt3DInput',
14
+ 'PySide6.Qt3DLogic',
15
+ 'PySide6.Qt3DRender',
16
+ 'PySide6.QtBluetooth',
17
+ 'PySide6.QtCharts',
18
+ 'PySide6.QtConcurrent',
19
+ 'PySide6.QtDataVisualization',
20
+ 'PySide6.QtDBus',
21
+ 'PySide6.QtDesigner',
22
+ 'PySide6.QtGamepad',
23
+ 'PySide6.QtHelp',
24
+ 'PySide6.QtLocation',
25
+ 'PySide6.QtMultimedia',
26
+ 'PySide6.QtMultimediaWidgets',
27
+ 'PySide6.QtNetwork',
28
+ 'PySide6.QtNfc',
29
+ 'PySide6.QtOpenGL',
30
+ 'PySide6.QtOpenGLWidgets',
31
+ 'PySide6.QtPositioning',
32
+ 'PySide6.QtPrintSupport',
33
+ 'PySide6.QtQml',
34
+ 'PySide6.QtQuick',
35
+ 'PySide6.QtQuick3D',
36
+ 'PySide6.QtQuickWidgets',
37
+ 'PySide6.QtRemoteObjects',
38
+ 'PySide6.QtScxml',
39
+ 'PySide6.QtSensors',
40
+ 'PySide6.QtSerialPort',
41
+ 'PySide6.QtSql',
42
+ 'PySide6.QtSvg',
43
+ 'PySide6.QtTest',
44
+ 'PySide6.QtTextToSpeech',
45
+ 'PySide6.QtUiTools',
46
+ 'PySide6.QtWebChannel',
47
+ 'PySide6.QtWebEngine',
48
+ 'PySide6.QtWebEngineCore',
49
+ 'PySide6.QtWebEngineWidgets',
50
+ 'PySide6.QtWebSockets',
51
+ 'PySide6.QtXml',
52
+ 'PySide6.QtXmlPatterns',
53
+ ]
@@ -0,0 +1,24 @@
1
+ excludedimports = [
2
+ 'cv2.videoio',
3
+ 'cv2.videostab',
4
+ 'cv2.face',
5
+ 'cv2.bgsegm',
6
+ 'cv2.optflow',
7
+ 'cv2.saliency',
8
+ 'cv2.text',
9
+ 'cv2.tracking',
10
+ 'cv2.mcc',
11
+ 'cv2.rapid',
12
+ 'cv2.stereo',
13
+ 'cv2.dnn',
14
+ 'cv2.freetype',
15
+ 'cv2.hdf',
16
+ 'cv2.img_hash',
17
+ 'cv2.line_descriptor',
18
+ 'cv2.reg',
19
+ 'cv2.rgbd',
20
+ 'cv2.surface_matching',
21
+ 'cv2.xfeatures2d',
22
+ 'cv2.ximgproc',
23
+ 'cv2.xphoto',
24
+ ]
@@ -0,0 +1,7 @@
1
+ excludedimports = [
2
+ 'matplotlib.tests',
3
+ 'numpy.random._examples',
4
+ 'scipy.tests',
5
+ 'PIL.tests',
6
+ 'jsonpickle.tests',
7
+ ]
@@ -0,0 +1,47 @@
1
+ import ast
2
+ from pathlib import Path
3
+
4
+ def scan_imports():
5
+ project_root = Path("../src/shinestacker")
6
+ imports = {
7
+ 'PySide6': set(),
8
+ 'scipy': set(),
9
+ 'matplotlib': set(),
10
+ 'cv2': set(),
11
+ 'numpy': set(),
12
+ 'PIL': set()
13
+ }
14
+
15
+ for py_file in project_root.rglob("*.py"):
16
+ with open(py_file, 'r', encoding='utf-8') as f:
17
+ try:
18
+ tree = ast.parse(f.read())
19
+ except:
20
+ continue
21
+
22
+ for node in ast.walk(tree):
23
+ if isinstance(node, ast.Import):
24
+ for alias in node.names:
25
+ for lib in imports.keys():
26
+ if lib in alias.name:
27
+ imports[lib].add(alias.name)
28
+ elif isinstance(node, ast.ImportFrom):
29
+ if node.module:
30
+ for lib in imports.keys():
31
+ if lib in node.module:
32
+ imports[lib].add(node.module)
33
+ if node.names:
34
+ for alias in node.names:
35
+ imports[lib].add(f"{node.module}.{alias.name}")
36
+ return imports
37
+
38
+ def print_imports(imports):
39
+ for lib, modules in imports.items():
40
+ if modules:
41
+ print(f"\n=== {lib.upper()} IMPORTS ===")
42
+ for imp in sorted(modules):
43
+ print(f" {imp}")
44
+
45
+ if __name__ == "__main__":
46
+ found_imports = scan_imports()
47
+ print_imports(found_imports)
@@ -0,0 +1,68 @@
1
+ #define MyAppName "ShineStacker"
2
+ #define MyAppVersion "1.6.1"
3
+ #define MyAppPublisher "Luca Lista"
4
+ #define MyAppURL "https://shinestacker.wordpress.com/"
5
+ #define MyAppExeName "shinestacker.exe"
6
+ #define MyAppAssocName MyAppName + " focus stacking project"
7
+ #define MyAppAssocExt ".fsp"
8
+ #define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt
9
+
10
+ [Setup]
11
+ ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
12
+ ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
13
+ AppId={{A2B55EAF-932F-4D5A-8D80-B7DF79D5AE7B}
14
+ AppName={#MyAppName}
15
+ AppVersion={#MyAppVersion}
16
+ ;AppVerName={#MyAppName} {#MyAppVersion}
17
+ AppPublisher={#MyAppPublisher}
18
+ AppPublisherURL={#MyAppURL}
19
+ AppSupportURL={#MyAppURL}
20
+ AppUpdatesURL={#MyAppURL}
21
+ DefaultDirName={autopf}\{#MyAppName}
22
+ UninstallDisplayIcon={app}\{#MyAppExeName}
23
+ ; "ArchitecturesAllowed=x64compatible" specifies that Setup cannot run
24
+ ; on anything but x64 and Windows 11 on Arm.
25
+ ArchitecturesAllowed=x64compatible
26
+ ; "ArchitecturesInstallIn64BitMode=x64compatible" requests that the
27
+ ; install be done in "64-bit mode" on x64 or Windows 11 on Arm,
28
+ ; meaning it should use the native 64-bit Program Files directory and
29
+ ; the 64-bit view of the registry.
30
+ ArchitecturesInstallIn64BitMode=x64compatible
31
+ ChangesAssociations=yes
32
+ DisableProgramGroupPage=yes
33
+ LicenseFile=.\LICENSE
34
+ ; Uncomment the following line to run in non administrative install mode (install for current user only).
35
+ ;PrivilegesRequired=lowest
36
+ OutputBaseFilename=shinestacker-setup
37
+ OutputDir=.\dist
38
+ VersionInfoVersion={#MyAppVersion}
39
+ VersionInfoCompany={#MyAppPublisher}
40
+ SetupIconFile=.\src\shinestacker\gui\ico\shinestacker.ico
41
+ SolidCompression=yes
42
+ WizardStyle=modern
43
+
44
+ [Languages]
45
+ Name: "english"; MessagesFile: "compiler:Default.isl"
46
+
47
+ [Tasks]
48
+ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
49
+
50
+ [Files]
51
+ ; Copy the entire shinestacker folder structure that contains both the exe and _internal
52
+ Source: ".\dist\shinestacker\shinestacker\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
53
+ Source: ".\examples\*"; DestDir: "{app}\examples"; Flags: ignoreversion recursesubdirs
54
+ ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
55
+
56
+ [Registry]
57
+ Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue
58
+ Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey
59
+ Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0"
60
+ Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""
61
+
62
+ [Icons]
63
+ Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
64
+ Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
65
+
66
+ [Run]
67
+ Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
68
+
@@ -0,0 +1,68 @@
1
+ #define MyAppName "ShineStacker"
2
+ #define MyAppVersion "1.6.1"
3
+ #define MyAppPublisher "Luca Lista"
4
+ #define MyAppURL "https://shinestacker.wordpress.com/"
5
+ #define MyAppExeName "shinestacker.exe"
6
+ #define MyAppAssocName MyAppName + " focus stacking project"
7
+ #define MyAppAssocExt ".fsp"
8
+ #define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt
9
+
10
+ [Setup]
11
+ ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
12
+ ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
13
+ AppId={{A2B55EAF-932F-4D5A-8D80-B7DF79D5AE7B}
14
+ AppName={#MyAppName}
15
+ AppVersion={#MyAppVersion}
16
+ ;AppVerName={#MyAppName} {#MyAppVersion}
17
+ AppPublisher={#MyAppPublisher}
18
+ AppPublisherURL={#MyAppURL}
19
+ AppSupportURL={#MyAppURL}
20
+ AppUpdatesURL={#MyAppURL}
21
+ DefaultDirName={autopf}\{#MyAppName}
22
+ UninstallDisplayIcon={app}\{#MyAppExeName}
23
+ ; "ArchitecturesAllowed=x64compatible" specifies that Setup cannot run
24
+ ; on anything but x64 and Windows 11 on Arm.
25
+ ArchitecturesAllowed=x64compatible
26
+ ; "ArchitecturesInstallIn64BitMode=x64compatible" requests that the
27
+ ; install be done in "64-bit mode" on x64 or Windows 11 on Arm,
28
+ ; meaning it should use the native 64-bit Program Files directory and
29
+ ; the 64-bit view of the registry.
30
+ ArchitecturesInstallIn64BitMode=x64compatible
31
+ ChangesAssociations=yes
32
+ DisableProgramGroupPage=yes
33
+ LicenseFile=.\LICENSE
34
+ ; Uncomment the following line to run in non administrative install mode (install for current user only).
35
+ ;PrivilegesRequired=lowest
36
+ OutputBaseFilename=shinestacker-setup
37
+ OutputDir=.\dist
38
+ VersionInfoVersion={#MyAppVersion}
39
+ VersionInfoCompany={#MyAppPublisher}
40
+ SetupIconFile=.\src\shinestacker\gui\ico\shinestacker.ico
41
+ SolidCompression=yes
42
+ WizardStyle=modern
43
+
44
+ [Languages]
45
+ Name: "english"; MessagesFile: "compiler:Default.isl"
46
+
47
+ [Tasks]
48
+ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
49
+
50
+ [Files]
51
+ ; Copy the entire shinestacker folder structure that contains both the exe and _internal
52
+ Source: ".\dist\shinestacker\shinestacker\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
53
+ Source: ".\examples\*"; DestDir: "{app}\examples"; Flags: ignoreversion recursesubdirs
54
+ ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
55
+
56
+ [Registry]
57
+ Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue
58
+ Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey
59
+ Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0"
60
+ Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""
61
+
62
+ [Icons]
63
+ Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
64
+ Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
65
+
66
+ [Run]
67
+ Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
68
+
@@ -0,0 +1 @@
1
+ __version__ = '1.6.1'
@@ -174,7 +174,7 @@ class MultiLayer(TaskBase, ImageSequenceManager):
174
174
  if self.exif_path == '':
175
175
  self.exif_path = job.action_path(0)
176
176
  if self.exif_path != '':
177
- self.exif_path = self.working_path + "/" + self.exif_path
177
+ self.exif_path = os.path.join(self.working_path, self.exif_path)
178
178
 
179
179
  def run_core(self):
180
180
  if isinstance(self.input_full_path(), str):
@@ -1,6 +1,6 @@
1
1
  # pylint: disable=C0114, C0115, C0116, R0913, R0917
2
2
  import os
3
- import numpy as np
3
+ import traceback
4
4
  from .. config.constants import constants
5
5
  from .. core.framework import TaskBase
6
6
  from .. core.colors import color_str
@@ -34,13 +34,19 @@ class FocusStackBase(TaskBase, ImageSequenceManager):
34
34
  self.sub_message_r(': denoise image')
35
35
  stacked_img = denoise(stacked_img, self.denoise_amount, self.denoise_amount)
36
36
  write_img(out_filename, stacked_img)
37
- if self.exif_path != '' and stacked_img.dtype == np.uint8:
38
- self.sub_message_r(': copy exif data')
39
- _dirpath, _, fnames = next(os.walk(self.exif_path))
40
- fnames = [name for name in fnames if extension_tif_jpg(name)]
41
- exif_filename = f"{self.exif_path}/{fnames[0]}"
42
- copy_exif_from_file_to_file(exif_filename, out_filename)
43
- self.sub_message_r(' ' * 60)
37
+ if self.exif_path != '':
38
+ self.sub_message_r(color_str(': copy exif data', constants.LOG_COLOR_LEVEL_3))
39
+ if not os.path.exists(self.exif_path):
40
+ raise RuntimeError(f"Path {self.exif_path} does not exist.")
41
+ try:
42
+ _dirpath, _, fnames = next(os.walk(self.exif_path))
43
+ fnames = [name for name in fnames if extension_tif_jpg(name)]
44
+ exif_filename = os.path.join(self.exif_path, fnames[0])
45
+ copy_exif_from_file_to_file(exif_filename, out_filename)
46
+ self.sub_message_r(' ' * 60)
47
+ except Exception as e:
48
+ traceback.print_tb(e.__traceback__)
49
+ raise RuntimeError("Can't copy EXIF data") from e
44
50
  if self.plot_stack:
45
51
  idx_str = f"{self.frame_count + 1:04d}" if self.frame_count >= 0 else ''
46
52
  name = f"{self.name}: {self.stack_algo.name()}"
@@ -51,11 +57,13 @@ class FocusStackBase(TaskBase, ImageSequenceManager):
51
57
  self.frame_count += 1
52
58
 
53
59
  def init(self, job, working_path=''):
60
+ if working_path == '':
61
+ working_path = job.working_path
54
62
  ImageSequenceManager.init(self, job)
55
63
  if self.exif_path is None:
56
64
  self.exif_path = job.action_path(0)
57
65
  if self.exif_path != '':
58
- self.exif_path = working_path + "/" + self.exif_path
66
+ self.exif_path = os.path.join(working_path, self.exif_path)
59
67
 
60
68
 
61
69
  def get_bunches(collection, n_frames, n_overlap):
@@ -3,6 +3,10 @@
3
3
  def add_project_arguments(parser):
4
4
  parser.add_argument('-x', '--expert', action='store_true', help='''
5
5
  expert options are visible by default.
6
+ ''')
7
+ parser.add_argument('-n', '--no-new-project', dest='new-project',
8
+ action='store_false', default=True, help='''
9
+ Do not open new project dialog at startup (default: open).
6
10
  ''')
7
11
 
8
12
 
@@ -1,4 +1,4 @@
1
- # pylint: disable=C0114, C0116, E0611
1
+ # pylint: disable=C0114, C0116, E0611, R0913, R0917
2
2
  import os
3
3
  import sys
4
4
  from PySide6.QtCore import QCoreApplication, QProcess
@@ -6,6 +6,7 @@ from PySide6.QtGui import QAction
6
6
  from shinestacker.config.constants import constants
7
7
  from shinestacker.config.config import config
8
8
  from shinestacker.app.about_dialog import show_about_dialog
9
+ from shinestacker.app.settings_dialog import show_settings_dialog
9
10
 
10
11
 
11
12
  def disable_macos_special_menu_items():
@@ -40,11 +41,18 @@ def disable_macos_special_menu_items():
40
41
  QProcess.startDetached("pkill", ["-u", user, "-f", "SystemUIServer"])
41
42
 
42
43
 
43
- def fill_app_menu(app, app_menu):
44
+ def fill_app_menu(app, app_menu, project_settings, retouch_settings,
45
+ handle_project_config, handle_retouch_config):
44
46
  about_action = QAction(f"About {constants.APP_STRING}", app)
45
47
  about_action.triggered.connect(lambda: show_about_dialog(app))
46
48
  app_menu.addAction(about_action)
47
49
  app_menu.addSeparator()
50
+ settings_action = QAction("Settings", app)
51
+ settings_action.triggered.connect(lambda: show_settings_dialog(
52
+ app, project_settings, retouch_settings,
53
+ handle_project_config, handle_retouch_config))
54
+ app_menu.addAction(settings_action)
55
+ app_menu.addSeparator()
48
56
  if config.DONT_USE_NATIVE_MENU:
49
57
  quit_txt, quit_short = "&Quit", "Ctrl+Q"
50
58
  else:
@@ -13,6 +13,7 @@ from PySide6.QtCore import Qt, QEvent, QTimer, Signal
13
13
  from shinestacker.config.config import config
14
14
  config.init(DISABLE_TQDM=True, COMBINED_APP=True, DONT_USE_NATIVE_MENU=True)
15
15
  from shinestacker.config.constants import constants
16
+ from shinestacker.config.settings import StdPathFile
16
17
  from shinestacker.core.logging import setup_logging
17
18
  from shinestacker.gui.main_window import MainWindow
18
19
  from shinestacker.retouch.image_editor_ui import ImageEditorUI
@@ -140,7 +141,9 @@ class MainApp(QMainWindow):
140
141
  app_menu.addAction(self.switch_to_project_action)
141
142
  app_menu.addAction(self.switch_to_retouch_action)
142
143
  app_menu.addSeparator()
143
- fill_app_menu(self, app_menu)
144
+ fill_app_menu(self, app_menu, True, True,
145
+ self.project_window.handle_config,
146
+ self.retouch_window.handle_config)
144
147
  return app_menu
145
148
 
146
149
  def quit(self):
@@ -227,7 +230,8 @@ open retouch window at startup instead of project windows.
227
230
  if filename and path:
228
231
  print("can't specify both arguments --filename and --path", file=sys.stderr)
229
232
  sys.exit(1)
230
- setup_logging(console_level=logging.DEBUG, file_level=logging.DEBUG, disable_console=True)
233
+ setup_logging(console_level=logging.DEBUG, file_level=logging.DEBUG, disable_console=True,
234
+ log_file=StdPathFile('shinestacker.log').get_file_path())
231
235
  app = Application(sys.argv)
232
236
  if config.DONT_USE_NATIVE_MENU:
233
237
  app.setAttribute(Qt.AA_DontUseNativeMenuBar)
@@ -264,7 +268,8 @@ open retouch window at startup instead of project windows.
264
268
  main_app.switch_to_retouch()
265
269
  else:
266
270
  main_app.switch_to_project()
267
- QTimer.singleShot(100, main_app.project_window.project_controller.new_project)
271
+ if args['new-project']:
272
+ QTimer.singleShot(100, main_app.project_window.project_controller.new_project)
268
273
  QTimer.singleShot(100, main_app.setFocus)
269
274
  sys.exit(app.exec())
270
275