From e3ce9244b84601647432d4ff8fc6f31d7fd570cf Mon Sep 17 00:00:00 2001 From: "J. Fran Matheu" Date: Mon, 28 Dec 2020 13:46:15 +0100 Subject: [PATCH] Add atelier 2 beta --- atelier/.vscode/settings.json | 7 + atelier/__init__.py | 102 ++ atelier/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1691 bytes atelier/addon_utils/__init__.py | 21 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 809 bytes .../__pycache__/prefs.cpython-37.pyc | Bin 0 -> 2849 bytes .../__pycache__/save.cpython-37.pyc | Bin 0 -> 1352 bytes .../__pycache__/support.cpython-37.pyc | Bin 0 -> 2040 bytes .../__pycache__/updates.cpython-37.pyc | Bin 0 -> 3167 bytes atelier/addon_utils/prefs.py | 158 +++ atelier/addon_utils/save.py | 25 + atelier/addon_utils/support.py | 58 ++ atelier/addon_utils/updates.py | 81 ++ atelier/custom_ui/__init__.py | 107 ++ .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 3322 bytes .../custom_ui/__pycache__/data.cpython-37.pyc | Bin 0 -> 2137 bytes .../custom_ui/__pycache__/io.cpython-37.pyc | Bin 0 -> 3969 bytes .../__pycache__/prefs.cpython-37.pyc | Bin 0 -> 2405 bytes .../__pycache__/block_data.cpython-37.pyc | Bin 0 -> 2270 bytes .../__pycache__/block_id_cats.cpython-37.pyc | Bin 0 -> 1880 bytes .../__pycache__/block_id_draw.cpython-37.pyc | Bin 0 -> 2290 bytes .../__pycache__/block_id_ppts.cpython-37.pyc | Bin 0 -> 1700 bytes .../blocks/__pycache__/header.cpython-37.pyc | Bin 0 -> 1728 bytes .../__pycache__/toolheader.cpython-37.pyc | Bin 0 -> 15597 bytes .../toolheader_default.cpython-37.pyc | Bin 0 -> 4492 bytes atelier/custom_ui/blocks/block_data.py | 65 ++ atelier/custom_ui/blocks/block_id_cats.py | 77 ++ atelier/custom_ui/blocks/block_id_draw.py | 57 ++ atelier/custom_ui/blocks/block_id_ppts.py | 70 ++ atelier/custom_ui/blocks/header.py | 60 ++ atelier/custom_ui/blocks/toolheader.py | 571 +++++++++++ .../custom_ui/blocks/toolheader_default.py | 127 +++ .../__pycache__/default_config.cpython-37.pyc | Bin 0 -> 364 bytes atelier/custom_ui/config/default_config.py | 3 + .../config/default_sculpt_config.json | 15 + atelier/custom_ui/data.py | 70 ++ atelier/custom_ui/draw.py | 253 +++++ atelier/custom_ui/io.py | 208 ++++ atelier/custom_ui/ops/__init__.py | 24 + .../ops/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1003 bytes .../ops/__pycache__/edit.cpython-37.pyc | Bin 0 -> 3957 bytes .../ops/__pycache__/ui_blocks.cpython-37.pyc | Bin 0 -> 2390 bytes .../ops/__pycache__/ui_presets.cpython-37.pyc | Bin 0 -> 3557 bytes atelier/custom_ui/ops/edit.py | 163 +++ atelier/custom_ui/ops/ui_blocks.py | 71 ++ atelier/custom_ui/ops/ui_presets.py | 84 ++ .../__pycache__/activator.cpython-37.pyc | Bin 0 -> 1732 bytes .../__pycache__/brush_utils.cpython-37.pyc | Bin 0 -> 1072 bytes .../__pycache__/falloff.cpython-37.pyc | Bin 0 -> 1334 bytes atelier/custom_ui/ops/utilities/activator.py | 61 ++ .../custom_ui/ops/utilities/brush_utils.py | 37 + atelier/custom_ui/ops/utilities/falloff.py | 35 + atelier/custom_ui/prefs.py | 133 +++ atelier/custom_ui/ui/__init__.py | 20 + .../ui/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 873 bytes .../ui/__pycache__/add_menu.cpython-37.pyc | Bin 0 -> 4110 bytes .../__pycache__/context_menu.cpython-37.pyc | Bin 0 -> 2809 bytes .../ui/__pycache__/header.cpython-37.pyc | Bin 0 -> 4714 bytes .../__pycache__/header_append.cpython-37.pyc | Bin 0 -> 537 bytes .../ui/__pycache__/toolheader.cpython-37.pyc | Bin 0 -> 1498 bytes .../toolheader_dropdowns.cpython-37.pyc | Bin 0 -> 1786 bytes atelier/custom_ui/ui/add_menu.py | 87 ++ atelier/custom_ui/ui/context_menu.py | 77 ++ atelier/custom_ui/ui/header.py | 192 ++++ atelier/custom_ui/ui/header_append.py | 4 + atelier/custom_ui/ui/toolheader.py | 200 ++++ atelier/custom_ui/ui/toolheader_dropdowns.py | 55 + atelier/experimental/__init__.py | 17 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 884 bytes .../__pycache__/panel.cpython-37.pyc | Bin 0 -> 828 bytes atelier/experimental/mesh_filters/__init__.py | 18 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 942 bytes .../__pycache__/data.cpython-37.pyc | Bin 0 -> 1069 bytes .../__pycache__/ops.cpython-37.pyc | Bin 0 -> 1318 bytes .../__pycache__/ui.cpython-37.pyc | Bin 0 -> 1189 bytes atelier/experimental/mesh_filters/data.py | 22 + atelier/experimental/mesh_filters/ops.py | 40 + atelier/experimental/mesh_filters/ui.py | 24 + .../experimental/non_destructive/__init__.py | 35 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1229 bytes .../__pycache__/data.cpython-37.pyc | Bin 0 -> 1766 bytes .../__pycache__/draw.cpython-37.pyc | Bin 0 -> 4901 bytes .../__pycache__/ops.cpython-37.pyc | Bin 0 -> 18775 bytes .../__pycache__/ui.cpython-37.pyc | Bin 0 -> 1416 bytes atelier/experimental/non_destructive/data.py | 33 + atelier/experimental/non_destructive/draw.py | 230 +++++ atelier/experimental/non_destructive/ops.py | 789 ++++++++++++++ atelier/experimental/non_destructive/test.py | 127 +++ atelier/experimental/non_destructive/ui.py | 31 + atelier/experimental/panel.py | 14 + atelier/icons/__init__.py | 78 ++ .../icons/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 2821 bytes .../icons/__pycache__/icons.cpython-37.pyc | Bin 0 -> 553 bytes atelier/icons/arrowDown_icon.png | Bin 0 -> 291 bytes atelier/icons/arrowUp_icon.png | Bin 0 -> 282 bytes atelier/icons/bMarket_icon.png | Bin 0 -> 3906 bytes atelier/icons/brushAdd_icon.png | Bin 0 -> 555 bytes atelier/icons/brushRemove_icon.png | Bin 0 -> 830 bytes atelier/icons/brushReset_icon.png | Bin 0 -> 1123 bytes atelier/icons/brushSave_icon.png | Bin 0 -> 519 bytes atelier/icons/brush_icon.png | Bin 0 -> 629 bytes atelier/icons/cubebrush_icon.png | Bin 0 -> 1497 bytes atelier/icons/dyntopoBrush_icon.png | Bin 0 -> 956 bytes atelier/icons/dyntopoConstant_icon.png | Bin 0 -> 965 bytes atelier/icons/dyntopoHighDetail_icon.png | Bin 0 -> 1646 bytes atelier/icons/dyntopoLowDetail_icon.png | Bin 0 -> 805 bytes atelier/icons/dyntopoManual_icon.png | Bin 0 -> 1861 bytes atelier/icons/dyntopoMidDetail_icon.png | Bin 0 -> 1255 bytes atelier/icons/dyntopoRelative_icon.png | Bin 0 -> 958 bytes atelier/icons/dyntopo_icon.png | Bin 0 -> 442 bytes atelier/icons/fallOff_icon.png | Bin 0 -> 502 bytes atelier/icons/frontFaces_icon.png | Bin 0 -> 398 bytes atelier/icons/maskCavity2_icon.png | Bin 0 -> 2740 bytes atelier/icons/maskCavity_icon.png | Bin 0 -> 3330 bytes atelier/icons/maskClear_icon.png | Bin 0 -> 418 bytes atelier/icons/maskExtractor_icon.png | Bin 0 -> 327 bytes atelier/icons/maskInvert_icon.png | Bin 0 -> 276 bytes atelier/icons/maskSharp_icon.png | Bin 0 -> 3900 bytes atelier/icons/maskSmooth2_icon.png | Bin 0 -> 5725 bytes atelier/icons/maskSmooth_icon.png | Bin 0 -> 5227 bytes atelier/icons/maskTopology_icon.png | Bin 0 -> 1991 bytes atelier/icons/mask_icon.png | Bin 0 -> 939 bytes atelier/icons/mirror_icon.png | Bin 0 -> 283 bytes atelier/icons/paypal_icon.png | Bin 0 -> 900 bytes atelier/icons/paypal_icon_2.png | Bin 0 -> 1015 bytes atelier/icons/rake_icon.png | Bin 0 -> 1109 bytes atelier/icons/separator_icon.png | Bin 0 -> 183 bytes atelier/icons/strokeAirbrush_icon.png | Bin 0 -> 834 bytes atelier/icons/strokeAnchored_icon.png | Bin 0 -> 1219 bytes atelier/icons/strokeCurve_icon.png | Bin 0 -> 513 bytes atelier/icons/strokeDots_icon.png | Bin 0 -> 587 bytes atelier/icons/strokeDragDot_icon.png | Bin 0 -> 568 bytes atelier/icons/strokeLine_icon.png | Bin 0 -> 655 bytes atelier/icons/strokeSpace_icon.png | Bin 0 -> 857 bytes atelier/icons/stroke_icon.png | Bin 0 -> 794 bytes atelier/icons/textureNew_icon.png | Bin 0 -> 240 bytes atelier/icons/textureOpen_icon.png | Bin 0 -> 940 bytes atelier/icons/texture_icon.png | Bin 0 -> 190 bytes atelier/tools/__init__.py | 55 + .../tools/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1808 bytes .../__pycache__/brush_rmb.cpython-37.pyc | Bin 0 -> 19963 bytes .../__pycache__/brush_utils.cpython-37.pyc | Bin 0 -> 2827 bytes .../__pycache__/custom_ui_edit.cpython-37.pyc | Bin 0 -> 6197 bytes .../__pycache__/custom_ui_io.cpython-37.pyc | Bin 0 -> 3900 bytes .../__pycache__/custom_ui_ops.cpython-37.pyc | Bin 0 -> 4577 bytes .../tools/__pycache__/dyntopo.cpython-37.pyc | Bin 0 -> 1149 bytes .../tools/__pycache__/falloff.cpython-37.pyc | Bin 0 -> 1320 bytes atelier/tools/brush_management/__init__.py | 34 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1081 bytes .../__pycache__/data.cpython-37.pyc | Bin 0 -> 1237 bytes .../__pycache__/ops.cpython-37.pyc | Bin 0 -> 1987 bytes .../__pycache__/ui.cpython-37.pyc | Bin 0 -> 6434 bytes atelier/tools/brush_management/data.py | 19 + atelier/tools/brush_management/ops.py | 86 ++ atelier/tools/brush_management/ui.py | 339 ++++++ atelier/tools/brush_thumbnailer/__init__.py | 31 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1084 bytes .../__pycache__/data.cpython-37.pyc | Bin 0 -> 2048 bytes .../__pycache__/ops.cpython-37.pyc | Bin 0 -> 5390 bytes .../__pycache__/presets.cpython-37.pyc | Bin 0 -> 2285 bytes .../__pycache__/ui.cpython-37.pyc | Bin 0 -> 2329 bytes atelier/tools/brush_thumbnailer/data.py | 44 + atelier/tools/brush_thumbnailer/ops.py | 257 +++++ atelier/tools/brush_thumbnailer/presets.py | 5 + atelier/tools/brush_thumbnailer/ui.py | 84 ++ atelier/tools/dyntopo_pro/__init__.py | 31 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1053 bytes .../__pycache__/data.cpython-37.pyc | Bin 0 -> 1657 bytes .../__pycache__/ops.cpython-37.pyc | Bin 0 -> 1944 bytes .../__pycache__/prefs.cpython-37.pyc | Bin 0 -> 4477 bytes .../dyntopo_pro/__pycache__/ui.cpython-37.pyc | Bin 0 -> 5435 bytes atelier/tools/dyntopo_pro/data.py | 33 + atelier/tools/dyntopo_pro/ops.py | 50 + atelier/tools/dyntopo_pro/prefs.py | 283 +++++ atelier/tools/dyntopo_pro/ui.py | 207 ++++ atelier/tools/mask/__init__.py | 21 + .../mask/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 904 bytes .../mask/__pycache__/data.cpython-37.pyc | Bin 0 -> 769 bytes atelier/tools/mask/cavity/__init__.py | 15 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 667 bytes .../cavity/__pycache__/ops.cpython-37.pyc | Bin 0 -> 1853 bytes .../mask/cavity/__pycache__/ui.cpython-37.pyc | Bin 0 -> 974 bytes atelier/tools/mask/cavity/ops.py | 60 ++ atelier/tools/mask/cavity/ui.py | 20 + atelier/tools/mask/data.py | 8 + atelier/tools/mesh_close_gaps/__init__.py | 31 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1073 bytes .../__pycache__/data.cpython-37.pyc | Bin 0 -> 965 bytes .../__pycache__/ops.cpython-37.pyc | Bin 0 -> 2148 bytes .../__pycache__/ui.cpython-37.pyc | Bin 0 -> 960 bytes atelier/tools/mesh_close_gaps/data.py | 18 + atelier/tools/mesh_close_gaps/ops.py | 69 ++ atelier/tools/mesh_close_gaps/ui.py | 21 + atelier/tools/mesh_detacher/__init__.py | 31 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1068 bytes .../__pycache__/data.cpython-37.pyc | Bin 0 -> 1289 bytes .../__pycache__/ops.cpython-37.pyc | Bin 0 -> 4196 bytes .../__pycache__/ui.cpython-37.pyc | Bin 0 -> 1770 bytes atelier/tools/mesh_detacher/data.py | 12 + atelier/tools/mesh_detacher/ops.py | 164 +++ atelier/tools/mesh_detacher/ui.py | 50 + atelier/tools/mesh_extractor/__init__.py | 31 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1072 bytes .../__pycache__/data.cpython-37.pyc | Bin 0 -> 1745 bytes .../__pycache__/ops.cpython-37.pyc | Bin 0 -> 10564 bytes .../__pycache__/ui.cpython-37.pyc | Bin 0 -> 2839 bytes atelier/tools/mesh_extractor/data.py | 22 + atelier/tools/mesh_extractor/ops.py | 450 ++++++++ atelier/tools/mesh_extractor/ui.py | 95 ++ atelier/tools/mirror_plane/__init__.py | 31 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1065 bytes .../__pycache__/data.cpython-37.pyc | Bin 0 -> 1311 bytes .../__pycache__/ops.cpython-37.pyc | Bin 0 -> 4521 bytes .../__pycache__/prop_fun.cpython-37.pyc | Bin 0 -> 587 bytes .../__pycache__/ui.cpython-37.pyc | Bin 0 -> 1837 bytes atelier/tools/mirror_plane/data.py | 14 + atelier/tools/mirror_plane/ops.py | 247 +++++ atelier/tools/mirror_plane/prop_fun.py | 14 + atelier/tools/mirror_plane/ui.py | 76 ++ atelier/tools/reference_system/__init__.py | 48 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1482 bytes .../__pycache__/data.cpython-37.pyc | Bin 0 -> 4275 bytes .../__pycache__/draw.cpython-37.pyc | Bin 0 -> 4173 bytes .../__pycache__/io.cpython-37.pyc | Bin 0 -> 1129 bytes .../__pycache__/ops.cpython-37.pyc | Bin 0 -> 11630 bytes .../__pycache__/previews.cpython-37.pyc | Bin 0 -> 1471 bytes .../__pycache__/prop_fun.cpython-37.pyc | Bin 0 -> 677 bytes .../__pycache__/ui.cpython-37.pyc | Bin 0 -> 5284 bytes atelier/tools/reference_system/data.py | 98 ++ atelier/tools/reference_system/draw.py | 161 +++ atelier/tools/reference_system/io.py | 29 + atelier/tools/reference_system/ops.py | 418 ++++++++ atelier/tools/reference_system/previews.py | 62 ++ atelier/tools/reference_system/prop_fun.py | 17 + atelier/tools/reference_system/ui.py | 209 ++++ atelier/tools/remesh/__init__.py | 39 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1190 bytes .../remesh/__pycache__/data.cpython-37.pyc | Bin 0 -> 2376 bytes .../remesh/__pycache__/ops.cpython-37.pyc | Bin 0 -> 7163 bytes .../remesh/__pycache__/ui.cpython-37.pyc | Bin 0 -> 3573 bytes .../remesh/__pycache__/utils.cpython-37.pyc | Bin 0 -> 1811 bytes atelier/tools/remesh/data.py | 62 ++ atelier/tools/remesh/ops.py | 189 ++++ atelier/tools/remesh/ui.py | 131 +++ atelier/tools/remesh/utils.py | 40 + atelier/tools/rmb/__init__.py | 53 + .../rmb/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1038 bytes .../tools/rmb/__pycache__/data.cpython-37.pyc | Bin 0 -> 1265 bytes .../tools/rmb/__pycache__/draw.cpython-37.pyc | Bin 0 -> 5964 bytes .../tools/rmb/__pycache__/km.cpython-37.pyc | Bin 0 -> 1012 bytes .../tools/rmb/__pycache__/ops.cpython-37.pyc | Bin 0 -> 12678 bytes atelier/tools/rmb/data.py | 46 + atelier/tools/rmb/draw.py | 328 ++++++ atelier/tools/rmb/km.py | 18 + atelier/tools/rmb/ops.py | 597 +++++++++++ atelier/tools/sculpt_notes/__init__.py | 47 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1349 bytes .../__pycache__/data.cpython-37.pyc | Bin 0 -> 5456 bytes .../__pycache__/ops.cpython-37.pyc | Bin 0 -> 8338 bytes .../__pycache__/ops_curves.cpython-37.pyc | Bin 0 -> 17283 bytes .../__pycache__/prop_fun.cpython-37.pyc | Bin 0 -> 4569 bytes .../__pycache__/ui.cpython-37.pyc | Bin 0 -> 6476 bytes .../__pycache__/utils.cpython-37.pyc | Bin 0 -> 4999 bytes atelier/tools/sculpt_notes/data.py | 82 ++ atelier/tools/sculpt_notes/draw.py | 171 ++++ atelier/tools/sculpt_notes/ops.py | 453 ++++++++ atelier/tools/sculpt_notes/ops_curves.py | 965 ++++++++++++++++++ atelier/tools/sculpt_notes/prop_fun.py | 253 +++++ atelier/tools/sculpt_notes/ui.py | 319 ++++++ atelier/tools/sculpt_notes/utils.py | 136 +++ atelier/tools/texture_management/__init__.py | 209 ++++ .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 6360 bytes .../sculpt_brush_icons/Blob_icon.png | Bin 0 -> 21335 bytes .../sculpt_brush_icons/Clay Strips_icon.png | Bin 0 -> 20280 bytes .../sculpt_brush_icons/Clay Thumb_icon.png | Bin 0 -> 17733 bytes .../sculpt_brush_icons/Clay_icon.png | Bin 0 -> 16942 bytes .../sculpt_brush_icons/Crease_icon.png | Bin 0 -> 17308 bytes .../Draw Face Sets_icon.png | Bin 0 -> 16772 bytes .../sculpt_brush_icons/Draw Sharp_icon.png | Bin 0 -> 20926 bytes .../Flatten/Contrast_icon.png | Bin 0 -> 16807 bytes .../sculpt_brush_icons/Grab_icon.png | Bin 0 -> 16847 bytes .../Inflate/Deflate_icon.png | Bin 0 -> 13011 bytes .../sculpt_brush_icons/Layer_icon.png | Bin 0 -> 22782 bytes .../sculpt_brush_icons/Mask_icon.png | Bin 0 -> 16772 bytes .../Multi-plane Scrape_icon.png | Bin 0 -> 17342 bytes .../sculpt_brush_icons/Nudge_icon.png | Bin 0 -> 16795 bytes .../sculpt_brush_icons/Pinch/Magnify_icon.png | Bin 0 -> 16905 bytes .../sculpt_brush_icons/Pose_icon.png | Bin 0 -> 16790 bytes .../sculpt_brush_icons/Rotate_icon.png | Bin 0 -> 16809 bytes .../sculpt_brush_icons/Scrape/Peaks_icon.png | Bin 0 -> 21617 bytes .../sculpt_brush_icons/SculptDraw_icon.png | Bin 0 -> 21028 bytes .../sculpt_brush_icons/Snake Hook_icon.png | Bin 0 -> 17265 bytes .../sculpt_brush_icons/Thumb_icon.png | Bin 0 -> 16391 bytes .../utils/__pycache__/cursor.cpython-37.pyc | Bin 0 -> 1166 bytes .../utils/__pycache__/draw2d.cpython-37.pyc | Bin 0 -> 17483 bytes .../utils/__pycache__/draw3d.cpython-37.pyc | Bin 0 -> 1715 bytes .../__pycache__/geo2dutils.cpython-37.pyc | Bin 0 -> 4120 bytes .../utils/__pycache__/others.cpython-37.pyc | Bin 0 -> 1258 bytes .../package_installer.cpython-37.pyc | Bin 0 -> 929 bytes .../space_conversion.cpython-37.pyc | Bin 0 -> 1284 bytes atelier/utils/bits.py | 33 + atelier/utils/cursor.py | 31 + atelier/utils/curves.py | 160 +++ atelier/utils/draw2d.py | 906 ++++++++++++++++ atelier/utils/draw3d.py | 54 + atelier/utils/easing/__init__.py | 37 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 1500 bytes .../easing/__pycache__/easing.cpython-37.pyc | Bin 0 -> 10819 bytes atelier/utils/easing/easing.py | 274 +++++ atelier/utils/geo2dutils.py | 159 +++ atelier/utils/others.py | 30 + atelier/utils/others/cubic.splines.py | 94 ++ atelier/utils/others/cubic_hermite_spline.py | 153 +++ atelier/utils/package_installer.py | 17 + atelier/utils/space_conversion.py | 28 + 315 files changed, 15050 insertions(+) create mode 100644 atelier/.vscode/settings.json create mode 100644 atelier/__init__.py create mode 100644 atelier/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/addon_utils/__init__.py create mode 100644 atelier/addon_utils/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/addon_utils/__pycache__/prefs.cpython-37.pyc create mode 100644 atelier/addon_utils/__pycache__/save.cpython-37.pyc create mode 100644 atelier/addon_utils/__pycache__/support.cpython-37.pyc create mode 100644 atelier/addon_utils/__pycache__/updates.cpython-37.pyc create mode 100644 atelier/addon_utils/prefs.py create mode 100644 atelier/addon_utils/save.py create mode 100644 atelier/addon_utils/support.py create mode 100644 atelier/addon_utils/updates.py create mode 100644 atelier/custom_ui/__init__.py create mode 100644 atelier/custom_ui/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/custom_ui/__pycache__/data.cpython-37.pyc create mode 100644 atelier/custom_ui/__pycache__/io.cpython-37.pyc create mode 100644 atelier/custom_ui/__pycache__/prefs.cpython-37.pyc create mode 100644 atelier/custom_ui/blocks/__pycache__/block_data.cpython-37.pyc create mode 100644 atelier/custom_ui/blocks/__pycache__/block_id_cats.cpython-37.pyc create mode 100644 atelier/custom_ui/blocks/__pycache__/block_id_draw.cpython-37.pyc create mode 100644 atelier/custom_ui/blocks/__pycache__/block_id_ppts.cpython-37.pyc create mode 100644 atelier/custom_ui/blocks/__pycache__/header.cpython-37.pyc create mode 100644 atelier/custom_ui/blocks/__pycache__/toolheader.cpython-37.pyc create mode 100644 atelier/custom_ui/blocks/__pycache__/toolheader_default.cpython-37.pyc create mode 100644 atelier/custom_ui/blocks/block_data.py create mode 100644 atelier/custom_ui/blocks/block_id_cats.py create mode 100644 atelier/custom_ui/blocks/block_id_draw.py create mode 100644 atelier/custom_ui/blocks/block_id_ppts.py create mode 100644 atelier/custom_ui/blocks/header.py create mode 100644 atelier/custom_ui/blocks/toolheader.py create mode 100644 atelier/custom_ui/blocks/toolheader_default.py create mode 100644 atelier/custom_ui/config/__pycache__/default_config.cpython-37.pyc create mode 100644 atelier/custom_ui/config/default_config.py create mode 100644 atelier/custom_ui/config/default_sculpt_config.json create mode 100644 atelier/custom_ui/data.py create mode 100644 atelier/custom_ui/draw.py create mode 100644 atelier/custom_ui/io.py create mode 100644 atelier/custom_ui/ops/__init__.py create mode 100644 atelier/custom_ui/ops/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/custom_ui/ops/__pycache__/edit.cpython-37.pyc create mode 100644 atelier/custom_ui/ops/__pycache__/ui_blocks.cpython-37.pyc create mode 100644 atelier/custom_ui/ops/__pycache__/ui_presets.cpython-37.pyc create mode 100644 atelier/custom_ui/ops/edit.py create mode 100644 atelier/custom_ui/ops/ui_blocks.py create mode 100644 atelier/custom_ui/ops/ui_presets.py create mode 100644 atelier/custom_ui/ops/utilities/__pycache__/activator.cpython-37.pyc create mode 100644 atelier/custom_ui/ops/utilities/__pycache__/brush_utils.cpython-37.pyc create mode 100644 atelier/custom_ui/ops/utilities/__pycache__/falloff.cpython-37.pyc create mode 100644 atelier/custom_ui/ops/utilities/activator.py create mode 100644 atelier/custom_ui/ops/utilities/brush_utils.py create mode 100644 atelier/custom_ui/ops/utilities/falloff.py create mode 100644 atelier/custom_ui/prefs.py create mode 100644 atelier/custom_ui/ui/__init__.py create mode 100644 atelier/custom_ui/ui/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/custom_ui/ui/__pycache__/add_menu.cpython-37.pyc create mode 100644 atelier/custom_ui/ui/__pycache__/context_menu.cpython-37.pyc create mode 100644 atelier/custom_ui/ui/__pycache__/header.cpython-37.pyc create mode 100644 atelier/custom_ui/ui/__pycache__/header_append.cpython-37.pyc create mode 100644 atelier/custom_ui/ui/__pycache__/toolheader.cpython-37.pyc create mode 100644 atelier/custom_ui/ui/__pycache__/toolheader_dropdowns.cpython-37.pyc create mode 100644 atelier/custom_ui/ui/add_menu.py create mode 100644 atelier/custom_ui/ui/context_menu.py create mode 100644 atelier/custom_ui/ui/header.py create mode 100644 atelier/custom_ui/ui/header_append.py create mode 100644 atelier/custom_ui/ui/toolheader.py create mode 100644 atelier/custom_ui/ui/toolheader_dropdowns.py create mode 100644 atelier/experimental/__init__.py create mode 100644 atelier/experimental/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/experimental/__pycache__/panel.cpython-37.pyc create mode 100644 atelier/experimental/mesh_filters/__init__.py create mode 100644 atelier/experimental/mesh_filters/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/experimental/mesh_filters/__pycache__/data.cpython-37.pyc create mode 100644 atelier/experimental/mesh_filters/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/experimental/mesh_filters/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/experimental/mesh_filters/data.py create mode 100644 atelier/experimental/mesh_filters/ops.py create mode 100644 atelier/experimental/mesh_filters/ui.py create mode 100644 atelier/experimental/non_destructive/__init__.py create mode 100644 atelier/experimental/non_destructive/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/experimental/non_destructive/__pycache__/data.cpython-37.pyc create mode 100644 atelier/experimental/non_destructive/__pycache__/draw.cpython-37.pyc create mode 100644 atelier/experimental/non_destructive/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/experimental/non_destructive/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/experimental/non_destructive/data.py create mode 100644 atelier/experimental/non_destructive/draw.py create mode 100644 atelier/experimental/non_destructive/ops.py create mode 100644 atelier/experimental/non_destructive/test.py create mode 100644 atelier/experimental/non_destructive/ui.py create mode 100644 atelier/experimental/panel.py create mode 100644 atelier/icons/__init__.py create mode 100644 atelier/icons/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/icons/__pycache__/icons.cpython-37.pyc create mode 100644 atelier/icons/arrowDown_icon.png create mode 100644 atelier/icons/arrowUp_icon.png create mode 100644 atelier/icons/bMarket_icon.png create mode 100644 atelier/icons/brushAdd_icon.png create mode 100644 atelier/icons/brushRemove_icon.png create mode 100644 atelier/icons/brushReset_icon.png create mode 100644 atelier/icons/brushSave_icon.png create mode 100644 atelier/icons/brush_icon.png create mode 100644 atelier/icons/cubebrush_icon.png create mode 100644 atelier/icons/dyntopoBrush_icon.png create mode 100644 atelier/icons/dyntopoConstant_icon.png create mode 100644 atelier/icons/dyntopoHighDetail_icon.png create mode 100644 atelier/icons/dyntopoLowDetail_icon.png create mode 100644 atelier/icons/dyntopoManual_icon.png create mode 100644 atelier/icons/dyntopoMidDetail_icon.png create mode 100644 atelier/icons/dyntopoRelative_icon.png create mode 100644 atelier/icons/dyntopo_icon.png create mode 100644 atelier/icons/fallOff_icon.png create mode 100644 atelier/icons/frontFaces_icon.png create mode 100644 atelier/icons/maskCavity2_icon.png create mode 100644 atelier/icons/maskCavity_icon.png create mode 100644 atelier/icons/maskClear_icon.png create mode 100644 atelier/icons/maskExtractor_icon.png create mode 100644 atelier/icons/maskInvert_icon.png create mode 100644 atelier/icons/maskSharp_icon.png create mode 100644 atelier/icons/maskSmooth2_icon.png create mode 100644 atelier/icons/maskSmooth_icon.png create mode 100644 atelier/icons/maskTopology_icon.png create mode 100644 atelier/icons/mask_icon.png create mode 100644 atelier/icons/mirror_icon.png create mode 100644 atelier/icons/paypal_icon.png create mode 100644 atelier/icons/paypal_icon_2.png create mode 100644 atelier/icons/rake_icon.png create mode 100644 atelier/icons/separator_icon.png create mode 100644 atelier/icons/strokeAirbrush_icon.png create mode 100644 atelier/icons/strokeAnchored_icon.png create mode 100644 atelier/icons/strokeCurve_icon.png create mode 100644 atelier/icons/strokeDots_icon.png create mode 100644 atelier/icons/strokeDragDot_icon.png create mode 100644 atelier/icons/strokeLine_icon.png create mode 100644 atelier/icons/strokeSpace_icon.png create mode 100644 atelier/icons/stroke_icon.png create mode 100644 atelier/icons/textureNew_icon.png create mode 100644 atelier/icons/textureOpen_icon.png create mode 100644 atelier/icons/texture_icon.png create mode 100644 atelier/tools/__init__.py create mode 100644 atelier/tools/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/__pycache__/brush_rmb.cpython-37.pyc create mode 100644 atelier/tools/__pycache__/brush_utils.cpython-37.pyc create mode 100644 atelier/tools/__pycache__/custom_ui_edit.cpython-37.pyc create mode 100644 atelier/tools/__pycache__/custom_ui_io.cpython-37.pyc create mode 100644 atelier/tools/__pycache__/custom_ui_ops.cpython-37.pyc create mode 100644 atelier/tools/__pycache__/dyntopo.cpython-37.pyc create mode 100644 atelier/tools/__pycache__/falloff.cpython-37.pyc create mode 100644 atelier/tools/brush_management/__init__.py create mode 100644 atelier/tools/brush_management/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/brush_management/__pycache__/data.cpython-37.pyc create mode 100644 atelier/tools/brush_management/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/tools/brush_management/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/tools/brush_management/data.py create mode 100644 atelier/tools/brush_management/ops.py create mode 100644 atelier/tools/brush_management/ui.py create mode 100644 atelier/tools/brush_thumbnailer/__init__.py create mode 100644 atelier/tools/brush_thumbnailer/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/brush_thumbnailer/__pycache__/data.cpython-37.pyc create mode 100644 atelier/tools/brush_thumbnailer/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/tools/brush_thumbnailer/__pycache__/presets.cpython-37.pyc create mode 100644 atelier/tools/brush_thumbnailer/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/tools/brush_thumbnailer/data.py create mode 100644 atelier/tools/brush_thumbnailer/ops.py create mode 100644 atelier/tools/brush_thumbnailer/presets.py create mode 100644 atelier/tools/brush_thumbnailer/ui.py create mode 100644 atelier/tools/dyntopo_pro/__init__.py create mode 100644 atelier/tools/dyntopo_pro/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/dyntopo_pro/__pycache__/data.cpython-37.pyc create mode 100644 atelier/tools/dyntopo_pro/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/tools/dyntopo_pro/__pycache__/prefs.cpython-37.pyc create mode 100644 atelier/tools/dyntopo_pro/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/tools/dyntopo_pro/data.py create mode 100644 atelier/tools/dyntopo_pro/ops.py create mode 100644 atelier/tools/dyntopo_pro/prefs.py create mode 100644 atelier/tools/dyntopo_pro/ui.py create mode 100644 atelier/tools/mask/__init__.py create mode 100644 atelier/tools/mask/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/mask/__pycache__/data.cpython-37.pyc create mode 100644 atelier/tools/mask/cavity/__init__.py create mode 100644 atelier/tools/mask/cavity/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/mask/cavity/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/tools/mask/cavity/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/tools/mask/cavity/ops.py create mode 100644 atelier/tools/mask/cavity/ui.py create mode 100644 atelier/tools/mask/data.py create mode 100644 atelier/tools/mesh_close_gaps/__init__.py create mode 100644 atelier/tools/mesh_close_gaps/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/mesh_close_gaps/__pycache__/data.cpython-37.pyc create mode 100644 atelier/tools/mesh_close_gaps/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/tools/mesh_close_gaps/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/tools/mesh_close_gaps/data.py create mode 100644 atelier/tools/mesh_close_gaps/ops.py create mode 100644 atelier/tools/mesh_close_gaps/ui.py create mode 100644 atelier/tools/mesh_detacher/__init__.py create mode 100644 atelier/tools/mesh_detacher/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/mesh_detacher/__pycache__/data.cpython-37.pyc create mode 100644 atelier/tools/mesh_detacher/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/tools/mesh_detacher/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/tools/mesh_detacher/data.py create mode 100644 atelier/tools/mesh_detacher/ops.py create mode 100644 atelier/tools/mesh_detacher/ui.py create mode 100644 atelier/tools/mesh_extractor/__init__.py create mode 100644 atelier/tools/mesh_extractor/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/mesh_extractor/__pycache__/data.cpython-37.pyc create mode 100644 atelier/tools/mesh_extractor/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/tools/mesh_extractor/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/tools/mesh_extractor/data.py create mode 100644 atelier/tools/mesh_extractor/ops.py create mode 100644 atelier/tools/mesh_extractor/ui.py create mode 100644 atelier/tools/mirror_plane/__init__.py create mode 100644 atelier/tools/mirror_plane/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/mirror_plane/__pycache__/data.cpython-37.pyc create mode 100644 atelier/tools/mirror_plane/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/tools/mirror_plane/__pycache__/prop_fun.cpython-37.pyc create mode 100644 atelier/tools/mirror_plane/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/tools/mirror_plane/data.py create mode 100644 atelier/tools/mirror_plane/ops.py create mode 100644 atelier/tools/mirror_plane/prop_fun.py create mode 100644 atelier/tools/mirror_plane/ui.py create mode 100644 atelier/tools/reference_system/__init__.py create mode 100644 atelier/tools/reference_system/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/reference_system/__pycache__/data.cpython-37.pyc create mode 100644 atelier/tools/reference_system/__pycache__/draw.cpython-37.pyc create mode 100644 atelier/tools/reference_system/__pycache__/io.cpython-37.pyc create mode 100644 atelier/tools/reference_system/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/tools/reference_system/__pycache__/previews.cpython-37.pyc create mode 100644 atelier/tools/reference_system/__pycache__/prop_fun.cpython-37.pyc create mode 100644 atelier/tools/reference_system/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/tools/reference_system/data.py create mode 100644 atelier/tools/reference_system/draw.py create mode 100644 atelier/tools/reference_system/io.py create mode 100644 atelier/tools/reference_system/ops.py create mode 100644 atelier/tools/reference_system/previews.py create mode 100644 atelier/tools/reference_system/prop_fun.py create mode 100644 atelier/tools/reference_system/ui.py create mode 100644 atelier/tools/remesh/__init__.py create mode 100644 atelier/tools/remesh/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/remesh/__pycache__/data.cpython-37.pyc create mode 100644 atelier/tools/remesh/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/tools/remesh/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/tools/remesh/__pycache__/utils.cpython-37.pyc create mode 100644 atelier/tools/remesh/data.py create mode 100644 atelier/tools/remesh/ops.py create mode 100644 atelier/tools/remesh/ui.py create mode 100644 atelier/tools/remesh/utils.py create mode 100644 atelier/tools/rmb/__init__.py create mode 100644 atelier/tools/rmb/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/rmb/__pycache__/data.cpython-37.pyc create mode 100644 atelier/tools/rmb/__pycache__/draw.cpython-37.pyc create mode 100644 atelier/tools/rmb/__pycache__/km.cpython-37.pyc create mode 100644 atelier/tools/rmb/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/tools/rmb/data.py create mode 100644 atelier/tools/rmb/draw.py create mode 100644 atelier/tools/rmb/km.py create mode 100644 atelier/tools/rmb/ops.py create mode 100644 atelier/tools/sculpt_notes/__init__.py create mode 100644 atelier/tools/sculpt_notes/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/tools/sculpt_notes/__pycache__/data.cpython-37.pyc create mode 100644 atelier/tools/sculpt_notes/__pycache__/ops.cpython-37.pyc create mode 100644 atelier/tools/sculpt_notes/__pycache__/ops_curves.cpython-37.pyc create mode 100644 atelier/tools/sculpt_notes/__pycache__/prop_fun.cpython-37.pyc create mode 100644 atelier/tools/sculpt_notes/__pycache__/ui.cpython-37.pyc create mode 100644 atelier/tools/sculpt_notes/__pycache__/utils.cpython-37.pyc create mode 100644 atelier/tools/sculpt_notes/data.py create mode 100644 atelier/tools/sculpt_notes/draw.py create mode 100644 atelier/tools/sculpt_notes/ops.py create mode 100644 atelier/tools/sculpt_notes/ops_curves.py create mode 100644 atelier/tools/sculpt_notes/prop_fun.py create mode 100644 atelier/tools/sculpt_notes/ui.py create mode 100644 atelier/tools/sculpt_notes/utils.py create mode 100644 atelier/tools/texture_management/__init__.py create mode 100644 atelier/tools/texture_management/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/user_data/sculpt_brush_icons/Blob_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Clay Strips_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Clay Thumb_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Clay_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Crease_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Draw Face Sets_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Draw Sharp_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Flatten/Contrast_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Grab_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Inflate/Deflate_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Layer_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Mask_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Multi-plane Scrape_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Nudge_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Pinch/Magnify_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Pose_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Rotate_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Scrape/Peaks_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/SculptDraw_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Snake Hook_icon.png create mode 100644 atelier/user_data/sculpt_brush_icons/Thumb_icon.png create mode 100644 atelier/utils/__pycache__/cursor.cpython-37.pyc create mode 100644 atelier/utils/__pycache__/draw2d.cpython-37.pyc create mode 100644 atelier/utils/__pycache__/draw3d.cpython-37.pyc create mode 100644 atelier/utils/__pycache__/geo2dutils.cpython-37.pyc create mode 100644 atelier/utils/__pycache__/others.cpython-37.pyc create mode 100644 atelier/utils/__pycache__/package_installer.cpython-37.pyc create mode 100644 atelier/utils/__pycache__/space_conversion.cpython-37.pyc create mode 100644 atelier/utils/bits.py create mode 100644 atelier/utils/cursor.py create mode 100644 atelier/utils/curves.py create mode 100644 atelier/utils/draw2d.py create mode 100644 atelier/utils/draw3d.py create mode 100644 atelier/utils/easing/__init__.py create mode 100644 atelier/utils/easing/__pycache__/__init__.cpython-37.pyc create mode 100644 atelier/utils/easing/__pycache__/easing.cpython-37.pyc create mode 100644 atelier/utils/easing/easing.py create mode 100644 atelier/utils/geo2dutils.py create mode 100644 atelier/utils/others.py create mode 100644 atelier/utils/others/cubic.splines.py create mode 100644 atelier/utils/others/cubic_hermite_spline.py create mode 100644 atelier/utils/package_installer.py create mode 100644 atelier/utils/space_conversion.py diff --git a/atelier/.vscode/settings.json b/atelier/.vscode/settings.json new file mode 100644 index 0000000..d0ec678 --- /dev/null +++ b/atelier/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.linting.enabled": false, + "python.formatting.provider": "autopep8", + "python.formatting.autopep8Args": ["--max-line-length", "120"], + "python.formatting.yapfArgs": ["--style", "{based_on_style: chromium, indent_width: 20}"], + "python.formatting.blackArgs": ["--line-length", "100"] +} \ No newline at end of file diff --git a/atelier/__init__.py b/atelier/__init__.py new file mode 100644 index 0000000..c0c8c5f --- /dev/null +++ b/atelier/__init__.py @@ -0,0 +1,102 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +''' + Copyright (C) 2019-2020 Juan Fran Matheu G. + Contact: jfmatheug@gmail.com +''' + +bl_info = { + "name" : "AtelierSculpt", + "author" : "J. Fran Matheu (@jfranmatheu)", + "description" : "New and custom UI plus brand new Sculpt Tools for Sculpt Mode! :D", + "blender" : (2, 91, 0), + "version" : (2, 0, 0), + "location" : "Everywhere in Sculpt Mode!", + "warning" : "Beta version! REMAKE OF BLENDER ATELIER: SCULPT", + "category" : "Generic" +} + +# ----------------------------------------------------------------- # +# PRE-CHECKING +# ----------------------------------------------------------------- # +from os.path import basename, dirname, realpath +root = dirname(realpath(__file__)) + +if basename(root) != "AtelierSculpt": + message = ("\n\n" + "The name of the folder containing this addon has to be 'AtelierSculpt'.\n" + "Please rename it.") + raise Exception(message) + +# ----------------------------------------------------------------- # +# INITIALIZATION - DEPENDENCIES +# ----------------------------------------------------------------- # +''' +try: + import mouse +except Exception as e: + print(e, "Package have to be installed") + from .utils.package_installer import admin_install_package + admin_install_package('mouse') +''' + +# ----------------------------------------------------------------- # +# REGISTRATION +# ----------------------------------------------------------------- # + +def register(): + from .icons import load_icons + load_icons() + + from .addon_utils import register as register_addon_utils + #from .data import register as register_data + from .tools import register as register_tools + from .custom_ui import register as register_interface + + register_addon_utils() + #register_data() + register_tools() + + try: + from .experimental import register as register_experimental + except: + pass + finally: + register_experimental() + + register_interface() + +def unregister(): + from .addon_utils import unregister as unregister_addon_utils + #from .data import unregister as unregister_data + from .tools import unregister as unregister_tools + from .custom_ui import unregister as unregister_interface + + # REVERSED + unregister_interface() + + try: + from .experimental import unregister as unregister_experimental + except: + pass + finally: + unregister_experimental() + + unregister_tools() + #unregister_data() + unregister_addon_utils() + + + from .icons import remove_icons + remove_icons() diff --git a/atelier/__pycache__/__init__.cpython-37.pyc b/atelier/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbee860ca0d015e1232921f9702d831ec461980e GIT binary patch literal 1691 zcma)6-A>#_7@hIY+AM|;N=Tck)ucCxFS@1lDyXVhV2MJ3k_FV%S;;b<*MIJT{`!>8(`X`^Qn=Uftgu>mDz!f5wn=h8rS$52M#lt zbB!tf*%V#s1umOo?lt}u(*?Tt8w#5A0bPRoJbieK{N-`CNuX`>(WOWR!-72D_R0F% z*Dt)?q6+>2%m#y3QyolmrgS_i!dSS3R$f6`|#XM^U3+;lX2@}V0 znU0F_XM3#~jpv*1`XE_9=#Sfn?3_e7C2=VWo{`g6WR#YYoIwmFIV{#X%pjGuJgGjVx7WP*_-pAi^;wZ)2vz`9I${*-h( z2kq}W2yi^q_h93)7>D)+o$_SCzbP(FeFiuz$TJJl=7SmBS{KQ&KP+%W685t^C)Go5fOSX zk`%32B~CRPx&wV1y1W2Tb(o?Ie?S;rp=&^3OjUy)@a@=wv2i=LVQfMqR*_eR!tocE zOZu4xtXUZSh;k1DkI_#kH{QUUqAN>9zCl-}-T^^%T8B5vMjuzrlt(m7;4LN0AR&W9 z7EJg~WouGAgt%&6zjas^Nh-y%V(dcIOf^@RR#+yL$1XsV_r* W4q%xEp1$113e<>)Ez@0gSN{PN{lO>z literal 0 HcmV?d00001 diff --git a/atelier/addon_utils/__init__.py b/atelier/addon_utils/__init__.py new file mode 100644 index 0000000..ad66770 --- /dev/null +++ b/atelier/addon_utils/__init__.py @@ -0,0 +1,21 @@ +from .prefs import BAS_Preferences +from .updates import BAS_OT_CheckUpdates +from .save import FILE_OT_incremental_save +from .support import BAS_PT_dev_support + +classes = [ + BAS_Preferences, + BAS_OT_CheckUpdates, + FILE_OT_incremental_save, + BAS_PT_dev_support +] + +def register(): + from bpy.utils import register_class + for cls in classes: + register_class(cls) + +def unregister(): + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) \ No newline at end of file diff --git a/atelier/addon_utils/__pycache__/__init__.cpython-37.pyc b/atelier/addon_utils/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02ee3e0b8f827af58e5d6795b2eeceae28a2a8b5 GIT binary patch literal 809 zcmZuvOK;Oa5Z?9Lv6Chx3JCSc2`L;R1Sb$eBq@JDz&Hb zXOQ?szH;hc;Kb~vDG#x$9nZ`2_-1BuSdL4K4V%S>ui-L$ILM|^%FvroshNR3nPEnd8OZuNPYfh=TcQpI(;miWuaMLFU^ zuDNdNx&o77e0Q)h!tl-U>AOe-#RjmMb3)0NTgKZb@B8QUjGWO6Z13~v9kz%ivV%)> zwl(g8T;v}&mY4Wy8i@aPVGbKwQ;m)FJN^CT*%B?DSFnM%8(HZ2m(Mc*qRqAAFq{<~Mvqsfs zqvWoPgd;4u;e1k`w$ZlU0p`~oB!u>9kAju|kIOx@{DsS&@eI45|6p_b8XK@-5u0IC zZnIsX<3ofd*hOM1S!3r?K-ao^P78MF95x%$x1PPw1JjcvoL@k4-;%j&vzLH@{TCj%~V^rWanqvTV&klN<%BUpHuNA2yf GDEI|=>%Auc literal 0 HcmV?d00001 diff --git a/atelier/addon_utils/__pycache__/prefs.cpython-37.pyc b/atelier/addon_utils/__pycache__/prefs.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bab30d6c0d72bc959334a36ddea075e9e7cc9d95 GIT binary patch literal 2849 zcmZuzOK%*x5$@N#d!8C=BwOC}ZV(`oM5ACG1X#oIT9WO=!A850hHt^zj-0sUm*TQt*85IWiK~7! zuKBgN?$_gn--un`jhlWG_DifBwc@tl*7Hi#iMxJJ%T?iyC4WitYf*n6&(@+_@Ly+* zXgMDEgLuVXF%7=TZ}Y*S?ce#m!CcmSW3VPKy|(=|X6+fn)_bto7@DfWLXpsz?+h)~ z*<>tDz7zbA3!VgAs_OG+-_Y|k&xU2y+RH?k9DOI!2^ZP9T6!9#H2a@*!H&M<~-F(7m#>u3xGlaKTy<*WkOG$9dgBPUW$N;@Ce@ZZU1Vc`MaKkql#O}Gl zZQps-=cUKSUtPb|#JCvR%8@)e^jl|Pc1-xIj3*M}ELT(s(Q438?d)rg;J}0}XT5_{-5(zaGm#gp*8;DW;_y zZ)Q9Sx!4QxXp$97l4oHg$M9Z=JaPb+&ubGnd#ay z>1}JzdHayX-XAzcTQ?ySPq_lmDhIu#8jKfqg85KQ{E+5Rrq)6^w<7~-a;&8Z)zZ_! z2i+g59efb`SmKLkNg#O4lZ-|lM#W3gOuNiGOoewCMts^|JhOAPtt@gnw8dR`d9jA% zGa&vd3Xt%FPBK?_opV(Zt!)~x zb|5nW!B_>?tU7Z@31rLSZ&`)aR*j3&%s#2II`C=(gEe5dAZvl_qRu93%}T7jYMj(r z=dFEFX5CqtH1vH<)`PvJg}uJsyRwbln%VkJ7yg@Yo6VNl0PN9soV3^qTYYO@RKNmv zRw3$~t#nbx~o~FG`fUNaG&lO}1P4QC*&C$RAh0p)KR!&UA zWa{o8bMF)i4bsq?w!Kh#^pu7XJ&3sXxhg%`-TiiV+Bl0xxrhirI8^I)e!vezE{|0! zm`@4XGPLv^s*VeCN~0XI>p06M_cu0z12K}v8!zdL^s6tY^{so4y+2X$f@jJ-C?;CP z^dX*?AOHqnmB;$fC`jWCfM%8lncSf4ltQ0j>j^(wm%6Uk0~`ZJ|V?@F)?>nD_{5(>^NuMA6Epii)}%c2zmj4N%z! z=__T6^h{N;0iprQ!6u_BGN4dR=YBKcfR;K=0B^#D3sngsz$4#{sLaS!d{jmIHDt<$ zV2GdKxmE!UWDX!@XnT1Kh{6*LArl^)8dk*s4_6>8m^tE?uoAyQvX1=H1e>Kmyi+zz zH3k=C6?qO5^z3&qlfMOGm}RSKRsj<`W*L4}a|OmS%wWI{a$1%P)HU7t`u?)k#D%fY(@+<++6Z2-Z%{N7?y=OMyMP=%1RYEiDWw;|>}s5JN=` z34TSY5{6ysybx%RXd-!t1lz4Yz$}*|cAjMEBt=Jzv`hTk6z(mE z;RhOg`1t*6xdpF^M<|0%S#Xs0o6eEygCcGZ@q0>Q%fJk%S{>8FJTP5H=Ztn;x$=L3 CgF;>a literal 0 HcmV?d00001 diff --git a/atelier/addon_utils/__pycache__/save.cpython-37.pyc b/atelier/addon_utils/__pycache__/save.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b674b7c10e666c26d193bd4efdd7e87e07ff68c7 GIT binary patch literal 1352 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 literal 0 HcmV?d00001 diff --git a/atelier/addon_utils/prefs.py b/atelier/addon_utils/prefs.py new file mode 100644 index 0000000..05a766b --- /dev/null +++ b/atelier/addon_utils/prefs.py @@ -0,0 +1,158 @@ +from os.path import dirname +import platform +platform = platform.system() + +from bpy.types import AddonPreferences, UILayout +from bpy.props import StringProperty, FloatVectorProperty, BoolProperty, BoolVectorProperty, EnumProperty, IntVectorProperty +from ..icons import preview_collections +from ..import __package__ as main_package +from os.path import join + +### PREFERENCES DATA SOURCES ### + +from ..tools.dyntopo_pro.prefs import * +from ..custom_ui.prefs import * +from .updates import update_properties, update_auto_check_updates + +prefs_properties = ( + dyntopo_properties, + custom_ui_properties, + update_properties +) + +# ----------------------------------------------------------------- # +# ADDON PREFERENCES # +# ----------------------------------------------------------------- # + +class BAS_Preferences(AddonPreferences): + bl_idname = main_package + + ''' + prefs_properties = {} + prefs_properties.update(dyntopo_properties) + prefs_properties.update(custom_ui_properties) + + for key, value in prefs_properties.items(): + code = key + ' : ' + value[0] + '(' + for pptkey, pptvalue in value[1].items(): + # isinstance(pptvalue, str) and not pptvalue.startswith('update_') and not pptvalue.endswith('_items') + val = '"' + str(pptvalue) + '"' if isinstance(pptvalue, str) and not pptvalue.startswith('x_') else str(pptvalue) + code += pptkey + '=' + val + ',' + code = code[:-1] + ')' + exec(code) + ''' + + for props in prefs_properties: exec(props) + + def get_custom_ui_presets(self, with_extension=True): + real_name = UILayout.enum_item_name(self, 'custom_ui_presets', self.custom_ui_presets) + return join(self.saved_custom_ui_folder, (real_name + ".json") if with_extension else real_name) + + is_custom_tool_header_active : BoolProperty(name="Is Custom Tool Header Active", description="", default=True) + is_custom_header_active : BoolProperty(name="Is Custom Header Active", description="", default=True) + + # FILE SAVE + file_incremental_notation : StringProperty( + description = "Incremental save notation for file", + name = "Incremental Notation", + default = "_v" + ) + + def draw(self, context): + layout = self.layout + + box = layout.box() + row = box.row() + row.operator("bas.check_updates", text="Check for Updates", icon='RECOVER_LAST') + row.prop(self, 'auto_check_updates', text="Auto-Check updates") + row.scale_y = 1.3 + if self.need_updating: + pcoll = preview_collections["main"] + box.label(text="The version " + self.last_version + " is available !", icon='ERROR') + row = box.row() + prop = row.operator('wm.url_open', text="Cubebrush", icon_value=pcoll["cubebrush_icon"].icon_id) + prop.url = "http://cbr.sh/qako92" + prop = row.operator('wm.url_open', text="B3D Market", icon_value=pcoll["bMarket_icon"].icon_id) + prop.url = "https://blendermarket.com/products/advanced-new-sculpt-mode-ui" + + box = layout.box() + box.scale_y = 1.2 + box.active_default = self.is_custom_tool_header_active + box.operator('bas.toolheader_activator', text="Use custom sculpt Tool Header") + + col = box.column(align=True) + col.enabled = self.is_custom_tool_header_active + col.prop(self, 'saved_custom_ui_folder') + col.prop(self, 'custom_ui_presets') + + box = layout.box() + box.scale_y = 1.2 + box.active_default = self.is_custom_header_active + box.operator('bas.header_activator', text="Use custom sculpt Header") + + + + + + ''' + layout = self.layout + layout.prop(self, "dyntopo_UseCustomValues", text="Use Custom Values for Dyntopo") + box = layout.box() + box.label(text="DYNTOPO: PER STAGES") + box.active = self.dyntopo_UseCustomValues + + col = box.column(align=True) + row = col.row(align=True) + row.separator(factor=6) + row.label(text="SKETCH") + row.label(text="DETAIL") + row.label(text="POLISH") + + _col = col.split().column(align=True) + #col.label(text="Relative Values") + _row = _col.row(align=False) + _row.prop(self, "relative_Low", text="Relative") + _row.prop(self, "relative_Mid", text="") + _row.prop(self, "relative_High", text="") + + _col = col.split().column(align=True) + #_col.label(text="Constant Values") + _row = _col.row(align=False) + _row.prop(self, "constant_Low", text="Constant") + _row.prop(self, "constant_Mid", text="") + _row.prop(self, "constant_High", text="") + + _col = col.split().column(align=True) + #_col.label(text="Brush") + _row = _col.row(align=False) + _row.prop(self, "brush_Low", text="Brush") + _row.prop(self, "brush_Mid", text="") + _row.prop(self, "brush_High", text="") + + layout.separator() + box = layout.box() + col = box.column(align=True) + col.label(text="CUSTOM UI PRESETS : ") + row = col.row(align=True) + row.prop(self, "create_custom_UI_Slot_1", text="Slot 1") + #row = col.row(align=True) + #row.prop(self, "custom_UI_Slot_1", text="UI Toggles") + row = col.row(align=True) + row.prop(self, "create_custom_UI_Slot_2", text="Slot 2") + + #layout.separator() + #box = layout.box() + #col = box.column() + #row = col.row(align=True) + #row.operator("bas.check_updates", text="Check for Updates") + #row.operator("bas.update", text="Update") + ''' + + #def invoke(self, context, event): + # adress = 'https://newsmui-check-version.blogspot.com/p/recent-version.html' #https://newsmui-check-version.blogspot.com/ + # response = urllib.request.urlopen(adress) + # html = str(response.read()) + # version = html[536:545] + + #layout.label(text="PER LEVELS (BY DEFAULT MODE) : ") + diff --git a/atelier/addon_utils/save.py b/atelier/addon_utils/save.py new file mode 100644 index 0000000..7766fca --- /dev/null +++ b/atelier/addon_utils/save.py @@ -0,0 +1,25 @@ +import bpy +from os.path import basename, join, dirname +from bpy.types import Operator +from ..import __package__ as main_package + +class FILE_OT_incremental_save(Operator): + bl_idname = "file.incremental_save" + bl_label = "Incremental Save" + bl_description = "Save new version of the actual project" + bl_options = {"REGISTER", "UNDO"} + def execute(self, context): + nota = context.preferences.addons[main_package].preferences.file_incremental_notation + filepath = bpy.data.filepath + if filepath.count(nota): + strnum = filepath.rpartition(nota)[-1].rpartition(".blend")[0] + intnum = int(strnum) + modnum = strnum.replace(str(intnum), str(intnum+1)) + output = filepath.replace(strnum, modnum) + base = basename(filepath) + bpy.ops.wm.save_as_mainfile(filepath=join(dirname(filepath),("%s"+nota+"%s.blend") % (base.rpartition(nota)[0],str(modnum)))) + else: + output = filepath.rpartition(".blend")[0]+nota+"01" + bpy.ops.wm.save_as_mainfile(filepath=output+".blend") + + return {'FINISHED'} diff --git a/atelier/addon_utils/support.py b/atelier/addon_utils/support.py new file mode 100644 index 0000000..4eb408e --- /dev/null +++ b/atelier/addon_utils/support.py @@ -0,0 +1,58 @@ +from bpy.types import Panel +from .. import bl_info, __package__ as main_package +from ..icons import Icon + +# ----------------------------------------------------------------- # +# DEV SUPPORT # +# ----------------------------------------------------------------- # + +class BAS_PT_dev_support(Panel): + bl_label = "Updates" + bl_description = "Check for updates. Support." + bl_category = 'Sculpt' + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = "NONE" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + # LOAD COLLECTION OF ICONS + prefs = context.preferences.addons[main_package].preferences + row = self.layout.row() + row.label(text="SUPPORT THE DEVELOPMENT :") + + box = self.layout.box().column() + box.label(text="Report a Bug / Make a Proposal") + _prop = box.operator('wm.url_open', text="GO !") + _prop.url = "https://trello.com/invite/b/rGqXWJjA/78593a39edca80af604e0f2b62e0851d/blender-atelier-sculpt" # https://trello.com/b/rGqXWJjA/blender-atelier-sculpt + + + box = self.layout.box() + row = box.row() + row.label(text="DONATION") + row = box.row() + prop = row.operator('wm.url_open', text="Paypal", icon_value=Icon.PAYPAL()) + prop.url = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BA3UXNSDLE55E&source=url" + + row = box.row() + row.label(text="DOWNLOAD / UPDATES") + row = box.row() + + row.operator("bas.check_updates", text="Check for Updates", icon='RECOVER_LAST') + row.scale_y = 1.3 + + row = box.row() + if prefs.need_updating: + row.alert = True + row.label(text="New Version ! - " + prefs.last_version) + + row = box.row() + row.alert = True + else: + _row = box.row() + _row.label(text="Version is up to date ! - " + str(bl_info['version'])) + + _prop = row.operator('wm.url_open', text="Cubebrush", icon_value=Icon.CUBEBRUSH()) + _prop.url = "http://cbr.sh/qako92" + __prop = row.operator('wm.url_open', text="B3d Market", icon_value=Icon.BMARKET()) + __prop.url = "https://blendermarket.com/products/advanced-new-sculpt-mode-ui" \ No newline at end of file diff --git a/atelier/addon_utils/updates.py b/atelier/addon_utils/updates.py new file mode 100644 index 0000000..41bddc6 --- /dev/null +++ b/atelier/addon_utils/updates.py @@ -0,0 +1,81 @@ +from bpy.types import Operator +from .. import bl_info, __package__ as main_package +from bpy.props import BoolProperty + +# ----------------------------------------------------------------- # +# UPDATES +# ----------------------------------------------------------------- # + +def check_update(): + import urllib + import re + adress = 'https://raw.githubusercontent.com/jfranmatheu/b3d_addons/main/versions/AtelierSculpt.txt' + response = urllib.request.urlopen(adress) + html = str(response.read()) + result = re.search('version:(.*);', html) + last_version = result.group(1) + current_version = str(bl_info['version']) + print("[Atelier Sculpt] Current Version :", current_version) + print("[Atelier Sculpt] Last Version :", last_version) + if last_version != current_version: + return True, last_version + else: + return False, False + +def update_auto_check_updates(self, context): + if self.auto_check_updates: + import bpy + bpy.ops.bas.check_updates() + +class BAS_OT_CheckUpdates(Operator): + bl_idname = "bas.check_updates" + bl_label = "Check for 'Atelier Sculpt' Updates" + + second_plane : BoolProperty(default=False) + + def error(self, ui, context): + ui.layout.label(text="Do you have Internet Conection? If yes, please report it.") + + def nope(self, ui, context): + ui.layout.label(text="You are up to date!") + + def success(self, ui, context): + ui.layout.label(text="New Version Available!") + + def execute(self, context): + prefs = context.preferences.addons[main_package].preferences + try: + prefs.need_updating, last = check_update() + if not last: + if not self.second_plane: + context.window_manager.popup_menu(self.nope, title = "Version " + str(bl_info['version']) + " is the last one", icon = 'INFO') + return {'FINISHED'} + else: + prefs.last_version = last + if prefs.need_updating: + if not self.second_plane: + context.window_manager.popup_menu(self.success, title = "Version " + prefs.last_version + " was found", icon = 'INFO') + else: + self.report({'INFO'}, "Atelier Sculpt: new update available - " + last) + except: + if not self.second_plane: + context.window_manager.popup_menu(self.error, title = "Can't Check for Updates!", icon = 'ERROR') + print("[ATELIER SCULPT] WARNING: Can't Check for updates! Please Report it!") + return {'FINISHED'} + + +update_properties = ''' +need_updating : BoolProperty( + description = "Need Updating", + name = "need_updating", + default = False +) + +last_version : StringProperty( + description = "Last Version", + name = "last_version", + default = "(2.0.0)" +) + +auto_check_updates : BoolProperty(default=True, name="Auto-Check for Updates", update=update_auto_check_updates) +''' diff --git a/atelier/custom_ui/__init__.py b/atelier/custom_ui/__init__.py new file mode 100644 index 0000000..a7bed0b --- /dev/null +++ b/atelier/custom_ui/__init__.py @@ -0,0 +1,107 @@ +from bpy.utils import register_class, unregister_class +from bpy.app.handlers import persistent, load_post, load_factory_startup_post +from .. import __package__ as main_package + +# CLASSES +from .ui.toolheader import BAS_HT_toolHeader, ToolHeader +from .ui.header import BAS_HT_header +from .ui.header_append import temporal_tool_header_buttons +from bl_ui.space_view3d import VIEW3D_HT_header as Header +from .ops.utilities.activator import BAS_OT_header_activator, BAS_OT_toolHeader_activator +from .ui.toolheader_dropdowns import classes as DROPDOWN_CLASSES +from .ui.context_menu import menu_ctx_tool_header, WM_MT_button_context + +# INTERFACE RELATED +from .ui import register as REGISTER_UI, unregister as UNREGISTER_UI + +# DATA RELATED +from .data import ( + classes as UI_DATA_CLASSES, + ToolHeader_PG_custom_ui as CustomUIPG, +) + +def register_data(): + for cls in UI_DATA_CLASSES: + register_class(cls) + from bpy.types import Scene as scn, WindowManager as wm + from bpy.props import PointerProperty as Pointer + scn.bas_custom_ui = Pointer(type=CustomUIPG) + +def unregister_data(): + for cls in reversed(UI_DATA_CLASSES): + unregister_class(cls) + from bpy.types import Scene as scn, WindowManager as wm + del scn.bas_custom_ui + +# OPS RELATED +from .ops import register as REGISTER_OPS, unregister as UNREGISTER_OPS + +# LOADER. +@persistent +def on_load_handler(dummy): + from bpy import context as C + + prefs = C.preferences.addons[main_package].preferences + + from os.path import exists + if prefs.saved_custom_ui_folder == '' or not exists(prefs.saved_custom_ui_folder): + from os.path import dirname, abspath, join + root = dirname(dirname(abspath(__file__))) + prefs.saved_custom_ui_folder = join(root, "user_data", "saved_custom_ui") + + from .io import load_custom_ui_preset + load_custom_ui_preset(C) + + if prefs.auto_check_updates: + from bpy import ops as OP + print("[ATELIER SCULPT] Auto-checking for updates...") + OP.bas.check_updates(second_plane=True) + +def register(): + REGISTER_OPS() + + Header.append(temporal_tool_header_buttons) + + try: + unregister_class(ToolHeader) + except: + print("[ATELIER SCULPT] ERROR: TOOL HEADER NOT REGISTERED !") + try: + unregister_class(Header) + except: + print("[ATELIER SCULPT] ERROR: HEADER NOT REGISTERED !") + REGISTER_UI() + + register_data() + + load_post.append(on_load_handler) + load_factory_startup_post.append(on_load_handler) + + try: + from bpy.types import WM_MT_button_context + WM_MT_button_context.prepend(menu_ctx_tool_header) + except RuntimeError as e: + print(e) + + BAS_HT_header.append(temporal_tool_header_buttons) + +def unregister(): + try: + from bpy.types import WM_MT_button_context + WM_MT_button_context.remove(menu_ctx_tool_header) + except RuntimeError as e: + print(e) + + BAS_HT_header.remove(temporal_tool_header_buttons) + + UNREGISTER_UI() + register_class(ToolHeader) + register_class(Header) + + unregister_data() + + load_post.remove(on_load_handler) + + Header.remove(temporal_tool_header_buttons) + + UNREGISTER_OPS() diff --git a/atelier/custom_ui/__pycache__/__init__.cpython-37.pyc b/atelier/custom_ui/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7becf459fc60fe63943e32ac2cc33addc2e4db0b GIT binary patch literal 3322 zcmZ`*OLH5?5#HGa77yY@iVu-uWJrF*kS*9{`4W{YC6It5x+Ib&z);!1HCbYo(o*|C zW(E?e0&<~Gsq%ktZb_AYKz>4g!(4sRE$5ulJ-Yx%Iohr5o$2oBnd$zfr?<-Gq5;pn z?|!DLWEj6-XZl#scmSXH4-jTB5*Q(INN74H!M+(-VaCaXStlFXjtxAEWrAFocM8zX zGCL@S3(i7Va!R_N3(8@|spxh-sD?GC25o~Cg2iyjxu*HWU^!fIR>D`$Br04gyyQ zRU1WDISoq{haMNG6iHPGVwcfjETmf3%@cPZV}4GBbh#W2wdk#h#)(>>bm$IVy3ajI z-;!j)#jRGCKJHN&$H8OIWgb_>-mDFS%BjK&Ed<7<^ul4x-9V3_37;O1q>Lj0^4cGE zI!E`~Xfz#Tsx>sSH)T**qyNfl zH26m_^uN*e{r;fk{H~LDEQ>X{3s2q#%Jnq_P;PG5NEtqhd{?GWTF3hv<0)+x8g0 z0cJT)!Z(r3+{{0KZo}erSvOA2iFpNT<|#n!r77-@tueGOhzLNQ1t{hxnXoX) zhQ$eF%y9{^n_g{{w6lAsbgG zkZEO&!FZ4qP8Uy?Sc#Q?gr0U~C3Pj9+VmU}WqW6kse}SwsT}ipkg z3W#?KBfyyUk5d)yE04_~$_wBHI0q{_x6M#PhujmM1c3!Oe{=UwtzPHJPUoQB-9CJ> z-}}Da8p-(X;DtAM2~e${#JoNl0;W9CY&Lrh;*^93YlOrZxRKYmrYtx~ya98m1u%W$ zaW5KpLfHv;RVj4+Xqr1!E*4E(KEHzlR^M4NJ&6N27yNS&^1DcysFtVn#1A0xD$|eo z7sy(2VU<*`lBUb!U!ov8Ml}32^;>Vgc;Be^3`i~m!mok% z8cV7)HAeULyUI>)V%r@KASxh|FQBnumW+zdlS=I61NmHY1}dpxArV<+4siHhPdT4B2t9NB4S4tFu}pUq;XRQ zt;!5HiGCF&SF(Q^hT0{c+1v|bxCGE;{2>zbx^|ZihbkZLUXB1B39h5Fg)Q{gJRqua zNf`p1I-Ea3t*dtiUjg1T_>u#u-JH0!2uCrG2NHr%G_~Gt?yBHjZaoZw7L+-%Oc^6sI=p(@WS?OUD7! zPff_`1CPG)z1Q~`ub>fhhO-7qx`|8lr4K21R{ilRs?@Z~l?6}+^$XNSk+Rc%#5y;zU9o@-yfT#=>70HFZicGwWlY^s!g_)PP zkbH~e0g`!WbW7jq`ESCQjRNoYcx#K>Kx&XJOBU8A_~O52!JEzJ^0}H-TF!6&4}Iq; Ay#N3J literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/__pycache__/data.cpython-37.pyc b/atelier/custom_ui/__pycache__/data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a655a4666f4dc84c71433617ac98aabc3978a5cc GIT binary patch literal 2137 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 B$Nc~R literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/blocks/__pycache__/block_data.cpython-37.pyc b/atelier/custom_ui/blocks/__pycache__/block_data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd460b96930bb985b047d0021613995998a046a9 GIT binary patch literal 2270 zcmZuyOK%%D5GJ_~J-m`-#f{@WP#5i?Y73!gdPz~VkzYk0a2z{9fQ0~Jz1-LvucR$W z#Sf@J0sCk4ARi0#7xCJY|3WV94A+un!4x>0B{}@QA!kPGwOW-xd;I(#e&!SMFMdp# z1IknA>RS+k2%3=|4Jk#PWvu6f4(QGi4c$W$dOs223h$B#?}CNC@b^ipbWOpuMNRGH z&wTy2XB)rr&Ara6I}^LRFZRvy=GHdf-`ef$bzVH%HY^Tiw?J0EiCy0cE zRFIGf8am=p;wE0=3HL8Yc*4K*0K6p1mn1ADWl<5;OL9TO3g`+2Ulc3g*s3X`N7*1gzWxuIL_e>gla^yxDvWoM3Vmeg zY3$-GG#s&SKv!=-jOd7PkYg&SU{`F+t_aLH+8ui%dWEb0h=D9wSsqbcIribZI&wfR zSQ!XMxQDo_26puuyG7j?FOA5ReJh~r?HJ16(Y1o+B7nD=vJ8VM$t2IBI5E{Y%2OR> z>3L$@e$-1O-nc0rCYrw<_G9Cslogz;4pTntr&{q-(|jArG&;xS{c_$zpyrbl;=rbv|Qt=BztXnT3mbB-$Z@@XTCex z_^$g>B~o>N+3t4od^6HfcW)2@#$k6og98$|wmlg3MWoX~e=^nmrv2pWu8L&}z}-lQ zL0@${I?2*R?#II{*WGxi^q|LwX}2I$3>?mh+WDC&bDo0loTF>%AqYZ!+N1$UpH^>V zz|NN@5z?Mm#0+;s!x6K~#zF&>5g9`y*f)p3h8LqdEhd|=A|HSN{e2h=8>ZI#LVSo* zKqro}jPpnEr7XZ*=wkJL=sMVFGNuTPX*~hPvy77u@)k2roGC-+apf9pK>`!VVCpTH z#h4D*a70el(Et=gyMpOCEHBQINVcf_1oq6iC_ja{HRuzl`V`7q-S1ifCcw27NPlpQ z?VMY~X%%Wi8FG`iQ3khbVKE`*v6o=(vl;B$N1Y_b6doTGN@(FLmdvw;hN4*z_=CM7 z{VD~|3*SdfwKW|v%`QO0+ZeG6y#G5iu{O3_jzk8gnyR#~bkv7zbwwI$Sh(fA0$b+E zV{m>BUEwrOlKGav{4T5!6pt(6`3LpaTY1nOZ3q|*zC4>Couse29j){_!7leD6k682&@J6F9W%#We{cRvgLZ& z4T|4F;0M0lvC}GrH5&`~pCM#)BGJ{EISfaM`6Tfm`+U}N2G7D574ZAQ)gSOG6ee*% I0}OQbKhnzaqW}N^ literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/blocks/__pycache__/block_id_cats.cpython-37.pyc b/atelier/custom_ui/blocks/__pycache__/block_id_cats.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..266d79580451ef50c312e48353eb68e9dd4075eb GIT binary patch literal 1880 zcmZ`)+iu%N5S3+JELpN_*G}T57p9jqdPq>TPc4d~G)dWNL{jBaZY>BP7|u#2Oo_yD z<<@-a?-b}4@wI=^r_S;sU(itClH55vJ9Bo<=(O8ySMd7wm%p8$RjK?VKFX_uFF)ZO z|A_-srWM$NDr`dy>d=5DK6jvnV|&q=Zow{epbLA@gZ)KgT7?5RTvVsq@Btjb@nU;g zgD3D5p5a>^PT)hdHQ+gX1RujE@F_^RrU@_LGx!`4JMhI~YubV@;U(hQ@D-`_U-i3b zQ>Alv9q_bfn1+_tPuV)Ynl?$`17&gAAe@EQG;I#tAPDF4w5Gf9b=tJ)AAHSd+A#SQ z;#d{)nbs%V5Ayf4Ng~(tSBrjidZ=mx=hT{zbBAanO|eZY*~|8k2o0#mL-rVD+e71w z+@Ux}S23cV316mtu2~z81@N8Bi4_9bw3!6T)EYnIGy3bga*|C@sjl$y?UQsv+%)563pg=Eyv|Ob+EW4_}Mgmh3PJgK%-%ucqyR zWg7NCRy30A4p_M2uLrJ2F1!Lrd+Ue z^azvl^!yRh9uG#0Nnsf_`xL=oyhJxh0L!XU+0ED?%P`C&uvc2gvVuBg*yV)4j*4l+v~+n??3i?okS*On z+H71#l&P2`Cq=7~;)r_w-1npi3xMB7qSW`*OGBKIqfbUQYK|m_>g|f-2}v&{9orU) zPq4Z}E_GzUy2LDQmrKeS9^bT+OVJsRWJ60jxge!+;I1;?LqoANT{CP9mdF=c(i?h= zF6oM6xWv6dP}(BeShi$rS<@Dwk;~k?TODN@Eo}}#Ca^H~HxNm*Kgx{$Zrb)-P8T8b zX`D8p=(hEDqR*yHUi8u0GHa{zqRTeoqV2|IA4cXyKd!Gui%pwx-d+Eyuop;vHvF3? z-_Is7qGmr2W^xp%E_Y{E=q|CX%ua(`S%YxB0#|6ZoSJ>}=DV+FvB&&~$1@iI`-yieu98=oF^FJgsHy!{0 literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/blocks/__pycache__/block_id_draw.cpython-37.pyc b/atelier/custom_ui/blocks/__pycache__/block_id_draw.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca16bc38635ec827bd028651262e9194d96b98ce GIT binary patch literal 2290 zcmbW3TXWM!6vrjUu^lIllMo=i(9(O;OG<%KptMD?<;296j3h&I#xpyLv`!4Vbyp6B z;jKLPdvvBBp`T=5`@~o1Q_otGAw1R|{j%P3cK7T#|6OIb(Woo5 zWxMtgXHd3FqO3>LP*l57DjP3V*{h1JUCk!@4oTxUIH9gj8954K?=eT)rMBMDhCPSa ztzmEAkiOa0$*@ayduHpv_T;?9r-Y5W`0zT;OBGOk1xj?`5yptDKowboC1f3zkt@(Z zu0j*J2J6TTIEUPX^T-Qu5xE6#A>W2~knh4J4gHezkt~$(KWidbL+~8du)=VeB}Cm9PyEs(qKZF?M;Iu z9eGop#vz$bMwtq-KV;$)pSe$VlBc3Zi01|YAva~@atn(3?82E%?i4P@wj=!`H>8A! zIw4`~PXpOrA>`Mo8|0RI~~*48bOd3Rf; z0nHFB>WLrW)p&CvFA z>y<~bmAOZ;nKl1ul*UO+!fB9B80BKEeP}r5z$AS<;$2I(aqXvJNK=MIwh#M#-LVeE zN;qLGW+VyRh>Ded*Ro8D40@WOi*o4l$D-WV>;q`XHr^sh9K^>@MMHKHIepLEJA~yi zi=&i`T`ZoccP!IzIvUnZRL7{Lcsv%>j@Ij$oenhf9+@W01sBcSBGZH^v0>{Ck9lRp z<{PfF(ufLAS^SuaitSkDfi4=^Z-QGriG9(?x(V)g-)xJyX7V!3HIo%XNz*Ojucw03uh#m%i46pfD46Vc4=7?y7A zIs5bRJd9(s7vt@|X`*$t7@xQv>PD;!33763=8`dU~$7o2S6zAdYDHUs7qh;xR-EeRYTYI33s!zu*Dom`+4>REy8H#oj z-zIqU@^ejWj8C`3hU_gemmSNqefb&KoW(?n1DzLVOOBp}H=iRf(~^%O_vE=24e8WI zg}3CnM%~X>%t~-(y*%lnlIdR5rFW)!SkHyyP1z|WJaQ9pUYc_OwT9LMo!EvpfMxz< zl6oidzN4Z!^Lf~HTKltCC~M%IIlIVT!;0*yB9`#s4x*-3{{OdBk>9i9v1hh|v0WEs zivI&Jd3&=3gs8uI|FcEeyN-a3SG^=JEUQSXy!&T!y|x4SW@2(1qIM^{=YNtfJ1z&T Kl~&Z1#s2`K;nOK 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 literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/blocks/__pycache__/toolheader_default.cpython-37.pyc b/atelier/custom_ui/blocks/__pycache__/toolheader_default.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2e7faf651534801f1294c889f0912fe1451c6e3 GIT binary patch literal 4492 zcma)9TW=f372X?{ONyi}zQjRm*9crEY!fB0ixzRyShge=Zfx4sC20f(+ZAU-Exp{` z%s15K)!+Wje{0DwzM(&p$3|rZulxxFGng3}eRIb&scuD9-`=rJB z>{!fX)>p=kU93VYFc<$ztjs)=E~~IA$}*c{HIyE!vj)lvn`a9st89@ip`2r<*lCnC zc7~lrS!b_dr{~o9we?#!w(o5Do2%P*?r-^b@2#!-+qeCjckZozwAnGS6MCqm?XBXK z4Tz!9GoG5y@PV0_$Z84d(zKKt1qVrI)*Mcl)ZzMq<$h2=pJZ4;6yp`lJ3$ieYCr#}u%kRI}mK_nMnQRA) zC9!PZ$aoZTvDwL^G;4QqnI(Nc58Jy@(s>};88F}H0Vebr-wX06TTTyzi(Sww9SFm$ znuFyN;aHwM(-a@VhRj9d^9Cw}PtPRf9hxJP*+bK}hUQb75F4633z#{rEnrd#qOcox z|MF*g{&l_Sh!Tb?TO^NEH4ptf4m0ULR`VI}r%{k`zc@$J5@mE0b0i4PLR4t*oO~2! zoqfNbFiz$z>APVzOxN_xLGwG#crY?9LXczA)n}qkohtk@Iu|E zrrN!|=3~UekUwhL1*EUh{1w`cPrI2_cH{WWr_fv0APlo+I?(q8$ids+2kXh^rcl$# zNZT6fYp9S_lh+f_Jxxu_KD5Z9pIXd$25Qu`(zNDMZ36R~AKza8~~OMWZ9Zf#1fUU z18Kp+t)9)S2bQ?Z?2(x{y%O48yk+_h%h%BYkpi*eumVL?VDHtIa>(#hX>)bGwXT-( zI2B0`X33c^0v6^{E#{Jw8A_Q8uFX^}P!q_A;#tw+Zs^Wi&6285`^G_^tJlFS~!yePg`h}{_q4&f`F4M>= z9m`K1#azrfNxfKpe7;NQEiXVA@Qu?@m18-m(a)OxJZtdNlfCBHoX4DoNLOp`XQ)6I z2nC|X%-CXZoeWw-8|hH%i;<>~Lr`Qt^p4!+%yA`T2%X(*UwLv;JAF(glU8qYT-!n{ zc^m7CcSu|$@h(IcFPkKy!+NG(1<*IRfC>aaZ6q6>?3&B2=|+un5DDHcT3Q!Kv3p8|{76a2@PN)8jyy zpUyN5l)0lW9fbs`6T+iO_YixtB;5^!_!TBb!dilMd8C(Vdb3(AMZqqQR9SX{h+{`? zF`rn$SV1%bADgCO%9i=AXwjz`cX0cm5Q(^lPlXrgT&#u&?c%qje+_GJ>13T3lS~zU zEIJbblB&tP|B7HxD1Xl&QVHP#f3p7gQY0=DK>pP=Wo_S94qZr)@lKR7x)k&3<`#1)9={YkeF$k>MnHI_3yfowPo9G>gsYB-=+? zemocWjF2L0HmsU?)*M_p8JSm2F~y1ej<%*r;_s-;B#h5N4mJf-@jP>oB$`i9gAic^)Uj8AbAVWS)sZuFdUK;vRvVU{Aj~2JKV+qm`}mDvY0da-VN@R4!!kzH zN8Wwo^WT4B#2#};E-R0`FYGUfiWOm}PTyDqlr<0MT4<>{*cBO&4|my5kTtDtZ+yIt);LIgNMx64a^t&wtS4tO(79OGSX7ELGb_ z5yi_O#~{mu*r9eCLbgh(-!CSvl%SyNmb1KP>OLl6-F=%AN;52DDXUGp3~ z*MrYeZcRy^S7Z)W!(4!+7BP+52JfD1vH!PHtJzSV?^DL)`>N{G;PvTP&A&DbG(lxp!3W^x?YD)4f&+%N(_B^lR zo$}@(8=kBCw2 len(blocks): + return None + return blocks[idx] + + @classmethod + def get_active_block(cls): + if cls.active_block_index == -1: + return None + return blocks[cls.active_block_index] + + @classmethod + def set_active_block(cls, idx): + cls.active_block_index = idx diff --git a/atelier/custom_ui/blocks/block_id_cats.py b/atelier/custom_ui/blocks/block_id_cats.py new file mode 100644 index 0000000..5d4b3a1 --- /dev/null +++ b/atelier/custom_ui/blocks/block_id_cats.py @@ -0,0 +1,77 @@ +categories = ( + 'Default', + 'NONE', + 'Brush', + 'Sliders', + 'Stroke', + 'Falloff', + 'Mask', + 'Texture', + 'Others', + 'NONE', + 'Utils', + 'NONE', + 'Spacing' +) + +default = [ + ('DEF_BRUSH_SELECTOR', 'Brush Selector'), + ('DEF_BRUSH_SETTINGS', 'Brush Settings'), + ('DEF_OTHER_BRUSH_SETTINGS', 'Other Brush Settings'), + ('', ''), + ('DEF_BRUSH_RADIUS', 'Brush Radius'), + ('DEF_BRUSH_STRENGTH', 'Brush Stregth'), + ('DEF_DIRECTION', 'Direction'), + ('', ''), + ('DEF_MIRROR', 'Mirror'), + ('DEF_STROKE_CURVE_SNAP', 'Stroke Curve Snapping'), + ('', ''), + ('DEF_SWITCH_MODE', 'Switch Mode'), + ] + +brush =[ + ('BRUSH_SETTINGS', 'Brush Settings'), + ('BRUSH_SETTINGS_ADVANCED', 'Brush Advance Settings'), + ('BRUSH_SELECTOR', 'Brush Selector'), + ('BRUSH_OPTIONS', 'Brush Options'), + ('BRUSH_OPTIONS_COLL', 'Brush Options (Collapsed)'), + ('RENDER_BRUSH_ICON', 'Render Brush Icon'), + ] +texture =[ + ('TEXTURE_MANAGER', 'Texture Manager'), + ('TEXTURE_OPTIONS', 'Texture Options'), + ] +mask =[ + ('MASK', 'Mask'), + ('MASK_TOPOLOGY', 'Auto-Mask Topology'), + ('FRONTFACES', 'Front-Faces'), + ] +falloff =[ + ('FALLOFF', 'FallOff'), + ('FALLOFF_PRESETS', 'FallOff Presets'), + ('FALLOFF_PRESETS_COLL', 'FallOff Presets (Collapsed)'), + ] +stroke =[ + ('STROKE', 'Stroke Settings'), + ('STROKE_METHOD', 'Stroke Method'), + ] +sliders =[ + ('SLIDER_RADIUS', 'Radius'), + ('SLIDER_STRENGTH', 'Strength'), + ('SLIDER_SMOOTH', 'Auto-Smooth'), + ('SLIDER_SPACING', 'Spacing'), + ('SLIDER_NORMAL_RADIUS', 'Normal Radius'), + ('SLIDER_OTHERS', 'Sliders specific per type'), + ] +others =[ + ('DYNTOPO_MULTIRES', 'Dyntopo & Multires'), + ('SYMMETRY', 'Symmetry'), + ('MIRROR_PLANE', 'Mirror Plane') + ] +utils =[ + ('INCREMENTAL_SAVE', "Icremental Save"), + ] +spacing =[ + ('SEPARATOR', 'Separator'), + ('DEF_SEPARATOR_SPACER', 'Separator Spacer'), + ] diff --git a/atelier/custom_ui/blocks/block_id_draw.py b/atelier/custom_ui/blocks/block_id_draw.py new file mode 100644 index 0000000..e04c558 --- /dev/null +++ b/atelier/custom_ui/blocks/block_id_draw.py @@ -0,0 +1,57 @@ +from enum import Enum, IntEnum +#from functools import partial +from .toolheader import BAS_HT_toolheader_blocks as draw +from .toolheader_default import DEFAULT_SCULPT_MODE_UI_BLOCKS as default + + +# NOTE: Only draw function call in enum (maybe with id) and ui_units_x is returned by +# function, so when iter each value is stored for when editing is enabled. +class UI_BLOCK_DRAW(Enum): + SEPARATOR = draw.separator + B3D_PREFERENCES = draw.blender_preferences + SUPPORT = draw.dev_support + BRUSH_SELECTOR = draw.brush_selector + BRUSH_OPTIONS = draw.brush_options + BRUSH_OPTIONS_COLL = draw.brush_options_collapsed + RENDER_BRUSH_ICON = draw.render_brush_icon + BAS_PREFERENCES = draw.bas_preferences + SETTINGS = draw.settings + TEXTURE_MANAGER = draw.texture_manager + TEXTURE_OPTIONS = draw.texture_options + DYNTOPO_MULTIRES = draw.dyntopo_multires + SYMMETRY = draw.symmetry + MIRROR_PLANE = draw.mirror_plane + MASK = draw.mask + MASK_TOPOLOGY = draw.mask_topology + FRONTFACES = draw.front_faces + FALLOFF = draw.falloff + FALLOFF_PRESETS = draw.falloff_presets + FALLOFF_PRESETS_COLL = draw.falloff_presets_collapse + STROKE = draw.stroke + STROKE_METHOD = draw.stroke_method + BRUSH_SETTINGS = draw.brush_settings + BRUSH_SETTINGS_ADVANCED = draw.brush_settings_advanced + SLIDER_RADIUS = draw.slider_radius + SLIDER_STRENGTH = draw.slider_strength + SLIDER_SMOOTH = draw.slider_smooth + SLIDER_SPACING = draw.slider_spacing + SLIDER_NORMAL_RADIUS = draw.slider_normal_radius + SLIDER_OTHERS = draw.slider_others + + INCREMENTAL_SAVE = draw.incremental_save + + DEF_SEPARATOR_SPACER = default.separator_spacer + + DEF_BRUSH_SELECTOR = default.brush_selector + DEF_BRUSH_SETTINGS = default.brush_settings + DEF_OTHER_BRUSH_SETTINGS = default.other_brush_settings + DEF_DIRECTION = default.direction + DEF_BRUSH_RADIUS = default.slider_radius + DEF_BRUSH_STRENGTH = default.slider_strength + DEF_MIRROR = default.mirror + DEF_STROKE_CURVE_SNAP = default.stroke_curve_snap + + DEF_SWITCH_MODE = default.switch_mode + + def __call__(self, *args): + return self.value(*args) diff --git a/atelier/custom_ui/blocks/block_id_ppts.py b/atelier/custom_ui/blocks/block_id_ppts.py new file mode 100644 index 0000000..10a5ebb --- /dev/null +++ b/atelier/custom_ui/blocks/block_id_ppts.py @@ -0,0 +1,70 @@ + +from enum import Enum +# OLD +# float / tuple or None / float tuple / dict or None +# IDENTIFIER = [DEF_WIDTH, MIN_MAX_WIDTH, PROPERTIES] + +class UI_BLOCK_PPTS(Enum): + # IDENTIFIER # None -> No properties + # Dict with property name followed by its type ('_' if is prefs property) + SEPARATOR = None + B3D_PREFERENCES = () + SUPPORT = () + BRUSH_SELECTOR = { + '_brush_selector_grid_size': int + } + BRUSH_OPTIONS = { + 'brush_options_show_reset': bool, + 'brush_options_show_remove': bool + } + BRUSH_OPTIONS_COLL = None + RENDER_BRUSH_ICON = None + BAS_PREFERENCES = None + SETTINGS = None + TEXTURE_MANAGER = { + '_texture_selector_grid_size': int, + '_image_selector_grid_size': int + } + TEXTURE_OPTIONS = { + 'texture_options_show_new_texture': bool, + 'texture_options_show_open_image': bool + } + DYNTOPO_MULTIRES = None + SYMMETRY = None + MIRROR_PLANE = None + MASK = None + MASK_TOPOLOGY = None + FRONTFACES = None + FALLOFF = None + FALLOFF_PRESETS = None + FALLOFF_PRESETS_COLL = None + STROKE = None + STROKE_METHOD = None + BRUSH_SETTINGS = None + SLIDER_RADIUS = None + SLIDER_STRENGTH = None + SLIDER_SMOOTH = None + SLIDER_SPACING = None + SLIDER_NORMAL_RADIUS = None + SLIDER_OTHERS = None + INCREMENTAL_SAVE = { + '_file_incremental_notation': str, + } + + DEF_SEPARATOR_SPACER = None + DEF_BRUSH_SELECTOR = { + '_brush_selector_grid_size': int + } + DEF_BRUSH_SETTINGS = None + DEF_OTHER_BRUSH_SETTINGS = None + DEF_DIRECTION = None + DEF_BRUSH_RADIUS = None + DEF_BRUSH_STRENGTH = None + DEF_MIRROR = None + DEF_STROKE_CURVE_SNAP = None + + DEF_SWITCH_MODE = None + + + def __call__(self): + return self.value \ No newline at end of file diff --git a/atelier/custom_ui/blocks/header.py b/atelier/custom_ui/blocks/header.py new file mode 100644 index 0000000..571ce30 --- /dev/null +++ b/atelier/custom_ui/blocks/header.py @@ -0,0 +1,60 @@ +class BAS_HT_header_blocks(): + def draw_shading(self, context): + view = context.space_data + shading = view.shading + overlay = view.overlay + layout = self.layout + + # Viewport Settings + layout.popover( + panel="VIEW3D_PT_object_type_visibility", + icon_value=view.icon_from_show_object_viewport, + text="", + ) + + # Gizmo toggle & popover. + row = layout.row(align=True) + # FIXME: place-holder icon. + row.prop(view, "show_gizmo", text="", toggle=True, icon='GIZMO') + sub = row.row(align=True) + sub.active = view.show_gizmo + sub.popover( + panel="VIEW3D_PT_gizmo_display", + text="", + ) + + # Overlay toggle & popover. + row = layout.row(align=True) + row.prop(overlay, "show_overlays", icon='OVERLAY', text="") + sub = row.row(align=True) + sub.active = overlay.show_overlays + sub.popover(panel="VIEW3D_PT_overlay", text="") + + row = layout.row() + #row.active = (object_mode == 'EDIT') or (shading.type in {'WIREFRAME', 'SOLID'}) + + if shading.type == 'WIREFRAME': + row.prop(shading, "show_xray_wireframe", text="", icon='XRAY') + else: + row.prop(shading, "show_xray", text="", icon='XRAY') + + row = layout.row(align=True) + row.prop(shading, "type", text="", expand=True) + sub = row.row(align=True) + # TODO, currently render shading type ignores mesh two-side, until it's supported + # show the shading popover which shows double-sided option. + + # sub.enabled = shading.type != 'RENDERED' + sub.popover(panel="VIEW3D_PT_shading", text="") + + def draw_close_gaps(self, props): + row = self.layout.row(align=True) + row.ui_units_x = 5.4 + prop = row.operator("bas.close_gaps", text="Close Gaps", icon='RESTRICT_INSTANCED_ON') + prop.use = props.use + prop.smooth_passes = props.smooth_passes + prop.keep_dyntopo = props.keep_dyntopo + row.popover ( + panel="BAS_PT_Close_Gaps_Options", + text="" + ) diff --git a/atelier/custom_ui/blocks/toolheader.py b/atelier/custom_ui/blocks/toolheader.py new file mode 100644 index 0000000..68c1eb3 --- /dev/null +++ b/atelier/custom_ui/blocks/toolheader.py @@ -0,0 +1,571 @@ +from ...icons import Icon, get_icon + +# NOTE ####################### +''' +To add new block that can be added to tool header: + +1. Add a function here in BAS_HT_toolheader_blocks with ONLY a 'th' argument. + +2. 'th' is for toolheader.py > BAS_HT_toolHeader, + from 'th' you can access context, brush, custom ui properties, addon preferences, etc. + +3. Copy your function name and go to custom_ui_ids_func.py and + create a new entry with an *identifier* followed by draw.{your_function_name}. + +4. Now go to custom_ui_menu_cats.py and creta a new entry in the desired category, + add the identifier and a name (to be shown in the sub-menu). + +5. To add properties to your UI block so you can change how it looks by RMB over it in edit mode:* + 5.1: In ui_data.py > ToolHeader_PG_custom_ui add your properties. + 5.2: Go to custom_ui_ids_ppts.py and create a new entry with the identifier of your block and + add a dictionary with each property as key and the type of the property as value. + To add property in preferences instead, write an '_' before the property as well as add the property + in addon__utils > prefs.py > BAS_Preferences. + 5.3: In your ui block, access to props via th.props and if it's in preferences, via th.prefs. + * Note: In case you don't want properties, just let an empty dictionary or write None. + +6. (Optional) If you want to create new category, add it in custom_ui_menu_cats.py as a list, + also add the name to the categories tuple a the top of custom_ui_menu_cats.py, + then go to toolheader_dropdowns.py create new class (see 'DefaultMenu' as example) and add the class + to the submenus list over the same place. +''' + +# GLOBAL VARS +dynStage_Active = 0 # 1 = SKETCH; 2 = DETAIL; 3 = POLISH; 0 = "NONE" # Por defecto ningún 'stage' está activado +dynMethod_Active = "NONE" +dyn_values_ui = [] # valores mostrados en la UI # DEFECTO # Cambiarán al cambiar de stage o detailing (method aquí) + +class BAS_HT_toolheader_blocks(): +# SPACING // SPACER + def separator(th): + split = th.layout.split() + col = split.column() + col.label(text="", icon_value=Icon.SEPARATOR()) + + return 1 + +# CUSTOM BRUSH ICON + def render_brush_icon(th): + row = th.layout.row(align=True) + row.operator("bas.brush_render_icon", text="", icon='RESTRICT_RENDER_OFF') + return 1 + +# BRUSH SELECTOR + def brush_selector(th): + rows_cols = th.prefs.brush_selector_grid_size + row = th.layout.row(align=True) + row.ui_units_x = 8 + row.template_ID_preview(th.sculpt, "brush", new="brush.add", rows=rows_cols[0], cols=rows_cols[1], hide_buttons=True) + return row.ui_units_x + +# BRUSH options COLLAPSED // ADD / RESET // REMOVE + def brush_options_collapsed(th): + row = th.layout.row(align=True) + row.popover( + panel="BAS_PT_brush_options_dropdown", + text="") + return 1 + +# BRUSH options // ADD / RESET // REMOVE + def brush_options(th): + row = th.layout.row(align=True) + row.ui_units_x = 1 + if th.props.brush_options_show_add: + row.operator("brush.add", text="", icon_value=Icon.BRUSH_ADD()) + # RESET BRUSH BUTTON + if th.props.brush_options_show_reset: + row.ui_units_x += 1 + row.operator("brush.reset", text="", icon_value=Icon.BRUSH_RESET()) # RESET BRUSH + # DELETE BRUSH BUTTON + if th.props.brush_options_show_remove: + row.ui_units_x += 1 + row.operator("bas.brush_remove", text="", icon_value=Icon.BRUSH_REMOVE()) # DELETE BRUSH + return row.ui_units_x + +# BRUSH SIZE + def slider_radius(th): + row = th.layout.row(align=True) + row.ui_units_x = 4.5 + # row.prop(ups, "use_unified_size", text="Size") # CHECKBOX PARA MARCAR EL UNIFIED SIZE + if(th.ups.use_unified_size): + row.prop(th.ups, "size", slider=True, text="R") # Size + else: + row.prop(th.brush, "size", slider=True, text="R") # Size + row.prop(th.brush, "use_pressure_size", toggle=True, text="") + + return row.ui_units_x + +# BRUSH STRENTH + def slider_strength(th): + row = th.layout.row(align=True) + row.ui_units_x = 4.3 + if(th.ups.use_unified_strength): + row.prop(th.ups, "strength", slider=True, text="S") # Hardness + else: + row.prop(th.brush, "strength", slider=True, text="S") # Hardness + row.prop(th.brush, "use_pressure_strength", toggle=True, text="") + + return row.ui_units_x + +# BRUSH AUTOSMOOTH SLIDER + def slider_smooth(th): + row = th.layout.row(align=True) + # auto_smooth_factor and use_inverse_smooth_pressure + row.ui_units_x = 5.8 + if (th.capabilities.has_auto_smooth): + row.prop(th.brush, "auto_smooth_factor", slider=True, text="Smooth") + row.prop(th.brush, "use_inverse_smooth_pressure", toggle=True, text="") + + return row.ui_units_x + +# BRUSH > STROKE > SPACING SLIDER + def slider_spacing(th): + row = th.layout.row(align=True) + row.ui_units_x = 6 + # Airbrush + if th.brush.use_airbrush: + row.prop(th.brush, "rate", text="Rate", slider=True) + # Space + elif th.brush.use_space: + row.prop(th.brush, "spacing", text="Spacing") + row.prop(th.brush, "use_pressure_spacing", toggle=True, text="") + # Line and Curve + elif th.brush.use_line or th.brush.use_curve: + row.prop(th.brush, "spacing", text="Spacing") + + return row.ui_units_x + +# SLIDER FOR NORMAL RADIUS AND AREA RADIUS (SCRAPE) + def slider_normal_radius(th): + row = th.layout.row(align=True) + row.ui_units_x = 6.5 + row.prop(th.brush, "normal_radius_factor", slider=True) + if th.brush.sculpt_tool == 'SCRAPE': + row.ui_units_x = 10.5 + row.prop(th.brush, "area_radius_factor", slider=True) + + return row.ui_units_x + +# SLIDERS SPECIFICALLY PER EACH BRUSH TYPE + def slider_others(th): + sculpt_tool = th.brush.sculpt_tool + # normal_weight + if th.capabilities.has_normal_weight: + row = th.layout.row(align=True) + ui_units_x = row.ui_units_x = 6.2 + row.prop(th.brush, "normal_weight", slider=True) + + # crease_pinch_factor + elif th.capabilities.has_pinch_factor: + row = th.layout.row(align=True) + ui_units_x = row.ui_units_x = 6 + row.prop(th.brush, "crease_pinch_factor", slider=True, text="Pinch") + + # rake_factor + elif th.capabilities.has_rake_factor: + row = th.layout.row(align=True) + ui_units_x = row.ui_units_x = 6 + row.prop(th.brush, "rake_factor", slider=True) + + elif sculpt_tool == 'MASK': + row = th.layout.row(align=True) + ui_units_x = row.ui_units_x = 6 + row.prop(th.brush, "mask_tool", text="Tool") + + # plane_offset, use_offset_pressure, use_plane_trim, plane_trim + elif th.capabilities.has_plane_offset: + row = th.layout.row(align=True) + ui_units_x = row.ui_units_x = 6 + row.prop(th.brush, "plane_offset", slider=True, text="Offset") + row.prop(th.brush, "use_offset_pressure", text="") + row = th.layout.row() + # row.ui_units_x = 2.7 + ui_units_x += 3.04 + row.prop(th.brush, "use_plane_trim", text="Trim") + if th.brush.use_plane_trim: + row = th.layout.row() + row.ui_units_x = 4.5 + row = th.layout.row() + row.prop(th.brush, "plane_trim", slider=True, text="Distance") + ui_units_x += 7.36 + + # height + elif th.capabilities.has_height: + row = th.layout.row(align=True) + ui_units_x = row.ui_units_x = 6 + row.prop(th.brush, "height", slider=True, text="Height") + else: + ui_units_x = 0 + + ''' + [‘DRAW’, ‘DRAW_SHARP’, ‘CLAY’, ‘CLAY_STRIPS’, ‘CLAY_THUMB’, ‘LAYER’, ‘INFLATE’, + ‘BLOB’, ‘CREASE’, ‘SMOOTH’, ‘FLATTEN’, ‘FILL’, ‘SCRAPE’, ‘MULTIPLANE_SCRAPE’, + ‘PINCH’, ‘GRAB’, ‘ELASTIC_DEFORM’, ‘SNAKE_HOOK’, ‘THUMB’, ‘POSE’, ‘NUDGE’, + ‘ROTATE’, ‘TOPOLOGY’, ‘CLOTH’, ‘SIMPLIFY’, ‘MASK’, ‘DRAW_FACE_SETS’], + ''' + + if sculpt_tool == 'CLAY_STRIPS': + row = th.layout.row() + row.ui_units_x = 5.6 + ui_units_x += 5.83 + row.prop(th.brush, "tip_roundness", slider=True, text="Roundnesss") + elif sculpt_tool == 'LAYER': + row = th.layout.row(align=True) + row.ui_units_x = 6.5 + ui_units_x += 6.73 + row.prop(th.brush, "use_persistent", slider=True, text="Persistent") + row.operator('sculpt.set_persistent_base', text="Set") + elif sculpt_tool == 'SMOOTH': + row = th.layout.row() + row.ui_units_x = 7 + ui_units_x += 8.26 + row.prop(th.brush, "smooth_deform_type", text="Deformation") + elif sculpt_tool in {'SCRAPE', 'FILL'}: + row = th.layout.row() + row.ui_units_x = 6.2 + ui_units_x += 6.48 + row.prop(th.brush, "area_radius_factor", text="Area Radius", slider=True) + elif sculpt_tool == 'MULTIPLANE_SCRAPE': + row = th.layout.row() + row.ui_units_x = 5 + ui_units_x += 5 + row.prop(th.brush, "multiplane_scrape_angle", text="Angle", slider=True) + elif sculpt_tool == 'GRAB': + row = th.layout.row() + ui_units_x += 6.71 + row.prop(th.brush, "use_grab_active_vertex", text="Grab Active Vertex", toggle=False) + elif sculpt_tool == 'ELASTIC_DEFORM': + row = th.layout.row(align=True) + row.ui_units_x = 16 + ui_units_x += 16.26 + row.prop(th.brush, "elastic_deform_type", text="Deformation") + row.prop(th.brush, "elastic_deform_volume_preservation", text="Volume Preservation", slider=True) + elif sculpt_tool == 'SNAKE_HOOK': + row = th.layout.row(align=True) + row.ui_units_x = 4.2 + ui_units_x += 4.44 + row.prop(th.brush, "rake_factor", text="Rake", slider=True) + elif sculpt_tool == 'POSE': + row = th.layout.row(align=True) + row.ui_units_x = 21 + ui_units_x += 21 + row.prop(th.brush, "pose_origin_type", text="Type") + row.prop(th.brush, "pose_offset", text="Offset") + row.prop(th.brush, "pose_smooth_iterations", text="Smooth") + row.prop(th.brush, "pose_ik_segments", text="IK Segments") + elif sculpt_tool == 'CLOTH': + # TODO: change to a dropdown panel. (popover) + row = th.layout.row(align=True) + row.ui_units_x = 23 + ui_units_x += 23 + row.prop(th.brush, "cloth_deform_type", text="Deformation") + row.prop(th.brush, "cloth_force_falloff_type", text="Falloff") + row.prop(th.brush, "cloth_mass", text="Mass", slider=True) + row.prop(th.brush, "cloth_damping", text="Damping") + # print("UI_UNITS_X:", ui_units_x) + return ui_units_x if ui_units_x != 0 else -1 + + # use_persistent, set_persistent_base + ''' + if capabilities.has_persistence: + ob = context.sculpt_object + do_persistent = True + + # not supported yet for this case + for md in ob.modifiers: + if md.type == 'MULTIRES': + do_persistent = False + break + + if do_persistent: + row = col.row(align=True) + row.prop(brush, "use_persistent") + row.operator("sculpt.set_persistent_base") + ''' + +# BRUSH SETTINGS (DROPDOWN) + def brush_settings(th): + row = th.layout.row() + row.ui_units_x = 1.5 + row.popover( + panel="VIEW3D_PT_tools_brush_settings", # b283 from *_brush to *_brush_settings + icon_value=Icon.BRUSH(), + text="") + #VIEW3D_PT_sculpt_options_unified + return row.ui_units_x + +# BRUSH SETTINGS (DROPDOWN) + def brush_settings_advanced(th): + row = th.layout.row() + row.ui_units_x = 1.5 + row.popover( + panel="VIEW3D_PT_tools_brush_settings_advanced", # b283 from *_brush to *_brush_settings + icon_value=Icon.BRUSH(), + text="") + #VIEW3D_PT_sculpt_options_unified + return row.ui_units_x + +# BRUSH STROKE SETTINGS (DROPDOWN) + def stroke(th): + row = th.layout.row() + row.ui_units_x = 1.5 + row.popover( + panel="VIEW3D_PT_tools_brush_stroke", + icon_value=Icon.STROKE(), + text="") + return row.ui_units_x + +# BRUSH STROKE METHOD + def stroke_method(th, only_icons=True): + row = th.layout.row() + if only_icons: + ui_units_x = row.ui_units_x = 1.5 + else: + ui_units_x = row.ui_units_x = 2.5 + len(th.brush.stroke_method)/4 + row.prop(th.brush, "stroke_method", text="", icon_value=Icon.STROKE()) + + return ui_units_x + +# BRUSH FALLOFF SETTINGS/CURVES (DROPDOWN) + def falloff(th): + row = th.layout.row() + row.ui_units_x = 1.5 + row.popover( + panel="VIEW3D_PT_tools_brush_falloff", + icon_value=Icon.FALLOFF(), + text="") + + return row.ui_units_x + +# BRUSH FALLOFF CURVE PRESETS + def falloff_presets(th): + props = th.props + row = th.layout.row(align=True) + row.ui_units_x = 6 + + row.operator("bas.falloff_curve_presets", icon='SMOOTHCURVE', depress=(not props.depress_smooth)).shape = 'SMOOTH' + row.operator("bas.falloff_curve_presets", icon='SPHERECURVE', depress=(not props.depress_round)).shape = 'SPHERE' + row.operator("bas.falloff_curve_presets", icon='ROOTCURVE', depress=(not props.depress_root)).shape = 'ROOT' + row.operator("bas.falloff_curve_presets", icon='SHARPCURVE', depress=(not props.depress_sharp)).shape = 'SHARP' + row.operator("bas.falloff_curve_presets", icon='LINCURVE', depress=(not props.depress_line)).shape = 'LIN' + row.operator("bas.falloff_curve_presets", icon='NOCURVE', depress=(not props.depress_max)).shape = 'CONSTANT' + + return row.ui_units_x + + def falloff_presets_collapse(th): + row = th.layout.row(align=True) + row.ui_units_x = 1.5 + row.prop(th.brush, "curve_preset", text="") + + return row.ui_units_x + +# FRONT FACES ONLY (TOGGLE) + def front_faces(th): + split = th.layout.row() + #col = split.row() + split.prop(th.brush, "use_frontface", text="", icon_value=Icon.FRONTFACES()) + + return 1 + +# AUTOMASK BY TOPOLOGY + def mask_topology(th): + split = th.layout.row() + split.prop(th.brush, "use_automasking_topology", text="", icon_value=Icon.MASK_TOPOLOGY()) + + return 1 + +# MASK SETTINGS / INVERT / CLEAR + def mask(th): + # MASK MENU + row = th.layout.row(align=True) + row.ui_units_x = 2 + #row.menu("VIEW3D_MT_hide_mask", text=" Mask ", icon_value=Icon.MASK()) + # MASK -> INVERT + props = row.operator("paint.mask_flood_fill", text="", icon_value=Icon.MASK_INVERT()) + props.mode = 'INVERT' + # MASK -> CLEAR + props = row.operator("paint.mask_flood_fill", text="", icon_value=Icon.MASK_CLEAR()) + props.mode = 'VALUE' + props.value = 0 + + return row.ui_units_x + +# SYMMETRY TOGGLES + def symmetry(th): + # MIRRORS X, Y, Z + _row = th.layout.row(align=True) + _row.ui_units_x = 3.5 + _row.prop(th.sculpt, "use_symmetry_x", text="X", toggle=True) + _row.prop(th.sculpt, "use_symmetry_y", text="Y", toggle=True) + _row.prop(th.sculpt, "use_symmetry_z", text="Z", toggle=True) + #row = th.layout.row(align=True) + #row.popover(panel="BAS_PT_mirror_plane_options", text="", icon_value=Icon.MIRROR()) + #if mirror_plane: + # _icon = 'HIDE_OFF' if scn.show else 'HIDE_ON' + # row.prop(scn, 'show', text="", icon=_icon, toggle=True) + + return _row.ui_units_x + +# MIRROR + MIRROR PLANE TOGGLES + def mirror_plane(th): + units = BAS_HT_toolheader_blocks.symmetry(th) + _row = th.layout.row(align=True) + _row.ui_units_x = 2 + mirror = th.context.scene.bas_mirrorplane + _row.popover(panel="BAS_PT_mirror_plane_options", text="", icon_value=Icon.MIRROR()) + if mirror.created: + _row.ui_units_x = 3 + _icon = 'HIDE_OFF' if mirror.show else 'HIDE_ON' + _row.prop(mirror, 'show', text="", icon=_icon, toggle=True) + + return _row.ui_units_x + units + +# TOPOLOGY SETTINGS / DYNTOPO / MULTIRES + def dyntopo_multires(th): + layout = th.layout + context = th.context + props = th.props + + mods = context.active_object.modifiers + # SCULPT --> MULTIRES + if mods != None: + for modifier in mods: + # Si el modificador 'modifier' es de tipo Multires + if modifier.type == 'MULTIRES': + row = layout.row(align=True) + row.label(text="", icon="MOD_MULTIRES") + row.ui_units_x = 5 + row.prop(modifier, "sculpt_levels", text="Sculpt") + row = layout.row(align=True) + row.ui_units_x = 3.6 + row.operator("object.multires_subdivide", text="Subdivide").modifier = "Multires" + row = layout.row(align=True) + row.ui_units_x = 3.8 + row.prop(sculpt, "show_low_resolution", text="Fast Nav", toggle=False) + return 13 + + sub = layout.row(align=True) + sub.popover(panel="VIEW3D_PT_sculpt_dyntopo", text="") + + # SCULPT --> DYNAMIC TOPOLOGY + if(context.sculpt_object.use_dynamic_topology_sculpting): + dyntopo = context.scene.bas_dyntopo + dynStage_Active = dyntopo.stage + useStage = dyntopo.toggle_stages + + # Si hay stage + if not useStage: + from ..tools.dyntopo_pro.ui import dyntopoStages + dynMethod_Active = dyntopo.detailing + n = int(dynStage_Active) - 1 # CHIVATO PARA EL STAGE + + # LOOK FOR ACTUAL DYN METHOD + if(dynMethod_Active == "RELATIVE"): + dyn_values_ui = dyntopoStages[n].relative_Values + icon = Icon.DYNTOPO_RELATIVE() + elif(dynMethod_Active == "CONSTANT"): + dyn_values_ui = dyntopoStages[n].constant_Values + icon = Icon.DYNTOPO_CONSTANT() + elif(dynMethod_Active == "BRUSH"): + dyn_values_ui = dyntopoStages[n].brush_Values + icon = Icon.DYNTOPO_BRUSH() + elif(dynMethod_Active == "MANUAL"): + dyn_values_ui = dyntopoStages[n].relative_Values + icon = Icon.DYNTOPO_MANUAL() + + # PANEL DESPLEGABLE + sub.popover(panel="BAS_PT_dyntopo_stages", text="", icon_value=icon) + row = layout.row(align=True) + + # BOTONES PARA OPCIONES/VALORES PARA DETAIL SIZE DE DYNTOPO SEGUN EL METHOD Y STAGE + detail_icon = [Icon.DYNTOPO_LOW(), Icon.DYNTOPO_MEDIUM(), Icon.DYNTOPO_HIGH()] + detail_level = context.scene.bas_dyntopo.detail_level + for i in range(1, 4): + op = row.operator("bas.dyntopo_change_value", text="", icon_value=detail_icon[i-1], depress=(i == detail_level)) + op.value = dyn_values_ui[i-1] # LOW DETAIL + op.detail = i + return 6.83 + # Si no hay ningún 'Stage' activado + else: + sub.popover(panel="BAS_PT_dyntopo_stages", text="", icon='STYLUS_PRESSURE') # NUEVO PANEL PARA LOS 'STAGES' + + col = layout.column() + row = col.row(align=True) + row.ui_units_x = 6 + + active = dyntopo.levels_active + for lvl in range(1, 7): + row.operator("bas.dyntopo_change_level", text=str(lvl), depress=(lvl == active)).lvl = lvl + return 9.83 + return 1.93 + + +# TEXTURE SETTINGS (DROPDOWN) / NEW TEXTURE / OPEN IMAGE + def texture_options(th): + row = th.layout.row(align=True) + row.popover(panel="VIEW3D_PT_tools_brush_texture", icon_value=Icon.TEXTURE(), text="") + x = 1.6 + if th.props.texture_options_show_new_texture: + x += 1 + row.operator("texture.new", text="", icon_value=Icon.TEXTURE_NEW()) # NEW TEXTURE + if th.props.texture_options_show_open_image: + x += 1 + row.operator("image.open", text="", icon_value=Icon.TEXTURE_OPEN()) # OPEN IMAGE TEXTURE + + return x + +# TEXTURE QUICK SELECTOR + def texture_manager(th): + brush = th.brush + texture = brush.texture + tex_rows_cols = th.prefs.texture_selector_grid_size + img_rows_cols = th.prefs.image_selector_grid_size + row = th.layout.row(align=True) + ## TEXTURES AND IMAGES + if texture != None: # HAY TEXTURA + if texture.image != None: # image_user -> image # LA TEXTURA TIENE IMAGEN + if th.props.texture_manager_collapse: + row.ui_units_x = 4 + row.prop(brush, "texture", text="") + row.prop(texture, "image", text="") # open="image.open" + else: + row.ui_units_x = 14 + row.template_ID_preview(brush, "texture", rows=tex_rows_cols[0], cols=tex_rows_cols[1], hide_buttons=True) + row.template_ID_preview(texture, "image", rows=img_rows_cols[0], cols=img_rows_cols[1], hide_buttons=True) # open="image.open" + else: # LA TEXTURA NOO TIENE IMAGEN + row.ui_units_x = 5 + row.template_ID_preview(texture, "image", rows=img_rows_cols[0], cols=img_rows_cols[1], open="image.open", hide_buttons=False) # open="image.open" + else: # NO HAY TEXTURA + row.ui_units_x = 5 + row.template_ID_preview(brush, "texture", rows=tex_rows_cols[0], cols=tex_rows_cols[1], new="texture.new", hide_buttons=False) + + return row.ui_units_x + +# INCREMENTAL SAVE + def incremental_save(th): + th.layout.operator('file.incremental_save', text="", icon_value=get_icon(Icon.BRUSH_SAVE)) + return 1 + +# PANEL FOR TOGGLE UI ELEMENTS + def settings(layout): + sub = layout.split().column(align=True) + sub.popover(panel="NSMUI_PT_th_settings",icon='HIDE_OFF',text="") + +# PREFERENCES PANEL + def bas_preferences(layout): + sub = layout.split().column(align=True) + sub.popover(panel="NSMUI_PT_Addon_Prefs",icon='PREFERENCES',text="") + +# BLENDER QUICK PREFERENCES FOR SCULPT + def blender_preferences(layout): + sub = layout.split().column(align=True) + sub.popover(panel="BAS_PT_Blender_QuickPrefs",icon='BLENDER',text="") + + def dev_support(layout): + prefs = context.preferences.addons["BlenderAtelier_Sculpt"].preferences + sub = layout.split().column(align=True) + if prefs.need_updating: + text="Update Available!" + else: + text="" + sub.popover(panel="NSMUI_PT_dev_support",icon='FUND',text=text) diff --git a/atelier/custom_ui/blocks/toolheader_default.py b/atelier/custom_ui/blocks/toolheader_default.py new file mode 100644 index 0000000..23a9b3e --- /dev/null +++ b/atelier/custom_ui/blocks/toolheader_default.py @@ -0,0 +1,127 @@ +from bl_ui.properties_paint_common import UnifiedPaintPanel +from bpy.types import ToolSettings + +class DEFAULT_SCULPT_MODE_UI_BLOCKS: + # SPACING // SEPARATOR_SPACING + def separator_spacer(parent): + parent.layout.separator_spacer() + return 0 + + def switch_mode(parent): + row = parent.layout.row(align=True) + row.ui_units_x = 1.6 + row.template_header() + return row.ui_units_x + + def brush_type_icon(parent): + return 3 + + def brush_selector(parent): + paint_settings = UnifiedPaintPanel.paint_settings(parent.context) + if paint_settings: + rows_cols = parent.prefs.brush_selector_grid_size + parent.layout.template_ID_preview(paint_settings, "brush", rows=rows_cols[0], cols=rows_cols[1], hide_buttons=True) + return 7.6 + return -1 + + def brush_settings(parent): + row = parent.layout.row() + row.popover("VIEW3D_PT_tools_brush_settings_advanced", text="Brush") + row.popover("VIEW3D_PT_tools_brush_stroke") + row.popover("VIEW3D_PT_tools_brush_falloff") + row.popover("VIEW3D_PT_tools_brush_display") + + return 12.3 + + def slider_radius(parent): + size = "size" + size_owner = parent.ups if parent.ups.use_unified_size else parent.brush + if size_owner.use_locked_size == 'SCENE': + size = "unprojected_radius" + + UnifiedPaintPanel.prop_unified( + parent.layout, + parent.context, + parent.brush, + size, + pressure_name="use_pressure_size", + unified_name="use_unified_size", + text="Radius", + slider=True, + header=True + ) + return 7.3 + + def slider_strength(parent): + # strength, use_strength_pressure + pressure_name = "use_pressure_strength" if parent.capabilities.has_strength_pressure else None + UnifiedPaintPanel.prop_unified( + parent.layout, + parent.context, + parent.brush, + "strength", + pressure_name=pressure_name, + unified_name="use_unified_strength", + text="Strength", + header=True + ) + return 7.75 + + def direction(parent): + # direction + if not parent.capabilities.has_direction: + row = parent.layout.row() + row.ui_units_x = 1.9 + row.prop(parent.brush, "direction", expand=True, text="") + return row.ui_units_x + return -1 + + def mirror(parent): + row = parent.layout.row(align=True) + row.ui_units_x = 4.5 + row.label(icon='MOD_MIRROR') + sub = row.row(align=True) + sub.scale_x = 0.6 + sculpt = parent.sculpt + sub.prop(sculpt, "use_symmetry_x", text="X", toggle=True) + sub.prop(sculpt, "use_symmetry_y", text="Y", toggle=True) + sub.prop(sculpt, "use_symmetry_z", text="Z", toggle=True) + row.popover(panel="VIEW3D_PT_sculpt_symmetry_for_topbar", text="") + + return 4.5 # sub.scale_x * 3 + 2.66 + + def other_brush_settings(parent): + # Expand panels from the side-bar as popovers. + popover_kw = {"space_type": 'VIEW_3D', "region_type": 'UI', "category": "Tool"} + parent.layout.popover_group(context=".sculpt_mode", **popover_kw) + return 11.25 + + def stroke_curve_snap(parent): + paint_settings = UnifiedPaintPanel.paint_settings(parent.context) + if paint_settings: + brush = paint_settings.brush + if brush and hasattr(brush, "stroke_method") and brush.stroke_method == 'CURVE': + snap_items = ToolSettings.bl_rna.properties["snap_elements"].enum_items + tool_settings = parent.context.tool_settings + snap_elements = tool_settings.snap_elements + if len(snap_elements) == 1: + text = "" + for elem in snap_elements: + icon = snap_items[elem].icon + break + else: + text = "Mix" + icon = 'NONE' + del snap_items, snap_elements + + row = parent.layout.row(align=True) + row.prop(tool_settings, "use_snap", text="") + + sub = row.row(align=True) + sub.popover( + panel="VIEW3D_PT_snapping", + icon=icon, + text=text, + ) + return 2.6 + return -1 # Invalid \ No newline at end of file diff --git a/atelier/custom_ui/config/__pycache__/default_config.cpython-37.pyc b/atelier/custom_ui/config/__pycache__/default_config.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1939ec6153a468c83d8c978c0789346fe2f45c1 GIT binary patch literal 364 zcmZvXy-EZz5XY13>Mg=`+F041#e!NXBHk%iSO|L9EP)U#nHJx16 zD{sy1WIK+muk)HC>5eP1WR_Sl)=347#l3$IydhWR;VfS-S7-FWgY)!ePN#i;%>$?R zmTPI2bf&-va0hcc7{PHUGfdF!EK)a literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/config/default_config.py b/atelier/custom_ui/config/default_config.py new file mode 100644 index 0000000..223f391 --- /dev/null +++ b/atelier/custom_ui/config/default_config.py @@ -0,0 +1,3 @@ +from os.path import realpath, dirname, join + +default_sculpt_config = join(dirname(realpath(__file__)), "default_sculpt_config.json") diff --git a/atelier/custom_ui/config/default_sculpt_config.json b/atelier/custom_ui/config/default_sculpt_config.json new file mode 100644 index 0000000..caf817f --- /dev/null +++ b/atelier/custom_ui/config/default_sculpt_config.json @@ -0,0 +1,15 @@ +{ + "blocks": [ + "DEF_SWITCH_MODE", + "DEF_BRUSH_SELECTOR", + "DEF_BRUSH_RADIUS", + "DEF_BRUSH_STRENGTH", + "DEF_DIRECTION", + "DEF_BRUSH_SETTINGS", + "DEF_SEPARATOR_SPACER", + "DEF_STROKE_CURVE_SNAP", + "DEF_SEPARATOR_SPACER", + "DEF_MIRROR", + "DEF_OTHER_BRUSH_SETTINGS" + ] +} \ No newline at end of file diff --git a/atelier/custom_ui/data.py b/atelier/custom_ui/data.py new file mode 100644 index 0000000..b38f2d9 --- /dev/null +++ b/atelier/custom_ui/data.py @@ -0,0 +1,70 @@ +from bpy.types import PropertyGroup +from bpy.props import ( + BoolProperty, IntVectorProperty, + FloatVectorProperty, CollectionProperty, + IntProperty, StringProperty, + PointerProperty, FloatProperty, + BoolVectorProperty, EnumProperty +) + +class BLOCK_ITEM(PropertyGroup): + draw_function = [None] + width : IntProperty(default=1) + x : IntProperty(default=1) + name : StringProperty(default="Name") + id : StringProperty(default='') + + def draw(self, th): + self.draw_function[0](th) + + +class BLOCK_LIST(PropertyGroup): + block_slots : CollectionProperty(type=BLOCK_ITEM) + active_block_index : IntProperty(default=0) + + +class ToolHeader_PG_custom_ui(PropertyGroup): + #blocks = [] + #width = [] + #pos_x = [] + # NOTE: can't modify data to fill width from draw functions in panel so is useful shit... + #ui_blocks : PointerProperty(type=BLOCK_LIST) + #_______________# + # BRUSH MANAGER # + # BRUSH OPTIONS # (Linked with Brush manager as it's used when show_collapsed) + #bman_selection_grid_size : IntVectorProperty ( + # size=2, default=(8, 6), min=1, max=20, step=1, + # name="Grid Size", description="Brush selection horizontal and vertical slots" + #) + brush_options_show_add : BoolProperty(default=True, name="Show Add Brush Button") + brush_options_show_remove : BoolProperty(default=True, name="Show Remove Brush Button") + brush_options_show_reset : BoolProperty(default=True, name="Show Reset Brush Button") + brush_options_show_collapsed : BoolProperty(default=False, name="Show Collapsed (dropdown panel)") + + #_______________# + # BRUSH MANAGER # + show_mesh_tools : BoolProperty(default=True, name="Show Mesh Tools") + + #_______________# + # TEXTURE MANAGER # + texture_manager_collapse : BoolProperty(default=False, name="Collapse") + + #_________________# + # TEXTURE OPTIONS # + texture_options_show_new_texture : BoolProperty(default=False, name="Show new texture button") + texture_options_show_open_image : BoolProperty(default=False, name="Show open image button") + + #_________________# + # FALLOFF PRESETS # + depress_smooth : BoolProperty(default=True) # True is False! ;) + depress_round : BoolProperty(default=True) + depress_root : BoolProperty(default=True) + depress_sharp : BoolProperty(default=True) + depress_line : BoolProperty(default=True) + depress_max : BoolProperty(default=True) + + +classes = [ + BLOCK_ITEM, BLOCK_LIST, + ToolHeader_PG_custom_ui, +] diff --git a/atelier/custom_ui/draw.py b/atelier/custom_ui/draw.py new file mode 100644 index 0000000..58e582a --- /dev/null +++ b/atelier/custom_ui/draw.py @@ -0,0 +1,253 @@ +from ..utils.draw2d import Draw_2D_Rectangle +from .blocks.block_data import blocks + + +def edit_custom_ui_callback_px(self, context): + if self.area != context.area: + return + + update_interface(context) + + active_index = self.active_slot_index + y = 2 + height = self.toolheader.height - 6 + + if self.moving: + co = self.new_color + Draw_2D_Rectangle( + blocks[active_index].x, y, blocks[active_index].width, height, (co[0], co[1], co[2], 1)) + + #Draw_2D_Rectangle(0, 0, self.toolheader.width, self.toolheader.height, (.4, .9, .4, .15)) + + if self.active_item: + self.moving_x = int( + self.mouse_pos[0] - blocks[self.active_slot_index].width / 2) + x = self.moving_x if self.moving else blocks[active_index].x + a = .75 if self.moving else .6 + Draw_2D_Rectangle( + x, y, blocks[active_index].width, height, (1, 1, 1, a)) + + +def update_interface(context): + scale = context.preferences.view.ui_scale + bl_ui_unit = 20 * scale + sep = 5 * scale + x = 3 * scale + w = 0 + x0 = x + sep + + num_seps = 0 + + groups = [] + group = [] + group_width = 0 + total_width = 0 + invalid_group_indices = invalid_group_block_min_max = invalid_block_indices = [] + #prev_sep_idx = -1 + prev_gr_index = 0 + previous_was_spacer = False + + index_that_are_separator_spacer = [] + + for i, block in enumerate(blocks): + w = block.ui_units_x + if w == -1: + print("INVALID BLOCK:", block.id) + block.width = -1 + invalid_block_indices.append(block) + #if group_width == 0: + #group_width -= sep + previous_was_spacer = False + pass + elif w == 0: + invalid_group_block_min_max.append([prev_gr_index, i]) + if group_width == 0: + invalid_group_indices.append(len(groups)) + # invalid_group_block_max.append(i) + #prev_sep_idx = i + prev_gr_index = i + #group_width -= sep + # print(group_width) + #print([item.id for item in group]) + groups.append([group, group_width]) + total_width += group_width + num_seps += 1 + group = group.copy() + group.clear() + group_width = 0 + index_that_are_separator_spacer.append(i) + previous_was_spacer = True + # group.append(block) + else: + w *= bl_ui_unit + group_width += w + sep + block.width = w + group.append(block) + previous_was_spacer = False + + groups.append([group, group_width]) + total_width += group_width + # group.clear() + reg_width = context.region.width + + off_x = context.region.view2d.region_to_view(0, 0)[0] + + #print(reg_width - total_width) + # bef_width + bl_ui_unit + scale + sep >= aft_start_x: + so_tiny = reg_width - (total_width + bl_ui_unit * 1.5) < sep + 1 + + if off_x != 0: + print("SCROLLING!") + # To optimize (not process all the thing) we know if scrolling is ON, this is gone... + sep_ext = sep * 1.2 + for ss_index in index_that_are_separator_spacer: + blocks[ss_index].width = sep_ext + + off_x *= 2 + view2d = context.region.view2d + for i, block in enumerate(blocks): + if block.width != -1: + x += sep + #block.width = w = block(self) * bl_ui_unit + block.abs_x = x + block.x = view2d.region_to_view(x, 0)[0] - off_x + #print("Item: %i - X: %i - Real X: %i" % (i, x, block.x)) + x += block.width # w + elif so_tiny: + print("SO TINY!") + # THIS FIX SOME BUG(s) WHEN VALUES ARE TINY OR BECOME NEGATIVE + sep_ext = sep * 1.2 + for ss_index in index_that_are_separator_spacer: + blocks[ss_index].width = sep_ext + for block in blocks: + if block.width != -1: + x += sep + block.x = block.abs_x = x + x += block.width + else: + num_groups = len(groups) - 1 # -1 only for index separator way + width_count = 0 + + # reversed(list(enumerate())) + for g_index, ss_index in enumerate(index_that_are_separator_spacer): + bef_width = groups[g_index][1] + width_count += bef_width + print("GINDEX:", g_index, " SSINDEX:", ss_index, " NUM_GROUPS:", + num_groups, " NUM_SEPS:", num_seps, " TOTAL_WIDTH:", total_width) + #print("Group Width:", groups[g_index][1]) + # THERE'S NOT AFTER GROUP + if g_index > num_groups or num_seps == 1: + print("NO AFTER GROUP! or num seps == 1") + blocks[ss_index].width = reg_width - total_width - sep * 3.25 + break + else: + if num_seps == 2: + aft_width = groups[g_index + 1][1] + print("num seps == 2") + # GROUPS 1-2 + if g_index == 0: + print("== 0 Group Index") + # Group too close to previous one + # NOTE: have to add aft_mid_width of the group as it's in the middle + # also one sep just to take into account the separation between groups. + if bef_width + aft_width / 2.0 + sep > reg_width / 2.0: + print("No before's spacing") + blocks[ss_index].width = sep * 1.2 + else: + aft_mid_x = reg_width / num_seps # Take mid # + # Offset mid to get start point using width + aft_start_x = aft_mid_x - aft_width / 2.0 + # Diff start with prev group width and standard unit and scale + blocks[ss_index].width = aft_start_x - \ + bef_width - bl_ui_unit - scale + # GROUPS 2-3 + elif g_index == 1: + print("== 1 Group Index") + sep_start_x = width_count + sep_final_x = reg_width - aft_width + blocks[ss_index].width = sep_final_x - \ + sep_start_x - bl_ui_unit * 1.5 + x0 * 1.1 # 1.1 + else: + # print(g_index) + print("num seps > 2") + next_group_width = groups[g_index + 1][1] + if g_index == 0: + print("FIRST ONE") + aft_mid_x = reg_width / num_seps # Take mid of next group + # Offset mid to get start point using width + aft_start_x = aft_mid_x - next_group_width / 2.0 + sep_width = aft_start_x - bef_width - bl_ui_unit - sep * .33 + elif (g_index + 1) == num_groups: + print("LAST ONE") + # END POINT - START POINT + sep_width = (reg_width - next_group_width) - \ + width_count - bl_ui_unit - sep * 1.2 + else: + print("IN THE MIDDLE") + aft_mid_x = (reg_width / num_seps) * \ + (g_index + 1) # Take mid # + # Offset mid to get start point using width + aft_start_x = aft_mid_x - next_group_width / 2.0 + # Diff start with prev group width and standard unit and scale + sep_width = aft_start_x - width_count - bl_ui_unit - sep * 1.4 + + sep_width = sep if sep_width < sep + 1 else sep_width + blocks[ss_index].width = sep_width + # print(sep_width) + + width_count += blocks[ss_index].width + total_width += blocks[ss_index].width + + # OLD METHOD. But... + ''' + for gindex in range(0, len(groups)): + if groups[gindex][1] != 0: + for i in range(invalid_group_block_min_max[gindex][0], invalid_group_block_min_max[gindex][1]): + if blocks[i].width != -1: + x += sep + blocks[i].abs_x = blocks[i].x = x + x += blocks[i].width # w + ''' + + for i, block in enumerate(blocks): + #if num_seps != 0 and block in invalid_block_indices: # verify if it has separator and group is 0... + # x -= sep + 1 + if block.width == -1: + pass # x -= 1 + #elif block.ui_units_x == -1: + # if num_seps != 0: + # x -= sep + 1 + else: + x += sep + block.abs_x = block.x = x + x += block.width # w + + # When having 2 separators together and in the block between them + # is -1 in width (not been drawn), there was a separator counting up, + # and that was causing issues in edit mode. + # INSTEAD. We need to iter the groups and each group block. + # then, asign abs_x, x to blocks[idx], avoid groups with 0 width. + ''' + block_idx = 0 + for gr_index, gr in enumerate(groups): + if (gr_index + 1) <= num_seps: + #gr[0].append(blocks[len(gr[0]) + 1]) + max_index = len(gr[0]) + 1 + else: + max_index = len(gr[0]) + if gr_index in invalid_group_indices: + #max_index = invalid_group_block_max[gr_index] + x += blocks[invalid_group_block_max[gr_index]].width + else: + if gr[1] != 0: + print(max_index) + for idx in range(block_idx, block_idx + max_index): + if blocks[idx].width != -1: + x += sep + blocks[idx].abs_x = blocks[idx].x = x + x += blocks[idx].width + print(x, blocks[idx].width) + else: + print("Group with width", gr[1], gr[0]) + block_idx += max_index + ''' diff --git a/atelier/custom_ui/io.py b/atelier/custom_ui/io.py new file mode 100644 index 0000000..4a5f441 --- /dev/null +++ b/atelier/custom_ui/io.py @@ -0,0 +1,208 @@ +from .. import __package__ as main_package + + +def save_actual_ui_state(context): + from .blocks.block_data import blocks + if not blocks: + return False + + save_path = context.preferences.addons[main_package].preferences.get_custom_ui_presets(True) + if save_path == "" or save_path == 'NONE': + from os.path import join + save_path = join(context.preferences.addons[main_package].preferences.saved_custom_ui_folder, "autosave.json") + + strings = [] + for block in blocks: + strings.append(block.id) + + data = { + 'blocks' : strings + } + + import json + + with open(save_path, 'w+', encoding='utf-8') as json_file: # Después de salir del bloque del "with" el archivo es cerrado automáticamente + json.dump(data, json_file, ensure_ascii=False, indent=4) + + return True + + +def duplicate_custom_ui_preset(context): + from .blocks.block_data import blocks + if not blocks: + return False + + save_path = context.preferences.addons[main_package].preferences.get_custom_ui_presets(False) + + last_char = save_path[-1:] + if last_char.isnumeric(): + new_num = int(last_char) + 1 + save_path += str(new_num) + else: + save_path += "_1" + + save_path += ".json" + + from os.path import isfile + if isfile(save_path): + print("Save path is not file") + return False + + strings = [] + for block in blocks: + strings.append(block.id) + + data = { + 'blocks' : strings + } + + import json + + with open(save_path, 'w+', encoding='utf-8') as json_file: # Después de salir del bloque del "with" el archivo es cerrado automáticamente + json.dump(data, json_file, ensure_ascii=False, indent=4) + + # UPDATE ACTIVE PRESET SLOT TO NEW ONE + # TODO + return True + +def create_custom_ui_preset(context, preset_name): + from .blocks.block_data import blocks + if preset_name == "": + return False + + from os.path import join, isfile + preset_name += ".json" if not preset_name.endswith('.json') else preset_name + save_path = join(context.preferences.addons[main_package].preferences.saved_custom_ui_folder, preset_name) + + if isfile(save_path): + return False + + strings = [] + for block in blocks: + strings.append(block.id) + + data = {} + + import json + + with open(save_path, 'x', encoding='utf-8') as json_file: # Después de salir del bloque del "with" el archivo es cerrado automáticamente + json.dump(data, json_file, ensure_ascii=False, indent=4) + + # UPDATE ACTIVE PRESET SLOT TO NEW ONE + # TODO + return True + + +def load_custom_ui_preset(context): + from .blocks.block_data import UI_Block, blocks + from os.path import isfile, join + + blocks.clear() + + prefs = context.preferences.addons[main_package].preferences + + if prefs.custom_ui_presets == 'NONE': + sculpt_ui_config = "" + else: + sculpt_ui_config = prefs.get_custom_ui_presets(True) + #else: + # sculpt_ui_config = join(prefs.saved_custom_ui_folder, prefs.custom_ui_presets) #prefs.sculpt_custom_ui_filepath + + if sculpt_ui_config == "" or not isfile(sculpt_ui_config): + # LOAD DEFAULT UI CONFIG + from .config.default_config import default_sculpt_config + sculpt_ui_config = default_sculpt_config + + if not isfile(sculpt_ui_config): + print("Default config is not file") + return False + + import json + + data = {} + print("[ATELIER SCULPT] Loading custom Sculpt UI from:", sculpt_ui_config) + + with open(sculpt_ui_config, 'r', encoding='utf-8') as json_file: + if not json_file: + print("Default config no json") + return False + data = json.load(json_file) + if data == {}: + print("Default config none") + return False + + #print(data) + + blocks_str = data.get('blocks', None) + if not blocks_str: + return False + + #print(blocks_str) + + for str in blocks_str: + UI_Block(str) + + return True + +def reset_custom_ui_preset(context): + from .blocks.block_data import UI_Block + from os.path import isfile, join + + UI_Block.clear() + + # LOAD DEFAULT UI CONFIG + from .config.default_config import default_sculpt_config + sculpt_ui_config = default_sculpt_config + + if not isfile(sculpt_ui_config): + return False + + # READ DEFAULT CONFIG FILE + import json + + data = {} + + with open(sculpt_ui_config, 'r', encoding='utf-8') as json_file: + if not json_file: + return False + data = json.load(json_file) + if data == {} or data is None: + return False + + #print(data) + + blocks_str = data.get('blocks', None) + if not blocks_str: + return False + + for str in blocks_str: + UI_Block(str) + + save_path = context.preferences.addons[main_package].preferences.get_custom_ui_presets(True) + if save_path == "" or save_path == 'NONE': + from os.path import join + save_path = join(context.preferences.addons[main_package].preferences.saved_custom_ui_folder, "autosave.json") + + # WRITE DEFAULT CONFIG TO ACTIVE PRESET FILE + with open(save_path, 'w+', encoding='utf-8') as json_file: + json.dump(data, json_file, ensure_ascii=False, indent=4) + + return True + + +def remove_custom_ui(context): + from .blocks.block_data import UI_Block + from os.path import isfile + + UI_Block.clear() + + prefs = context.preferences.addons[main_package].preferences + preset = prefs.get_custom_ui_presets(True) + + if not isfile(preset): + return False + + import os + os.remove(preset) + + return True diff --git a/atelier/custom_ui/ops/__init__.py b/atelier/custom_ui/ops/__init__.py new file mode 100644 index 0000000..0c2d56f --- /dev/null +++ b/atelier/custom_ui/ops/__init__.py @@ -0,0 +1,24 @@ +from .utilities.falloff import BAS_OT_falloff_curve_presets +from .utilities.brush_utils import BAS_OT_brush_remove +from .utilities.activator import BAS_OT_header_activator, BAS_OT_toolHeader_activator +from .edit import BAS_OT_edit_custom_ui +from .ui_blocks import classes as UI_BLOCKS_CLASSES +from .ui_presets import classes as UI_PRESETS_CLASSES + +classes = [ + BAS_OT_falloff_curve_presets, + BAS_OT_brush_remove, + BAS_OT_edit_custom_ui, + BAS_OT_header_activator, + BAS_OT_toolHeader_activator +] + UI_BLOCKS_CLASSES + UI_PRESETS_CLASSES + +def register(): + from bpy.utils import register_class + for cls in classes: + register_class(cls) + +def unregister(): + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) diff --git a/atelier/custom_ui/ops/__pycache__/__init__.cpython-37.pyc b/atelier/custom_ui/ops/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2cbb10105408f5bcb9c1ed536445ff8f215690f3 GIT binary patch literal 1003 zcmZuwOK;RL5Vn)eZZ`Yqwxul+95^9u4=V&G5JK#3Tk)t`Xj&mzK~@^OZG`i{c2-ns zPvy@b@r!ch)W5)qahlyig(FX9=9}0v-%KXGo`+yP`}mV?+6etJ#(X)TyoRl2AQ)m; zpac_)jcy4mv58&jwrC^{agru!R(3ca*e6_}p zWueZf&NA`l4hYAs)i7tVhP#x`5?aInv?38I z#Z?4pRm%bi%%Lf4^%w-hzz#5Vi7@(hPg`5)bN3QopbLBj+-YuCm84XR;a_^ zAPbW?Jq;#;r%>~wovcV%sN*c1yMm{q=T8C^$vD?42pP*#6^u0(F_(T+h+GGYl7lQ) z0i|&YJkU{oUb^#a2athkflInzP7Y|h3_!4zF#Jnp*X GI)4D;N&5u= literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/ops/__pycache__/edit.cpython-37.pyc b/atelier/custom_ui/ops/__pycache__/edit.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..875185e5749eaf4e42a55d44db4c42ebeaba8068 GIT binary patch literal 3957 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 literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/ops/__pycache__/ui_presets.cpython-37.pyc b/atelier/custom_ui/ops/__pycache__/ui_presets.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8c41f3927e27d9346a4cecb3467e42e3dcce4ee GIT binary patch literal 3557 zcmb_fOK;mo5MGjcP%le<#BQA?VVb6m&{ja(OB)1*ZP{*Jq;hJ<7eW9*b2koSKGa>x zjs@kET-)D}99#4k@!C`VLQkDpQj~1Tb%2z@E_Y^!`xw1tqLK3 zqcD2%&{%^a{)CDWs(8f2x9TarYN`shHBa+%X3o!>d6087?-l%_S(NR9r~4(d1nnZ# zy|Q01s|xvn&=M`bBDBoZmzr4vxk9TTSEW1$a*fV`JSXKk$aUHPxgq5S$n$go5C}zVYb2Qp)Fb$lva;3 z!J?Q9>TNVP+S1%O*l}=LeDIxv(uN{Ff+`}fl{Z8oL-HDy@l>Je8w{NG6djf8#JQc$ z_U>TiksR50Y766#vA~csGaBpLJDr{VAGbD}8ZW?babEVs1C%O=q#3`cHJBy3#Idl3EtKsf(xkS;G zIc0Ejwv$__c?aU4TUKEE5Y4!3S$;_S9=5BN^(%xw?I|64mP=8Hb!d9_A@kxnXi=6d z8Fs>#U{>jaiE0sbm}ey@LWfFMa`Gp&?**WhbZ?ybA>cK0&)_;D=%P0D7KcbR9-46qyR`XCn1%WkScUI(gx&DqKM90L$mu(jcDKi+&CrzRzNDCh2K>a+yJpwlv~ zz`I1NkfF6W!!BpQKjZkOkxatu%8#`9Tl5DBRD!&JGqaevlVuKcF(NleGgdwzk zsfx#$tv<$))Ro}-GFmMY*;A1St9InJ{!xrLzqPlwyEj-)7msT-Tw%DuiS4;GF16P? z?X8Cow>C$ZT)D(igX`_E@6q71$Ve=VfSsnkxt-v4HJ#+Hm|#ii*c7@s=_UzVegoXX zKY;yJ2xO>I2lW#iB@)dcATZ!wIWd}hB=PwYfu&g9Io6t3}7D)_} z6K+BWa)m&?DJMj$HdMXBvB!auJ$PKMK@kR2qcojN(hH~c?Q~nT-}78&WLAItd|1|Hnb(4ScvaPe_ZSe65K zz%Hfe1l?btVv3`sR)%664}|!)P=vf`rW_3zX!GZlr6M*QRMwmbbUreF*@T_wXk7j=_Hbt9{qtOF$HWIE#`pbqR%128HEI7A0YD zBZGmonh61UhF?p$FuN{F`*ZL>z)r(MYG=S>3nPR>kB;yHUfV__GJDPc1312f3D=;U z2M21Ki?MbHH_8%Jt>~=B1Xn?w$dV+v1~SJ4jpq;XFN0{wmjyRkTi%tEJSn0XUbi@2 z{^F`{yTRzcgS`E5ygW#1lCb1BJ=~`~TL_qS?5r%IV*NpKLH%9 blocks[n+1].x + blocks[n+1].width / 2 + self.spacing: + #print("SLIDE RIGHT") + self.slide_item(n+1, True) + return {'PASS_THROUGH'} + + elif mouse_inside_item(self.mouse_pos, win_x, win_h, th_w, th_h): + i = 0 + for n in range(0, self.num_blocks): + if mouse_inside_item(self.mouse_pos, win_x + blocks[n].x, 2 + win_h, blocks[n].width, th_h - 6): + self.active_slot_index = i + self.active_item = True + #print("inside item:", n) + if event.type == 'LEFTMOUSE': + if event.value == 'PRESS': + self.moving = True + return {'RUNNING_MODAL'} + elif event.type == 'RIGHTMOUSE': + if event.value == 'RELEASE': + bpy.ops.bas.show_custom_ui_context_menu(index=i) + return {'RUNNING_MODAL'} + elif event.type in {'DEL', 'X'} and event.value == 'RELEASE': + blocks.pop(i) + self.num_blocks -= 1 + self.active_slot_index = -1 + self.active_item = False + return {'RUNNING_MODAL'} + return {'PASS_THROUGH'} + i += 1 + self.active_slot_index = -1 + self.active_item = False + if event.type == 'LEFTMOUSE': + return {'RUNNING_MODAL'} + else: + self.active_slot_index = -1 + self.active_item = False + if self.moving: + self.moving = False + + return {'PASS_THROUGH'} \ No newline at end of file diff --git a/atelier/custom_ui/ops/ui_blocks.py b/atelier/custom_ui/ops/ui_blocks.py new file mode 100644 index 0000000..71ffe4e --- /dev/null +++ b/atelier/custom_ui/ops/ui_blocks.py @@ -0,0 +1,71 @@ +import bpy +from bpy.types import Operator +from bpy.props import StringProperty, IntProperty +from ..blocks.block_data import UI_Block +from ..io import save_actual_ui_state +from ... import __package__ as main_package + + +class BAS_OT_add_item_to_custom_ui(Operator): + bl_idname = "bas.add_item_to_custom_ui" + bl_label = "" + bl_description = "Add item to Custom UI" + block : StringProperty(default='') + def execute(self, context): + if self.block != '': + UI_Block(self.block) + #ui = context.scene.bas_custom_ui + #ui.blocks.append(eval('UI_BLOCK_DRAW.' + self.block)) + #ui.width.append(20) + #ui.pos_x.append(10) + #block = context.scene.bas_custom_ui.ui_blocks.block_slots.add() + #block.draw_function[0] = eval('UI_BLOCK_DRAW.' + self.block) + #block.name = self.block.capitalize() + #block.id = self.block + save_actual_ui_state(context) + else: + self.report({'ERROR'}, "Not valid block!") + return {'CANCELLED'} + return {'FINISHED'} + + +class BAS_OT_remove_item_from_custom_ui(Operator): + bl_idname = "bas.remove_item_from_custom_ui" + bl_label = "" + bl_description = "Remove item from Custom UI" + index : IntProperty(default=-1) + def execute(self, context): + if self.index != -1: + #context.scene.bas_custom_ui.blocks.pop(self.index) + UI_Block.pop(self.index) + else: + self.report({'ERROR'}, "Not valid item index!") + return {'CANCELLED'} + return {'FINISHED'} + + +class BAS_OT_show_custom_ui_context_menu(Operator): + bl_idname = "bas.show_custom_ui_context_menu" + bl_label = "" + bl_description = "Show Custom UI Context Menu" + + index: IntProperty(default=-1) + + def execute(self, context): + block = UI_Block.get_block(self.index) + if not block: + return {'FINISHED'} + block_props = block.ppts + if not block_props or not isinstance(block_props, dict): + return {'FINISHED'} # TODO: report block has no properties. + + UI_Block.set_active_block(self.index) + bpy.ops.wm.call_panel(name="BAS_PT_toolheader_edit_uiblock_context_menu", keep_open=True) + return {'FINISHED'} + + +classes = [ + BAS_OT_add_item_to_custom_ui, + BAS_OT_remove_item_from_custom_ui, + BAS_OT_show_custom_ui_context_menu +] diff --git a/atelier/custom_ui/ops/ui_presets.py b/atelier/custom_ui/ops/ui_presets.py new file mode 100644 index 0000000..e898ef2 --- /dev/null +++ b/atelier/custom_ui/ops/ui_presets.py @@ -0,0 +1,84 @@ +import bpy +from bpy.types import Operator +from bpy.props import StringProperty, IntProperty +from ..blocks.block_data import UI_Block +from ..io import save_actual_ui_state +from ... import __package__ as main_package + + +class BAS_OT_clear_custom_ui(Operator): + bl_idname = "bas.clear_custom_ui_preset" + bl_label = "" + bl_description = "Clear Custom UI" + def execute(self, context): + UI_Block.clear() + self.report({'INFO'}, "Preset has been cleared") + return {'FINISHED'} + +class BAS_OT_remove_custom_ui(Operator): + bl_idname = 'bas.remove_custom_ui_preset' + bl_label = "" + bl_description = "Remove Custom UI" + def execute(self, context): + from ..io import remove_custom_ui + remove_custom_ui(context) + self.report({'INFO'}, "Preset has been removed") + return {'FINISHED'} + +class BAS_OT_create_custom_ui_preset(Operator): + bl_idname = "bas.create_custom_ui_preset" + bl_label = "" + bl_description = "Create Custom UI Preset" + + name : StringProperty(default="My preset", name="Preset Name") + + def execute(self, context): + if self.name == '' or self.name == ' ': + self.report({'ERROR'}, "Preset name is invalid") + return {'CANCELLED'} + from ..io import create_custom_ui_preset + if not create_custom_ui_preset(context, self.name): + self.report({'ERROR'}, "Couldn't create new preset!") + return {'CANCELLED'} + self.report({'INFO'}, "Preset %s has been created" % self.name) + return {'FINISHED'} + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self) + + +class BAS_OT_duplicate_custom_ui_preset(Operator): + bl_idname = "bas.duplicate_custom_ui_preset" + bl_label = "" + bl_description = "Duplicate Custom UI Preset" + + def execute(self, context): + from ..io import duplicate_custom_ui_preset + if not duplicate_custom_ui_preset(context): + self.report({'ERROR'}, "Couldn't duplicate active preset!") + return {'CANCELLED'} + return {'FINISHED'} + + +class BAS_OT_reset_custom_ui_preset(Operator): + bl_idname = "bas.reset_custom_ui_preset" + bl_label = "" + bl_description = "Reset Custom UI Preset" + + def execute(self, context): + from ..io import reset_custom_ui_preset + if not reset_custom_ui_preset(context): + self.report({'ERROR'}, "Couldn't reset active preset!") + return {'CANCELLED'} + self.report({'INFO'}, "Preset has been reset") + return {'FINISHED'} + + +classes = [ + BAS_OT_create_custom_ui_preset, + BAS_OT_duplicate_custom_ui_preset, + BAS_OT_reset_custom_ui_preset, + BAS_OT_clear_custom_ui, + BAS_OT_remove_custom_ui +] diff --git a/atelier/custom_ui/ops/utilities/__pycache__/activator.cpython-37.pyc b/atelier/custom_ui/ops/utilities/__pycache__/activator.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..928b18f18360653c38f32c4ffdf196bf9e6c0150 GIT binary patch literal 1732 zcmd5+&2Aev5GMCuvg9gA8plprt1Zw#7d1#vErK9+T-7NG2d&-9VgX{kWbAeKuOzpK z4e6A;On|;m0bX`vk_=E)7H>=^m z;t5Q(2fzuZ8Odo#Dd85kGb^`4hm!9JcewkSa97Y*cId+1<38;DX7344xF=TV^Wcz7 zdY5n$nNSlvuY`Z?i{k`M@*Mxwl88XB#RTB{*J~j?i|NziYKSnP7Jx&-Vr$MgN`rGr z7As{&b@9IB(ry*|=t1j%xq+eC?}lb}WP;hVgJ*~P-|bEMrgvPOPiviKN}`i805HQk z?SxDsUE~@btW{FH5KA(Q9Dx4;V?~?;j7iG)p52w z=^bY*<+w2cEVKAnWM&;!T(k~>pYjeorHN1nHc;jQU>X%dK%M4mr2nbPHkJ-{6WUbR zEv&HXQt5&5i>-euu$G$tr`qbU5E>#Do7Xx^_7M$(3n z+aE|+hmCR*uiZkpi*OI&6NFC@K1i``IQ<;v`xHaz!6ayViaxJ|lGrpGDoAYlO&}U3 lO4NlefSnA~uDokj=%NW8wbk($`=7!dG@t`-v^Cna=wF$-qsagO literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/ops/utilities/__pycache__/brush_utils.cpython-37.pyc b/atelier/custom_ui/ops/utilities/__pycache__/brush_utils.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d70815387e8af6b25d03775d5780ca26d55a9aeb GIT binary patch literal 1072 zcmZuw&1(}u6rY*>$R=$=QNd424kF|df}RwSCau+aNVRDxEGxq%Gq#(~espHGv?axZ zo&>>vL+~$}t0(;vJo#p~Ed`zMUf%D`yzjTerKJ{v^W@zR`rAe5rv;Y>Fj@stLtqRs zEKq3wiNGld3AVB$oYW27)C;}T4}FYYA?7mgGh!YmpPaD48~Zp67#^dp{|o$}Q5TzF ztKc%$x$HV-ahT`gt;|8APtC$+rmsMItYwnzU*f4XzdQtlRUp*?#!-kF3JJrZ!${~d zC-mwxLZA5%x?ye(2V=UmLnpFS2UPMjKjvr4lUVhxYYaMD*{<$7amG5knMkt3&S0vO zW8N8B)2R(?j|XHsv<9TU0h{9)n&BBaCv!4$&K>Q}yqQ01%pomCzE=G@Cnx<{zmDEN zJa^~r%$YlfMD{@nEU&##!kh!>*-?M*%CGuz^61L;US%6d@sEYrgQ-~Ct~=U+UN>1z z%c>3&Y;2A;$FE#X2^P>YBs* zx`Sr9RGb!Zl4)9-bUouLE=_s|UakZeicQfnBGKmqBik7HMPOO8D&l ze|D#L;b8>36;PVRDW}x5C{1%#3M(&CdQ`^Z;-)zfG+`E)07MZ_xG-&y7_SS0I^|tR zr0UQ$cE{>`V3t$>EWmE{tF+ywY=h1;VMslFT5u)phSs=%&q_%Lu(Ycx?ao$KFdY#q b1&{4Kt}z?U1kdXtUSL2MAs6v2XL;o>0!Suk literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/ops/utilities/__pycache__/falloff.cpython-37.pyc b/atelier/custom_ui/ops/utilities/__pycache__/falloff.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..86bf172faecfe5a30a9b5f95ce90af7a146f747f GIT binary patch literal 1334 zcmaJ>&2Hm15T<@DJGOWGb6KE=U{Rp;B`Dg(0xcFroow8sXzeue0t-U`A&Yb(l@?W! zDv~sN+P${#QJ_yW*Pi+cJ#|RiNe@9u%t-T*8ggd7(cxgwBRIbJ@lRTZg#2ZV-9Vds zgQ7o0#R&5RSrF#4;2l|b-x3zG=pA7ZCzW3XZ-Yh1yp)XMzi~I27M^|JuumW52xa5R zyCoF8q4u2ioerEvABabcpeKJiAH^_ayir_hs@GDQOBn2ICEJ=o|K615eog&3t#Tpc zYIV?D!+S$WE&;ju zAmxG=X8*|EdjHIQ0uiRZ;Si@k%x3eGkq=>=uQ_xWU*)YZh2?@B0m>aOen;cht4QU& z@r{4$zx6zFODg2VPriD}5TRlH+93fV&m0of6NiL|OdJx|&m0mV^4cMt`l&-=L@I|Q z^<#&05V>(k*V=e`uiC5nbik5J%j51;J;GZ}Fp{&APzT5f^4fZ3Lw;6te{iK+VCr*66?k?dZTP>Lx*4SqBZ<|H1s|y z;w3nH4@&G0y_=8!j}Zr3chk`xB$V3PQVKmv*OIl!VhVjqe{OTJUFj|bEt#W0g0CW9 zasfkpV|>#%*iR5Fu@9m~VUNHtH3~yNLxU@G4UZ_z>slJud74sXWB5;yE?gU_vK=!( y(bnpDp_?Ci4j)D86T8PY5k%&?;Tn<|o{$@<+>VD_Lh@o0*4Oqg=K3-TlJ38boo9vs literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/ops/utilities/activator.py b/atelier/custom_ui/ops/utilities/activator.py new file mode 100644 index 0000000..a39c534 --- /dev/null +++ b/atelier/custom_ui/ops/utilities/activator.py @@ -0,0 +1,61 @@ +from bpy.types import Operator +from .... import __package__ as main_package + +# ----------------------------------------------------------------- # +# ACTIVATORS ! # +# ----------------------------------------------------------------- # + +class BAS_OT_toolHeader_activator(Operator): + bl_idname = "bas.toolheader_activator" + bl_label = "" + bl_description = "De/activate Addon's Tool Header" + def execute(self, context): + from bpy.utils import register_class, unregister_class + from ...ui.toolheader import ToolHeader, BAS_HT_toolHeader + prefs = context.preferences.addons[main_package].preferences + if prefs.is_custom_tool_header_active: + unregister_class(BAS_HT_toolHeader) + #unregister_class(NSMUI_PT_dyntopo_stages) + #unregister_class(NSMUI_PT_brush_optionsMenu) + #unregister_class(NSMUI_PT_Support_Dev) + + register_class(ToolHeader) + prefs.is_custom_tool_header_active = False + else: + unregister_class(ToolHeader) + + register_class(BAS_HT_toolHeader) + #register_class(NSMUI_PT_dyntopo_stages) + #register_class(NSMUI_PT_brush_optionsMenu) + #register_class(NSMUI_PT_Support_Dev) + prefs.is_custom_tool_header_active = True + return {'FINISHED'} + +class BAS_OT_header_activator(Operator): + bl_idname = "bas.header_activator" + bl_label = "" + bl_description = "De/activate Addon's Header" + def execute(self, context): + from bpy.utils import register_class, unregister_class + from ...ui.header import Header, BAS_HT_header + prefs = context.preferences.addons[main_package].preferences + if prefs.is_custom_header_active: + unregister_class(BAS_HT_header) + #unregister_class(NSMUI_HT_header_sculpt) + #unregister_class(NSMUI_PT_remeshOptions) + #register_class(VIEW3D_HT_header) + register_class(Header) + prefs.is_custom_header_active = False + else: + unregister_class(Header) + register_class(BAS_HT_header) + #unregister_class(VIEW3D_HT_header) + #register_class(NSMUI_HT_header_sculpt) + #register_class(NSMUI_PT_remeshOptions) + prefs.is_custom_header_active = True + return {'FINISHED'} + +classes = [ + BAS_OT_header_activator, + BAS_OT_toolHeader_activator +] diff --git a/atelier/custom_ui/ops/utilities/brush_utils.py b/atelier/custom_ui/ops/utilities/brush_utils.py new file mode 100644 index 0000000..0ea9664 --- /dev/null +++ b/atelier/custom_ui/ops/utilities/brush_utils.py @@ -0,0 +1,37 @@ +import bpy +from bpy.types import Operator +from bpy.props import BoolProperty, IntProperty, StringProperty + + +class BAS_OT_brush_remove(Operator): + bl_idname = "bas.brush_remove" + bl_label = "" + bl_description = "Remove and Unlink Active Brush" + def execute(self, context): + brush = context.tool_settings.sculpt.brush + st = brush.sculpt_tool + + # TODO: ENSURE TO REMOVE FROM FAV BRUSHES ! + #if brush in favBrushes: + # favBrushes.remove(bpy.data.brushes[brush.name]) + + bpy.data.brushes.remove(brush, do_unlink=True) + brush = None + + # Seleccionar automaticamente una brocha del mismo tipo + for b in bpy.data.brushes: + if b.sculpt_tool == st and b.use_paint_sculpt: + context.tool_settings.sculpt.brush = b + return {'FINISHED'} + + if not context.tool_settings.sculpt.brush: + # Sino hay ninguna del mismo tipo entonces se cogerá la primera en buscar + for b in bpy.data.brushes: + if b.use_paint_sculpt: + context.tool_settings.sculpt.brush = b + return {'FINISHED'} + return {'FINISHED'} + +classes = [ + BAS_OT_brush_remove +] \ No newline at end of file diff --git a/atelier/custom_ui/ops/utilities/falloff.py b/atelier/custom_ui/ops/utilities/falloff.py new file mode 100644 index 0000000..2fb826a --- /dev/null +++ b/atelier/custom_ui/ops/utilities/falloff.py @@ -0,0 +1,35 @@ +import bpy + + +def toggle_off_curves(ui): + ui.depress_smooth = True + ui.depress_round = True + ui.depress_root = True + ui.depress_sharp = True + ui.depress_line = True + ui.depress_max = True + +# TODO: dynamic description +class BAS_OT_falloff_curve_presets(bpy.types.Operator): + bl_idname = "bas.falloff_curve_presets" + bl_label = "" + bl_description = "Select Curve Preset" + shape: bpy.props.StringProperty(name="shape", default='SMOOTH') + def execute(self, context): + ui = context.scene.bas_custom_ui + toggle_off_curves(ui) + if self.shape == 'SMOOTH': + ui.depress_smooth = False + elif self.shape == 'SPHERE': + ui.depress_round = False + elif self.shape == 'ROOT': + ui.depress_root = False + elif self.shape == 'SHARP': + ui.depress_sharp = False + elif self.shape == 'LIN': + ui.depress_line = False + elif self.shape == 'CONSTANT': + ui.depress_max = False + + context.tool_settings.sculpt.brush.curve_preset = self.shape + return {'FINISHED'} diff --git a/atelier/custom_ui/prefs.py b/atelier/custom_ui/prefs.py new file mode 100644 index 0000000..2cc761f --- /dev/null +++ b/atelier/custom_ui/prefs.py @@ -0,0 +1,133 @@ +from os.path import dirname, abspath, join, exists, isdir +from os import listdir + +root = dirname(dirname(abspath(__file__))) +saved_custom_ui_folder_path = join(root, "user_data", "saved_custom_ui") + + +custom_ui_presets_list = [('NONE', "NONE", "")] +def custom_ui_preset_items(self, context): + if not exists(self.saved_custom_ui_folder) or not isdir(self.saved_custom_ui_folder): + return [('NONE', "NONE", "")] + preset_paths = [f[:-5] for f in listdir(self.saved_custom_ui_folder) if f.endswith(".json")] + custom_ui_presets_list.clear() + for path in preset_paths: + custom_ui_presets_list.append((path.replace(' ', '_'), path, "")) + if custom_ui_presets_list: + return custom_ui_presets_list + else: + return [('NONE', "NONE", "")] + +# WRAPPER FOR LOAD CUSTOM UI PRESET METHOD. +def update_custom_ui_preset(self, context): + from ..custom_ui.io import load_custom_ui_preset + load_custom_ui_preset(context) + +''' +custom_ui_properties = { + # UI block specific. + 'brush_selector_grid_size' : ( + 'IntVectorProperty', + { + 'size' : 2, + 'default' : [4, 10], + 'subtype' : 'XYZ', + 'min' : 2, + 'max' : 16, + 'name' : "Grid Size", + 'description' : "Set the number of rows and columns for the brush popover selector" + } + ), + 'texture_selector_grid_size' : ( + 'IntVectorProperty', + { + 'size' : 2, + 'default' : [4, 10], + 'subtype' : 'XYZ', + 'min' : 2, + 'max' : 16, + 'name' : "Grid Size", + 'description' : "Set the number of rows and columns for the texture popover selector" + } + ), + 'image_selector_grid_size' : ( + 'IntVectorProperty', + { + 'size' : 2, + 'default' : [4, 10], + 'subtype' : 'XYZ', + 'min' : 2, + 'max' : 16, + 'name' : "Grid Size", + 'description' : "Set the number of rows and columns for the image popover selector" + } + ), + # General. + 'saved_custom_ui_folder' : ( + 'StringProperty', + { + 'subtype' : 'DIR_PATH', + 'name' : "Saved Custom UI Folder path", + 'description' : "", + 'default' : saved_custom_ui_folder_path + } + ), + 'custom_ui_presets' : ( + 'EnumProperty', + { + 'name' : "Preset list", + 'description' : "", + 'items' : custom_ui_preset_items, + 'update' : update_custom_ui_preset + } + ), + # Other UI Block specific properties. + 'voxel_size_presets' : ( + 'FloatVectorProperty', + { + 'size' : 5, + 'default' : [.1, .05, .01, .005, .001], + 'subtype' : 'NONE', + 'min' : .0001, + 'max' : 1, + 'precision' : 4, + 'step' : .005, + 'unit' : 'LENGTH', + 'name' : "Voxel Size Presets", + 'description' : "Preset Values for Voxel Size" + } + ) +} +''' + + +custom_ui_properties = ''' +brush_selector_grid_size : IntVectorProperty ( + size=2, default=[4, 10], subtype='XYZ', min=2, max=16, + name="Grid Size", description="Set the number of rows and columns for the brush popover selector" +) +texture_selector_grid_size : IntVectorProperty ( + size=2, default=[4, 10], subtype='XYZ', min=2, max=16, + name="Grid Size", description="Set the number of rows and columns for the texture popover selector" +) +image_selector_grid_size : IntVectorProperty ( + size=2, default=[4, 10], subtype='XYZ', min=2, max=16, + name="Grid Size", description="Set the number of rows and columns for the image popover selector" +) + +saved_custom_ui_folder : StringProperty ( + name="Saved Custom UI Folder path", + subtype='DIR_PATH', + default=saved_custom_ui_folder_path +) +custom_ui_presets : EnumProperty ( + items=custom_ui_preset_items, name="Preset list", + update=update_custom_ui_preset +) + +voxel_size_presets : FloatVectorProperty( + name="Voxel Size Presets", description="Preset Values for Voxel Size", + subtype='NONE', default=[.1, .05, .01, .005, .001], min=0.0001, max=1, + size=5, step=.005, precision=4, unit='LENGTH' +) +''' diff --git a/atelier/custom_ui/ui/__init__.py b/atelier/custom_ui/ui/__init__.py new file mode 100644 index 0000000..0a21ebc --- /dev/null +++ b/atelier/custom_ui/ui/__init__.py @@ -0,0 +1,20 @@ +from .add_menu import classes as ADD_MENU_CLASSES +from .context_menu import classes as CONTEXT_MENU_CLASSES +from .toolheader_dropdowns import classes as TOOLHEADER_DROPDOWN_CLASSES +from .header import BAS_HT_header +from .toolheader import BAS_HT_toolHeader + +classes = [ + BAS_HT_header, + BAS_HT_toolHeader +] + ADD_MENU_CLASSES + CONTEXT_MENU_CLASSES + TOOLHEADER_DROPDOWN_CLASSES + +def register(): + from bpy.utils import register_class + for cls in classes: + register_class(cls) + +def unregister(): + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) diff --git a/atelier/custom_ui/ui/__pycache__/__init__.cpython-37.pyc b/atelier/custom_ui/ui/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae38538c8a3c1b144121644e61bdec6f6ccfef59 GIT binary patch literal 873 zcmZuvTW`}a6t)4Fm2H0cNFZ<7Hz}V zp_Y&Oof{0P`nYb#X(R*_0J}riCzA#F%p%G-IQG{LSrq9+J7YMchhl8P9KjTiVWF5B ziuD!3=)5Nl4}Em5@Flv$*BGO-d#6wf( z-?+kOgTtqxh<5k{02xd>g!(u8q8uF_J5aV2Dtktzw{B_zocNywnQsI3iI2M7qo z4q*q|nC}4kzbH>&=Z+{3q#?~~{EsmAx5DryoB(0=s(h1eElq>BVT-HWipdu!ozaav z%F8XaC~;Pc>a^Ovy=NcnE$dy(cfp?ayR`)>kc{P(zAV^tJ&(K{Pfy6`*!BIfUpsM; zOLi`GP|zFAri<~1#hsX-r`luHGlcS@q{Y`<1P>Rp+3{rTdgD3q=CgO+>~y-lwzUf9 rs)K6#_B?NZLu;xj{#B8#O_yc8%nDki?3HR@p(=6USLmnNGpyzxZ*I>u literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/ui/__pycache__/add_menu.cpython-37.pyc b/atelier/custom_ui/ui/__pycache__/add_menu.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef370f812adc550e8863328db131f12faa4f64f6 GIT binary patch literal 4110 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 literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/ui/__pycache__/toolheader.cpython-37.pyc b/atelier/custom_ui/ui/__pycache__/toolheader.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd386c069dfd104f9f2ff782b428b1e7ab4c2f14 GIT binary patch literal 1498 zcmZWp-EJH;6t?GoH@j*6p%sW;RayZNkluh0BI%}0QI&-3hDxJBmUrwVGx7Y?o?#ob zD=vc6*C>}rTq5xbJcDn!$}4ci@l3X;;FZsuV;`UW&hdA=*Y9@`j6c5pjjZ2C=r3a| zh6BpiFx3GFh8Pwo!y(2-w}h41p#!?j9N}hO=z;DsPxx67b}%|eVfP7QJ_~+EEa1+y z9WJrX2o1V75Dg8mUOml|vxKu_n&j0n&AEWZ&F`M>Ki?UWgE6U!LXZni8J9O!d&HZF zBED2Nc55yXQquU6o^wKC6Lm4nz8UHv2#!L`P-rn6+RO?aW`{0wLeHSRutNV8I>Qal zAPin12ZaV@N3ZVfjutQu8WiDTX$;72m%2u8L2BP%Cp$yEG(I|dMh^COhx;cwIN5*l zbTkIdIXxU6#U>Rq0+>r;M<>JkjvnCCtayl z@1pb$*lP?Bz{+~Mv0)-^&fIhhzVE>Kcjw;Ajq2_Yeczk=Gk=Dz0qKk1p93?XcJ`t6 zMvqRP9glBZ$ZK#{9NgNbLVeNPsOz;EA38X ztuFNPG|5>pB^k}($|b~$dCv8E>qTN((n%tcD&b1^-m4xKzkpS|vrkSc*Q|{`}KO#WE=?6}8Dl zyA>A+m!qc4(HrbhokRv)%j@m(O0EK)HJIuX5D0g14?9*PT@MF%+48Vw2hCc5J!=K8 zSb^smkDpNI$F2Xh(zfBipsNEya+*Q=>MkKbl3EzON63qsirbyeM396TLkFM=I^hC} zEr^2V=cKwSxn2fS@^i?ywYy*!rO974*meK5gfSO_5cv^g(TD+-9~%8*BTP%0=TP-P zKsN2q3an=5aM085L)<#qHdo7~A=)jY;v&n6ye0Ko>(in$MA>1|G`Vixip5jXzGU0Y yhrm_Qh9MeBA6(@JAVP1Nu9bAVjj8-9V7ogxyULt&uEG+dJur`+?n^ z&5mYHjaPyT5`3Hy-W%mwQF1W|#D!;1gt8&e5zaJrJw|@8yf7(Il4=rq( z3(Otp@(>6`)Z!?&63ar=rVh7{5p}7@o!m`am~*Mmz1&YahVSqo50lVB-yj;$@CBkF zvwm@sh($9i>C)&3jk>SkUNo|l_mC8fzp`7&y79_sIrn~A7O>vMxFYFOGGiEncx_iS z^2za&CZ850<4ioL>Z~kes;}ELec2u8@-rX|B^E`AO|8VCcH&Ye@u-{l)Jr-(GrjOP{3(LN`1(*6BoeHY;Y}IJCcRFG7RQm{7XdI$)>qcD&bA zJWB!Zk*(aCJ+D=y(Z$aQZ`h*S?l&<|-k}b~VhEUz%{*>Hr2In@s@W~me!mTh{SydC zDHelWojCCk<4nsVOKgE9$@TWUyqLx_kx?wO1sh#co=jQ6)ZjELAjfb{3dnY$`gK`y z39!f`B(Fl&m6#6@k9prGlI) zXIeg29w$@Am0LmDk9x`lS5H(2kTn=PGA)#Snv3iDn5T`GN#6i({Mn&2*?KJ+;FICy z3H>&d1NMmz;0p0EkYAD5hT-qk-7hDPBolJ--Mz_9Rqc_QOb$zu18|dF&I-yze6MT@ zN_4T5 zpvxOTkmbV{Spz$;;Bz9YXZiNf>Dfbj2y)LEz-j=qix2;|)QmN>2T+j$j0=)O`BM?b zc}W|H306Ife{KlBSP7;axA^yglTa}kuFon+Di8&mf!D1ZfxL-HSM%@?W~DeY8!2nS zfZfI^gYG4A6WkLa5FL9t(2V82{v5M*0KpHZ)!A6Y~^) LnD?xnGaUZ|j%yF} literal 0 HcmV?d00001 diff --git a/atelier/custom_ui/ui/add_menu.py b/atelier/custom_ui/ui/add_menu.py new file mode 100644 index 0000000..ea08028 --- /dev/null +++ b/atelier/custom_ui/ui/add_menu.py @@ -0,0 +1,87 @@ +from bpy.types import Menu +from ..blocks.block_id_cats import * + + +def draw_items(self, items): + layout = self.layout + for item in items: + if item[0] == '': + layout.separator() + else: + layout.operator("bas.add_item_to_custom_ui", text=item[1]).block = item[0] + + +class DefaultMenu(Menu): + bl_idname = "BAS_MT_default" + bl_label = "DefaultMenu" + def draw(self, context): + draw_items(self, default) + +class BrushMenu(Menu): + bl_idname = "BAS_MT_brush" + bl_label = "BrushMenu" + def draw(self, context): + draw_items(self, brush) +class TextureMenu(Menu): + bl_idname = "BAS_MT_texture" + bl_label = "TextureMenu" + def draw(self, context): + draw_items(self, texture) +class SlidersMenu(Menu): + bl_idname = "BAS_MT_sliders" + bl_label = "SlidersMenu" + def draw(self, context): + draw_items(self, sliders) +class FalloffMenu(Menu): + bl_idname = "BAS_MT_falloff" + bl_label = "FalloffMenu" + def draw(self, context): + draw_items(self, falloff) +class MaskMenu(Menu): + bl_idname = "BAS_MT_mask" + bl_label = "MaskMenu" + def draw(self, context): + draw_items(self, mask) +class StrokeMenu(Menu): + bl_idname = "BAS_MT_stroke" + bl_label = "StrokeMenu" + def draw(self, context): + draw_items(self, stroke) +class OthersMenu(Menu): + bl_idname = "BAS_MT_others" + bl_label = "OthersMenu" + def draw(self, context): + draw_items(self, others) +class UtilsMenu(Menu): + bl_idname = "BAS_MT_utils" + bl_label = "UtilsMenu" + def draw(self, context): + draw_items(self, utils) +class SpacingMenu(Menu): + bl_idname = "BAS_MT_spacing" + bl_label = "SpacingMenu" + def draw(self, context): + draw_items(self, spacing) + +submenus = [ DefaultMenu, + BrushMenu, TextureMenu, SlidersMenu, FalloffMenu, + MaskMenu, StrokeMenu, OthersMenu, UtilsMenu, SpacingMenu +] + +class BAS_MT_custom_ui_items(Menu): + bl_idname = "BAS_MT_custom_ui_items" + bl_label = "Items" + + def draw(self, context): + layout = self.layout + + for cat in categories: # UI_BLOCK_DRAW_STR.ALL.value (deprecated) + if cat == 'NONE': + layout.separator() + else: + layout.menu("BAS_MT_"+cat.lower(), text=cat) + + +classes = [ + BAS_MT_custom_ui_items +] + submenus diff --git a/atelier/custom_ui/ui/context_menu.py b/atelier/custom_ui/ui/context_menu.py new file mode 100644 index 0000000..9bc93d3 --- /dev/null +++ b/atelier/custom_ui/ui/context_menu.py @@ -0,0 +1,77 @@ +from bpy.types import Menu, Panel +from ... import __package__ as main_package + + +class WM_MT_button_context(Menu): + bl_label = "Unused" + + def draw(self, context): + pass + + +class BAS_MT_toolheader_ctx_ui_presets(Menu): + bl_idname = "BAS_MT_toolheader_ctx_ui_presets" + bl_label = "Tool Header Preset Operators" + + def draw(self, context): + layout = self.layout + prefs = context.preferences.addons[main_package].preferences + if prefs.custom_ui_presets != 'NONE': + layout.prop(prefs, 'custom_ui_presets', text="") + layout.separator() + layout.operator('bas.create_custom_ui_preset', text="Create New Preset") + layout.operator('bas.duplicate_custom_ui_preset', text="Duplicate Active Preset") + layout.operator('bas.reset_custom_ui_preset', text="Reset Active Preset") + layout.operator('bas.clear_custom_ui_preset', text="Clear Active Preset") + layout.operator('bas.remove_custom_ui_preset', text="Remove Active Preset") + + +def menu_ctx_tool_header(self, context): + layout = self.layout + layout.separator() + layout.operator('bas.edit_custom_ui', text="Edit Tool Header") + layout.menu('BAS_MT_toolheader_ctx_ui_presets', text="Tool Header Presets") + #layout.separator() + + +class BAS_PT_toolheader_edit_uiblock_context_menu(Panel): + bl_idname = "BAS_PT_toolheader_edit_uiblock_context_menu" + bl_label = "UI Block Context Menu" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = "NONE" + bl_category = 'Sculpt' + + def draw(self, context): + from .ui_block_data import UI_Block + block = UI_Block.get_active_block() + if not block: + return + block_props = block.ppts + + if not block_props or not isinstance(block_props, dict): + return + + layout = self.layout + prefs = context.preferences.addons[main_package].preferences + props = context.scene.bas_custom_ui + layout.label(text="'" + block.name + "' Properties:") + + for key, value in block_props.items(): + if key.startswith('_'): + data = prefs + key = key[1:] + else: + data = props + draw_prop(layout, data, key, value) + + +def draw_prop(layout, data, prop, tipo): + #if tipo is int: + layout.prop(data, prop) + +classes = [ + WM_MT_button_context, + BAS_MT_toolheader_ctx_ui_presets, + BAS_PT_toolheader_edit_uiblock_context_menu +] diff --git a/atelier/custom_ui/ui/header.py b/atelier/custom_ui/ui/header.py new file mode 100644 index 0000000..96dd1b9 --- /dev/null +++ b/atelier/custom_ui/ui/header.py @@ -0,0 +1,192 @@ +from ..blocks.header import BAS_HT_header_blocks +from bl_ui.space_view3d import VIEW3D_HT_header as Header +from ... icons import Icon +from bpy.types import Object + + +# --------------------------------------------- # +# HEADER UI +# --------------------------------------------- # +class BAS_HT_header(Header): # Header -> VIEW3D_HT_header + bl_idname = "BAS_HT_header" + bl_label = "" + bl_space_type = "VIEW_3D" + bl_region_type = "HEADER" + #bl_context = ".paint_common" + + transformTools = { + "builtin.move", + "builtin.rotate", + "builtin.scale", + "builtin.transform", + "builtin.box_hide" # exclude for mask tools + } + + def draw(self, context): + if context.mode == "SCULPT": + layout = self.layout + props_ui = context.scene.bas_custom_ui + header = BAS_HT_header_blocks + + obj = context.active_object + object_mode = 'OBJECT' if obj is None else obj.mode + act_mode_item = Object.bl_rna.properties["mode"].enum_items[object_mode] + #act_mode_i18n_context = bpy.types.Object.bl_rna.properties["mode"].translation_context + sub = layout.row(align=True) + sub.ui_units_x = 2 + sub.operator_menu_enum( + "object.mode_set", "mode", + text="", # bpy.app.translations.pgettext_iface(act_mode_item.name, act_mode_i18n_context), + icon=act_mode_item.icon, + ) + + brush = context.tool_settings.sculpt.brush + + if (context.workspace.tools.from_space_view3d_mode("SCULPT", create=False).idname in self.transformTools): + self.is_brush = False + else: + self.is_brush = True + if brush.sculpt_tool == 'MASK' or brush.sculpt_tool == 'BOX_MASK': # context.workspace.tools.from_space_view3d_mode("SCULPT", create=False).idname == "builtin.box_mask": + row = layout.row(align=True) + row.operator("bas.mask_by_cavity", text='Cavity', icon_value=Icon.MASK_CAVITY()) + row.popover(panel="BAS_PT_Mask_By_Cavity", text="") + + row = layout.row(align=True) + props = row.operator("sculpt.mask_filter", text='Smooth', icon_value=Icon.MASK_SMOOTH()) + props.filter_type = 'SMOOTH' + props.auto_iteration_count = True + + props = row.operator("sculpt.mask_filter", text='Sharp', icon_value=Icon.MASK_SHARP()) # Sharper + props.filter_type = 'SHARPEN' + props.auto_iteration_count = True + + row = layout.row(align=True) + row.label(text="Expand") + props = row.operator("sculpt.mask_filter", text='', icon='ADD') # Grow + props.filter_type = 'GROW' + props.auto_iteration_count = True + + props = row.operator("sculpt.mask_filter", text='', icon='REMOVE') # Shrink + props.filter_type = 'SHRINK' + props.auto_iteration_count = True + + row = layout.row(align=True) + row.label(text="Contrast") + props = row.operator("sculpt.mask_filter", text='', icon='ADD') # Contrast + props.filter_type = 'CONTRAST_INCREASE' + props.auto_iteration_count = False + + props = row.operator("sculpt.mask_filter", text='', icon='REMOVE') # Decrease Contrast + props.filter_type = 'CONTRAST_DECREASE' + props.auto_iteration_count = False + + layout.separator() + + layout.separator_spacer() + + # REMESHERS + #if props_ui.show_mesh_tools: + col = layout.column() + row = col.row(align=True) + row.ui_units_x = 6.8 + #row.label(text="Remesher :") + remesher = context.window_manager.bas_remesh + row.popover( + panel="BAS_PT_remesh_options", + icon='MODIFIER_ON', # EXPERIMENTAL + text="" + ) + row.prop(remesher, 'remesher', text="", toggle=True, expand=False) + + # QUADRIFLOW + if remesher.remesher == 'QUADRIFLOW': + remesh = row.operator('object.quadriflow_remesh', icon='PLAY', text="") + + # DECIMATION + elif remesher.remesher == 'DECIMATE': + remesh = row.operator('bas.decimate_remesh', icon='PLAY', text="") + remesh.ratio = remesher.decimate_ratio + remesh.triangulate = remesher.decimate_triangulate + remesh.symmetry = remesher.decimate_symmetry + remesh.symmetry_axis = remesher.decimate_symmetry_axis + + # DYNTOPO'S FLOOD FILL + elif remesher.remesher == 'DYNTOPO': + remesh = row.operator('bas.dyntopo_remesh', icon='PLAY', text="") + remesh.resolution = remesher.dyntopo_resolution + remesh.force_symmetry = remesher.dyntopo_symmetry + remesh.symmetry_axis = remesher.dyntopo_symmetry_axis + remesh.only_masked = remesher.dyntopo_only_masked + + # OPEN VDB - VOXEL + elif remesher.remesher == 'VOXEL': + row.ui_units_x = 5.4 + if remesher.voxel_join_object != None: + row.operator('bas.voxel_remesh_join', icon='PLAY', text="") + elif remesher.voxel_reprojection != 'NONE': + row.operator('bas.voxel_remesh_reproject', icon='PLAY', text="") + elif context.sculpt_object.use_dynamic_topology_sculpting: + row.operator('bas.voxel_remesh', icon='PLAY', text="") + else: + row.operator('object.voxel_remesh', icon='PLAY', text="") + + # SEPARATOR + row = layout.row() + row.label(text="", icon_value=Icon.SEPARATOR()) + + # MESH TOOLS + if props_ui.show_mesh_tools: + text = "" + else: + text = "Tools" + row = layout.row() + row.prop(props_ui, 'show_mesh_tools', icon='COLLAPSEMENU', text=text) + if props_ui.show_mesh_tools: + # QUICK MASK EXTRACTOR + row = layout.row(align=True) + row.ui_units_x = 6.2 # 5.8 + extractor = context.window_manager.bas_extractor + if not extractor.is_created: + if extractor.mode == 'BLENDER': + mesh = context.active_object.data + _props = row.operator("mesh.paint_mask_extract", text="Mask Extract", icon='CLIPUV_HLT') + else: + _props = row.operator("bas.mask_extractor_quick", text="Mask Extractor", icon='CLIPUV_HLT') + _props.thickness = extractor.thickness + _props.offset = extractor.offset + _props.smoothPasses = extractor.smooth_passes + _props.mode = extractor.mode + _props.superSmooth = extractor.super_smooth + _props.keepMask = extractor.keep_mask + _props.editNewMesh = extractor.edit_new_mesh + _props.postEdition = extractor.post_edition + row.popover(panel="BAS_PT_Mask_Extractor_Options", text="") + else: + row.popover(panel="BAS_PT_Mask_Extractor_Options", text="Mask Extractor", icon='MODIFIER_ON') + #row.operator("bas.mask_extractor_quick", text="Mask Extractor", icon='CLIPUV_HLT') + + # DETACH FROM MASK, MESH DETACHER + detacher = context.window_manager.bas_detacher + row = layout.row(align=True) + row.ui_units_x = 6.3 + _props = row.operator("bas.mask_detacher", text="Mesh Detacher", icon='LIBRARY_DATA_BROKEN') # OUTLINER_OB_META + _props.detachInDifferentObjects = detacher.detach_multi_objects + _props.separateLooseParts = detacher.separate_loose_parts + _props.sculptMaskedMesh = detacher.go_sculpt_masked_mesh + _props.closeDetachedMeshes = detacher.close_detached_meshes + _props.closeOnlyMasked = detacher.close_only_masked + _props.doRemesh = detacher.do_remesh + row.popover(panel="BAS_PT_Mesh_Detacher_Options", text="") + + # CLOSE GAPS + header.draw_close_gaps(self, context.window_manager.bas_closegaps) + + layout.separator_spacer() + # QUICK ACCESS TO MODIFIERS + # layout.column().popover(panel="NSMUI_PT_quick_modifiers", text="", icon='MODIFIER_ON') + # row = layout.row() + #row.label(text="Shading") + header.draw_shading(self, context) + + else: + Header.draw(self, context) diff --git a/atelier/custom_ui/ui/header_append.py b/atelier/custom_ui/ui/header_append.py new file mode 100644 index 0000000..ccf86f9 --- /dev/null +++ b/atelier/custom_ui/ui/header_append.py @@ -0,0 +1,4 @@ +def temporal_tool_header_buttons(self, context): + self.layout.menu('BAS_MT_toolheader_ctx_ui_presets', text="Presets") + self.layout.menu("BAS_MT_custom_ui_items", text="", icon='ADD') + self.layout.operator('bas.edit_custom_ui', text="", icon='GREASEPENCIL') diff --git a/atelier/custom_ui/ui/toolheader.py b/atelier/custom_ui/ui/toolheader.py new file mode 100644 index 0000000..2339e86 --- /dev/null +++ b/atelier/custom_ui/ui/toolheader.py @@ -0,0 +1,200 @@ +from bl_ui.properties_paint_common import UnifiedPaintPanel +#from .icons import preview_collections +from bl_ui.space_view3d import VIEW3D_HT_tool_header as ToolHeader +from ..blocks.block_data import blocks +from ... import __package__ as main_package + +widths = [] + + +class BAS_HT_toolHeader(ToolHeader): # , UnifiedPaintPanel): + bl_idname = "BAS_HT_ToolHeader" + bl_label = "Toolheader" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOL_HEADER" + #bl_context = ".paint_common" + bl_options = {'REGISTER', 'UNDO'} + + def draw(self, context): + if (context.mode != "SCULPT"): + super().draw(context) + return + # Not a brush but a tool. + elif not UnifiedPaintPanel.paint_settings(context): + super().draw(context) + return + + # self.layout.template_header() # to change region + + # VARIABLES + toolsettings = context.tool_settings + self.sculpt = toolsettings.sculpt + self.brush = brush = self.sculpt.brush + + wm = context.window_manager + scn = context.scene + + # Not a brush but a tool. + if brush is None: + return + + self.capabilities = self.brush.sculpt_capabilities + self.ups = toolsettings.unified_paint_settings + self.context = context + + self.prefs = context.preferences.addons[main_package].preferences + self.sep = 5 * context.preferences.view.ui_scale + + self.props = scn.bas_custom_ui + #blocks = self.props.blocks + #width = self.props.width + #pos_x = self.props.pos_x + #slots = self.props.ui_blocks.block_slots + + layout = self.layout + + for block in blocks: + block(self) + + ''' + scale = context.preferences.view.ui_scale + bl_ui_unit = 20 * scale + sep = 5 * scale + x = 3 * scale + w = 0 + x0 = x + sep + + num_seps = 0 + + groups = [] + group = [] + group_width = 0 + total_width = 0 + + index_that_are_separator_spacer = [] + + for i, block in enumerate(blocks): + w = block(self) + if w == -1: + block.width = w + elif w == 0: + #group_width -= sep + #print(group_width) + #print([item.id for item in group]) + groups.append([group, group_width]) + total_width += group_width + num_seps += 1 + group.clear() + group_width = 0 + index_that_are_separator_spacer.append(i) + #group.append(block) + else: + w *= bl_ui_unit + group.append(block) + group_width += w + sep + block.width = w + + groups.append([group, group_width]) + total_width += group_width + group.clear() + reg_width = context.region.width + + off_x = context.region.view2d.region_to_view(0, 0)[0] + + #print(reg_width - total_width) + so_tiny = reg_width - (total_width + bl_ui_unit * 1.5) < sep + 1 # bef_width + bl_ui_unit + scale + sep >= aft_start_x: + + if off_x != 0: + #print("SCROLLING!") + # To optimize (not process all the thing) we know if scrolling is ON, this is gone... + sep_ext = sep * 1.2 + for ss_index in index_that_are_separator_spacer: + blocks[ss_index].width = sep_ext + + off_x *= 2 + view2d = context.region.view2d + for i, block in enumerate(blocks): + if block.width != -1: + x += sep + #block.width = w = block(self) * bl_ui_unit + block.abs_x = x + block.x = view2d.region_to_view(x, 0)[0] - off_x + #print("Item: %i - X: %i - Real X: %i" % (i, x, block.x)) + x += block.width # w + elif so_tiny: + #print("SO TINY!") + # THIS FIX SOME BUG(s) WHEN VALUES ARE TINY OR BECOME NEGATIVE + sep_ext = sep * 1.2 + for ss_index in index_that_are_separator_spacer: + blocks[ss_index].width = sep_ext + for block in blocks: + if block.width != -1: + x += sep + block.x = block.abs_x = x + x += block.width + else: + num_groups = len(groups) - 1 # -1 only for index separator way + width_count = 0 + + for g_index, ss_index in enumerate(index_that_are_separator_spacer): + bef_width = groups[g_index][1] + width_count += bef_width + #print("GINDEX:", g_index, " SSINDEX:", ss_index, " NUM_GROUPS:", num_groups, " NUM_SEPS:", num_seps, " TOTAL_WIDTH:", total_width) + # THERE'S NOT AFTER GROUP + if g_index > num_groups or num_seps == 1: + #print("NO AFTER GROUP!") + blocks[ss_index].width = reg_width - total_width - sep + break + else: + if num_seps == 2: + aft_width = groups[g_index + 1][1] + #print("num seps == 2") + # GROUPS 1-2 + if g_index == 0: + #print("== 0 Group Index") + aft_mid_x = reg_width / num_seps # Take mid # + aft_start_x = aft_mid_x - aft_width / 2 # Offset mid to get start point using width + blocks[ss_index].width = aft_start_x - bef_width - bl_ui_unit - scale # Diff start with prev group width and standard unit and scale + # GROUPS 2-3 + elif g_index == 1: + #print("== 1 Group Index") + sep_start_x = width_count + sep_final_x = reg_width - aft_width + blocks[ss_index].width = sep_final_x - sep_start_x - bl_ui_unit * 1.5 + x0 * 1.1 + else: + #print(g_index) + next_group_width = groups[g_index + 1][1] + if g_index == 0: + #print("FIRST ONE") + aft_mid_x = reg_width / num_seps # Take mid of next group + aft_start_x = aft_mid_x - next_group_width / 2 # Offset mid to get start point using width + sep_width = aft_start_x - bef_width - bl_ui_unit - sep * .33 + elif (g_index + 1) == num_groups: + #print("LAST ONE") + sep_width = (reg_width - next_group_width) - width_count - bl_ui_unit - sep * 1.2 # END POINT - START POINT + else: + #print("IN THE MIDDLE") + aft_mid_x = (reg_width / num_seps) * (g_index + 1) # Take mid # + aft_start_x = aft_mid_x - next_group_width / 2 # Offset mid to get start point using width + sep_width = aft_start_x - width_count - bl_ui_unit - sep * 1.4 # Diff start with prev group width and standard unit and scale + + sep_width = sep if sep_width < sep + 1 else sep_width + blocks[ss_index].width = sep_width + #print(sep_width) + + width_count += blocks[ss_index].width + total_width += blocks[ss_index].width + + for i, block in enumerate(blocks): + if block.width != -1: + x += sep + block.abs_x = block.x = x + x += block.width # w + ''' + + # layout.separator_spacer() + + #layout.popover('BAS_PT_custom_ui_uilist', text="Slots") + + #layout.menu("BAS_MT_custom_ui_items", text="", icon='ADD') + #layout.operator('bas.edit_custom_ui', text="", icon='GREASEPENCIL') diff --git a/atelier/custom_ui/ui/toolheader_dropdowns.py b/atelier/custom_ui/ui/toolheader_dropdowns.py new file mode 100644 index 0000000..01c479f --- /dev/null +++ b/atelier/custom_ui/ui/toolheader_dropdowns.py @@ -0,0 +1,55 @@ +from bpy.types import Panel +from ..blocks.block_id_cats import * +from ...icons import Icon +from ... import __package__ as main_package +from ...tools.brush_thumbnailer.ui import BrushThumbnailerOptions + + +class BAS_PT_brush_options_dropdown(Panel): + bl_label = "Brush Options" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = "NONE" + bl_category = 'Sculpt' + bl_description = "Dropdown Panel for Brush Options! You can create/remove/reset/create custom icon (all based in active brush)" + + # BRUSH OPTIONS + def draw(self, context): + scn = context.scene + wm = context.window_manager + brush = context.tool_settings.sculpt.brush + prefs = context.preferences.addons[main_package].preferences + + icon_brushAdd = Icon.BRUSH_ADD() + icon_brushReset = Icon.BRUSH_RESET() + icon_brushRemove = Icon.BRUSH_REMOVE() + + # 1ST ROW + col = self.layout.column() + row = col.row(align=True) + row.scale_y = 1.5 + # NEW BRUSH BUTTON (DUPLICATE) + row.operator("brush.add", text="New / Duplicate", icon_value=icon_brushAdd) + + # 2ND ROW + row = col.row(align=True) + # RESET BRUSH BUTTON + row.operator("brush.reset", text="Reset", icon_value=icon_brushReset) # RESET BRUSH + # DELETE BRUSH BUTTON + row.operator("bas.brush_remove", text="Remove", icon_value=icon_brushRemove) + col.separator() + + # 3RD-4TH ROW + BrushThumbnailerOptions.draw(self, context) + + # 5th Row + self.layout.separator(factor=.3) + col = self.layout.column() + box = col.box() + box.label(text="Brush Selector") + box.row().prop(prefs, 'brush_selector_grid_size') + + +classes = [ + BAS_PT_brush_options_dropdown +] diff --git a/atelier/experimental/__init__.py b/atelier/experimental/__init__.py new file mode 100644 index 0000000..8449d8e --- /dev/null +++ b/atelier/experimental/__init__.py @@ -0,0 +1,17 @@ +def register(): + from bpy.utils import register_class as REGISTER + from . panel import BAS_PT_experimental_panel as Experimental_Base_Panel + from . non_destructive import register as REGISTER_NONDESTRUCTIVE + from . mesh_filters import register as REGISTER_MESHFILTERS + REGISTER(Experimental_Base_Panel) + REGISTER_NONDESTRUCTIVE() + #REGISTER_MESHFILTERS() + +def unregister(): + from bpy.utils import unregister_class as UNREGISTER + from . panel import BAS_PT_experimental_panel as Experimental_Base_Panel + from . non_destructive import unregister as UNREGISTER_NONDESTRUCTIVE + from . mesh_filters import unregister as UNREGISTER_MESHFILTERS + UNREGISTER_NONDESTRUCTIVE() + #UNREGISTER_MESHFILTERS() + UNREGISTER(Experimental_Base_Panel) diff --git a/atelier/experimental/__pycache__/__init__.cpython-37.pyc b/atelier/experimental/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56bf892c00482250e115ec4f66ebcc8d82bb9b28 GIT binary patch literal 884 zcmb_bO>fgM7`BtNO-s9Rm^SUkHJ1v(34{=BmqHPg(X<>YOIE1OX36=$P7ssY)A%z; z{Gwbr?JscRwcD_&XB_#-oA>qmk>^QYd55qG&au8Zi4M%v_@-u z3-PscYcdd2Z%*%^5DIX0r1+K41y^(>W3BJ-)_p!4MD%h_`S+YFk?^#LCC%fMOUUu- zoS;>;Kj%07vJg@$Gi24Uhm;w*I!?2cGOi0%t_tyutA_D*6Rxl67a^gk4!p{rg|8>k zJe*Z$;eNMatU0|jEthBY5jy=ay$GZEY%!WoK84GZe}VU5^kzJH2W6t4NOhsnpRc3m zWTBx6c{?V9JimyGn9Q;`5$P2fN}e*V9*wgyWpN?0w04mv{b!GfUMZ0mn#7D{sV0Mh zOTksNDrH`feO3siA{7Ot{rtwhpkYv(0TAx)E+5;-f0WcpSqr<9RPD5TSf;f=TTzt@ z!vxrh0`hh4<3UR`A*PNDv!sDIq@`*hzrS393Fs0|clf8I36= zF9>+xZwdH<-ukfz!Ho19KXDi7Q`?yGT*%a+o%raSpz0$ug2WUEo}#f2UL1fQHz0_c zq6H0TZpkf;Luf&`C2kq$Epj7n%r~OCc*C!J{4|+sp>*={JQw_7 zQ1W>2q@scB0xjeR72!e^sZ9@|4M6eFj%=Nh{x7B5I>CX3jO9ENjM)xj*uS-O ze#F?PHJ5)@!i8j#FK~uk^mNHn!OW%b_icMoZU!!=R<4~@e2@O$t8DnJCh^?0u|K>e2^Y9> zej` zEOHs`Rn5zM(Jc$5twD7vuT&;LClGNF8&qM=8d+0;Vy?Clxr<@pKR%|=a> z#<@yW!Fe}dRN+>1RNEc`5W2tng*2q*2+nrHfeN8qZaCh6^S{Hf^go8XVb~0Z8-&Kd zY&w2errWC4qhN3xBkI}%23rFLI}$Zb6O4I)faC8tW)D52nSBIgngf6+s9Mv(wlxNy zYwjX+5U?R@xDQ^{x+w$mSxj&vPJq%pgp)z!If-0wD~HSG5`{04Oq5!_bQ+_2Ve20o C?%iDg literal 0 HcmV?d00001 diff --git a/atelier/experimental/mesh_filters/__pycache__/data.cpython-37.pyc b/atelier/experimental/mesh_filters/__pycache__/data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f6568f4574fdd8fab7477dfc791d11742ce1b41 GIT binary patch literal 1069 zcmZ8gOK;Oa5cV@p?4%8)T#x{B42K@zf=~rbAE}CHg-Aijva~YUX=`ciHM@?~gd>$W z^}x^I!VlUT;*`I@fik;pc&)Xw-_Fj?&U`c8?)5?o2YmWWj$O<8t~bl!A=ttxk5N#I z+RQ5K#4en~u`TLQm$`+Pc!i(%X79095hMX(KWWoe(xE{T;u_I*(%rXchlUpx4I%jG zCOsOBtwDETqw9gA)+N-W0C{$jvl;}ElZ=77^}n{@ygpmt6@6!In{)OCCN&o?1+PHV zGu0b0p4H3kz*fB_-WPmYEq-2IT`e5EfBIql>;AX-#WQr&lE$y{&8rjbemOb_ws6W6 z1z3qqt;Bh4flF;90KZe0y40ior3W5uZCc<%3j!KIo3?2OFy+etPojTLy0i!3^~uVn zMOTrEzFIrP{6|gxW0q48a)BkWM)PPFPPi!I;dw6S4~A96W^sK6F*WL}&UqQ z$OzClL)(F;Jekiy1!$Q~S*^S>D?mjQYix*^N6i}Rf(!!Hmiz>( zlb1`Bor_g%->C#la*19jwF zWg2%0c|Xlqb0U20W0jDsEO~8`l!S!geu~aihnqHPEy2>`Hndo8wD>$WQC$w)nq20%6^hbjg z3Jh)wLO^RAk0RM^?lP|;*=l=Z9vDFXB&?r3org^I?uL(^2NJ fKYTRkzP4^}Hg$Nae@Ut9*|+<4*YRxgxt{wATj()E literal 0 HcmV?d00001 diff --git a/atelier/experimental/mesh_filters/__pycache__/ops.cpython-37.pyc b/atelier/experimental/mesh_filters/__pycache__/ops.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17401089764f4fd297909b87cb9523b5547ede44 GIT binary patch literal 1318 zcmb_cOK%)S5bnpk?2Vm}m|L(6f=F|T5Xm7zh{TUr7$s}O0-LNR8cn8qUzwd5SNA$= zX?+2%9Juob_y_qVIrIrBm*g+xMAeLq9G|IGS6A0o)qed|_3o^#tumCizy6uN@)&zV zhjS5VJiukXM5UPG1sgCWl>D6yxbg-<`6@tLs!&C^dum0k;_fTnV{zvlX0dKgngpw} z2-hwy^BmP8v24nxVk-C8v+gNdvZ;5<7duNn_0e}6vO-jUGq{S|`;*rRWvTb!S(a_81MQszks5MMxy0lr5%(NM&!%1Q3 zm#W&_#!GcZ+NNh0lZeG!L&f+t9*8S^mD9S;;Uo0L((xybKXrVNQzQxPE4Z2t-a_NN zf7A!XX)?xAc{mj~7b^TC*4!tTLh?CaVVfRn zz_z*#Oo3XLsc9vf`)I>cL9-A5g!6*z~qlM5dw!(Rvj83*qTV4gdfE literal 0 HcmV?d00001 diff --git a/atelier/experimental/mesh_filters/__pycache__/ui.cpython-37.pyc b/atelier/experimental/mesh_filters/__pycache__/ui.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0d8692839e8bf71dc8cfccf76645fcb09c303dc GIT binary patch literal 1189 zcmZuxy>AmS6u0kuA4$^yMJhrpRktv700x8*&<~10rK)HP(&>=poZWE2_fh+zCRI8W ziHU&`COYyjd1dOz%Ea?si-O>t{PNz}e*V4R{hYP=`8EPO`uLeHx(NNS$)ynpp1{!e z!EwZ~L>Z1SM%>}9bTT(`Atv0DB=aHUh`8?c3z1BJGMm=o29hM7u zj+^XyCiFXckjhFZs%6TB(&tVycXMrZm+tIQF)oElGm%$J($eluY^_TJybZp45C{}u zjv|NS$mLE%xEpz#00u7#MA+au#Zilgy!8P^ZQcfKb7tYqMnxrDMi8qajC<59X&P7EmvDd%k~ zb$`zBjei+hv1|VHZ|MqP)ctQkFhxUTd*{w4d;(ILI@AF++#xw}$G0nQ1HD~7Ayem@ ze}+!HsW%L!?vzZ)x9|)^#>ppw(c$tNkQ{L5ZSPs_FqsbW!PhU=em(};h^5Q;jdFS| z<4ZOvM(`#n2uMH0^|y zDF`n;7Cqk(E#!f^V%Lz2rM7;fS>rc#)Ge5+KVIXm)l0czM>f(Oa0qv?k3*-8L;PP_ z?dok1Wk_kxGC`?nQwlN}K_k?3C_Nl8c`?)J+bf~qZqSsQ5HiX7LYjGqbjcDyt4S$L z7g8k#pphn@gFIoC7!+z^<`T)Y&P4UD;A#N?s%7h}0%x4Xuj~i*VCXKm5R=Qt?T~tI yyBC;NznrYtQ?;_%F$BcP8eYBn_SQG6UOrR4KCNaeeAx8*$o@rWE2|EM$N3E|RYtA= literal 0 HcmV?d00001 diff --git a/atelier/experimental/mesh_filters/data.py b/atelier/experimental/mesh_filters/data.py new file mode 100644 index 0000000..6fcfb9e --- /dev/null +++ b/atelier/experimental/mesh_filters/data.py @@ -0,0 +1,22 @@ +from . ops import set_meshfilter_scale, set_meshfilter_smooth +from bpy.props import BoolVectorProperty, FloatProperty +from bpy.types import PropertyGroup + +MIN_SLIDER = -2 +MAX_SLIDER = 2 +STEP_SLIDER = 1/100 +ACC_SLIDER = 2 + +mesh_filter_sliders = ( + 'smooth', 'scale' +) + +class MeshFilter_Sliders(PropertyGroup): + axis : BoolVectorProperty(size=3, default=[False, False, False], name="Deform Axis", description="Apply the deformation in the selected axis") + + smooth : FloatProperty(soft_min =MIN_SLIDER, soft_max=MAX_SLIDER, default=0, precision=ACC_SLIDER, + name="Smooth", description="Smooth mesh", set=set_meshfilter_smooth + ) + scale : FloatProperty(soft_min =MIN_SLIDER, soft_max=MAX_SLIDER, default=0, precision=ACC_SLIDER, + name="Scale", description="Scale mesh", set=set_meshfilter_scale + ) diff --git a/atelier/experimental/mesh_filters/ops.py b/atelier/experimental/mesh_filters/ops.py new file mode 100644 index 0000000..413b547 --- /dev/null +++ b/atelier/experimental/mesh_filters/ops.py @@ -0,0 +1,40 @@ +import bpy + +def get_context(): + for window in bpy.context.window_manager.windows: + screen = window.screen + + for area in screen.areas: + if area.type == 'VIEW_3D': + for region in area.regions: + if region.type == 'WINDOW': + return {'window': window, 'screen': screen, 'area': area, 'region': region} + break + +AXIS = ('X', 'Y', 'Z') + +def set_meshfilter(_type, _strength, _axis): + #def_axis = set(AXIS[i] for i, axis in enumerate(_axis) if axis) + bpy.ops.sculpt.mesh_filter ( + #get_context(), + #'INVOKE_DEFAULT', + type=_type, + strength=_strength, + deform_axis={'X', 'Y', 'Z'}, + use_face_sets=False, + surface_smooth_shape_preservation=0.5, + surface_smooth_current_vertex=0.5, + sharpen_smooth_ratio=0.35 + ) + +def set_meshfilter_smooth(self, value): + if value != 0: + set_meshfilter('SMOOTH', value, self.axis) + self.smooth = 0 + return None + +def set_meshfilter_scale(self, value): + if value != 0: + set_meshfilter('SCALE', value, self.axis) + self.scale = 0 + return None \ No newline at end of file diff --git a/atelier/experimental/mesh_filters/ui.py b/atelier/experimental/mesh_filters/ui.py new file mode 100644 index 0000000..e4f0a29 --- /dev/null +++ b/atelier/experimental/mesh_filters/ui.py @@ -0,0 +1,24 @@ +from bpy.types import Panel +from . data import mesh_filter_sliders +from .. panel import BAS_PT_experimental_panel + +class BAS_PT_mesh_filter(Panel): + bl_parent_id = 'BAS_PT_experimental_panel' + bl_label = "Mesh Filter" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Sculpt" + #bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + return context.object and context.mode == 'SCULPT' + + def draw(self, context): + main = self.layout.column(align=True) + main.scale_y = 1.1 + sliders = context.window_manager.bas_meshfilter_sliders + + for filter in mesh_filter_sliders: + box = main.box() + box.prop(sliders, filter, text=filter.capitalize(), slider=True) diff --git a/atelier/experimental/non_destructive/__init__.py b/atelier/experimental/non_destructive/__init__.py new file mode 100644 index 0000000..f5dbdcc --- /dev/null +++ b/atelier/experimental/non_destructive/__init__.py @@ -0,0 +1,35 @@ +def register(): + from .ops import classes + from bpy.utils import register_class + + from .ui import BAS_PT_non_destructive_sculpting + register_class(BAS_PT_non_destructive_sculpting) + + for cls in classes: + register_class(cls) + + from .data import NonDestructiveSculptingPG, SculptLayer, Stroke + register_class(Stroke) + register_class(SculptLayer) + register_class(NonDestructiveSculptingPG) + + from bpy.types import WindowManager as wm + from bpy.props import PointerProperty as Pointer + wm.bas_nondestructive = Pointer(type=NonDestructiveSculptingPG) + +def unregister(): + from bpy.utils import unregister_class + from bpy.types import WindowManager as wm + del wm.bas_nondestructive + + from .data import NonDestructiveSculptingPG, SculptLayer, Stroke + unregister_class(NonDestructiveSculptingPG) + unregister_class(SculptLayer) + unregister_class(Stroke) + + from .ui import BAS_PT_non_destructive_sculpting + unregister_class(BAS_PT_non_destructive_sculpting) + + from .ops import classes + for cls in reversed(classes): + unregister_class(cls) diff --git a/atelier/experimental/non_destructive/__pycache__/__init__.cpython-37.pyc b/atelier/experimental/non_destructive/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3558469341a946b7c794be0ce7cc3812a8291943 GIT binary patch literal 1229 zcmZ9MOK;Oa5XX1@h@I3)tG0p`F1`9vp>hHtL?0>=f+!GBC1hmW?5eWxi&>{_kbOWP zZd~~eNPHq+IrS@WVrJd+k&QgFGrK$c`2Uo>e!s`CJ$(Hq{9-Zomx6gSu=5N*wT?zG z!N0TjOc=sM&xJK+zCFPa-MVN^{Pyuvzt9LSjD)4UW}FFASc#FEHOD>|c4DP=ZE#lc z%J_!4^Yw}yun%5sV#cJ*=ba;(*-1L7Q(LK9+iAD%=oK9r$9eXpx5(oTPSQ)4YDf3o z#7q0Ni}T#d65XG?a>9XNc;ephg5&9Y-Z+yaQc9`|3@ILfJc(5)A=Cqy+1lG3hoiS) zmS>@ms)XsJj8A2#Cex%S3C%~Bh@jAjy=&bWm9lfwx?yBC_tH5V@zYPsbh(# zjG%*EaLBln)U&CE0urW;3#_kzAPX#;Mj~)bS0&!8Q zAQB?aRIpphB$m*=Tp-W!LE=K+=#J?&#Nc1dBB2r%(X8g1YAH7Op?unMaj&PWcSc&WHn`Jqay5i5DV^`Hz5j)(?yx7YcaU=6%KMUd@YsSs26}JrU@^;pVJGOOXQI9r$ zvuJ~Les$w6V4ntn1Lgn@0XJz2aLeEx;5O|5?ijp5yJuDuz639<$k98~g2}WL@~o8N z2X@F=#`02!Pow~%TpUSJ6_Kwu4+Q6IR?bDfqI7p(2!4mcF&Alh2RELGd0sMkx6pZ< z-?KfqZk}JR$jH{-VP0i3^lCL6n2g|412D{rZED31wPTk$u}9swQRgA{sSgnax?P8G zA}G`TWO{nCcdY%#lV^vg=h{CznNH8Yn%Qs^4*ZSZ0es2_^ONNL5660XtvN`ps$!s zgm@ojNHxJUZ4ZAOv-+A^F0doqHUzU$>#4TFhs`@6A6f>Y5dA7pfSt z?t?z2{4nR`q5%C0tR4k8pzI>8c&R;zgH58A+=dI}4w|>nU}H(lhu$D0&2v$j{3}9a z3n}!!4t8C1Zv%_*D?Fxc_iTwP>mz#)m^Vd6U5LZeom$f4birg~%ZKn6Ish@DwbUf? z*ZPhZN<90O%o|rt`riivCr0wNEfK%w9%?hf4IIpy++#jH%qVO0RB|>dA*N7;?2JCO*n?<3tTv;q!Lm;oK^CQVP`OMVHg4{$oSwO z^!UsFU_cDU&&|}dA~6)1L!)0$qV`%Xu?}l38#qI4jA9a-XRW>;0)fS>upiq_&+R+g zcHf0UL}n88b?dugF~qJ=rpzUFTD@4nBdzKNxW6s{y1ov}UwaJRRf+$X>P|4#hDBt~ cU+0o;?$%Gn7x+RNzxwvT?z;VE-|M^o1J{C{K>z>% literal 0 HcmV?d00001 diff --git a/atelier/experimental/non_destructive/__pycache__/draw.cpython-37.pyc b/atelier/experimental/non_destructive/__pycache__/draw.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd3e4c3e6ece9abf0d552f23629315362c78953d GIT binary patch literal 4901 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 Mxr3juy1#}04~+emo&W#< literal 0 HcmV?d00001 diff --git a/atelier/experimental/non_destructive/__pycache__/ui.cpython-37.pyc b/atelier/experimental/non_destructive/__pycache__/ui.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f366794ff9a4fd1a7aa7e108853831801c68338 GIT binary patch literal 1416 zcma)6OK&7K5VqaVd1V9QuzSk|!AKAx_QVRI%{+pFq9J6GMQXKVdD>3Wp{Lzzdy>pB zIc=`|hs_a*U*s#N`~^;wyC+M;1#G#xzH+&`UiP5ZYajGxOy>><(H#>_IIX_ z`!;%WF#ego99eRDV8cdkw?^arz0<=JIz0SwIv!;%JU86etsyME4P=ckk=z1`IsS!! zOJo8@hNp)|Cw*c=IlB~@vHo0g0ocn5ZKF(GX&Fbg!yL z%&_!WvN?F44hm6np?3D=qU6lvvV0V!U+;eNRjM#zOy@!I3i=R(PV2&eH`1No|+Xw+mKmcDgh7I6$%t&AE*M6>KI7+XGHl zEECi$D`B?)Dsc|I-{215j2Uq*)zY>BNVw_H{kD@8OzXKY7m}-wAPn`HBkq({#zNMdVNEq9#{TDj{1xpPlh{iM`7PpsBIsZ$w$+l2d_YeDUP I0?8wP1MlRP9RL6T literal 0 HcmV?d00001 diff --git a/atelier/experimental/non_destructive/data.py b/atelier/experimental/non_destructive/data.py new file mode 100644 index 0000000..7455255 --- /dev/null +++ b/atelier/experimental/non_destructive/data.py @@ -0,0 +1,33 @@ +from bpy.types import OperatorStrokeElement, PropertyGroup +from bpy.props import CollectionProperty, BoolProperty, FloatProperty, PointerProperty, IntProperty, StringProperty +from enum import Enum + + +class StrokeMode(Enum): + NORMAL = 'NORMAL' + INVERT = 'INVERT' + SMOOTH = 'SMOOTH' + + def __call__(self): + return self.value + +class Stroke(PropertyGroup): + points : CollectionProperty(type=OperatorStrokeElement) + mode : StringProperty(default=StrokeMode.NORMAL()) + +class SculptLayer(PropertyGroup): + strokes : CollectionProperty(type=Stroke) + +class NonDestructiveSculptingPG(PropertyGroup): + # OLD SYSTEM + strokes : CollectionProperty(type=OperatorStrokeElement) + + # USER PROPERTIES + show_overlays : BoolProperty(default=True, name="Show Overlays") + + liquify_strength : FloatProperty(default=2.0, min=1.0, max=20.0, name="Liquify Strength") + curve_mode_follow_cursor : BoolProperty(default=False, name="Follow Cursor") + + # NEW LAYER SYSTEM + layers : CollectionProperty(type=SculptLayer) + active_layer_index : IntProperty(default=-1) diff --git a/atelier/experimental/non_destructive/draw.py b/atelier/experimental/non_destructive/draw.py new file mode 100644 index 0000000..27a8540 --- /dev/null +++ b/atelier/experimental/non_destructive/draw.py @@ -0,0 +1,230 @@ +from ...utils.draw2d import ( + Draw_2D_Lines, Draw_2D_Points, Draw_2D_Line, + Draw_Text, Draw_2D_Circle, Draw_2D_Rectangle, Color +) +from ...utils.draw3d import Draw_3D_Lines, Draw_3D_Points +from ...utils.space_conversion import convert_3d_spaceCoords_to_2d_screenCoords +from ...utils.geo2dutils import distance_between, is_inside_2d_rect + +orange = list(Color.Orange.value) +white = list(Color.White.value) +blue = list(Color.Turquoise.value) + + +spacing = 10 +tool_height = 25 +height = 125 +width = 160 +header_height = 30 + + +def draw_stroke_callback(self, context): + if self.area != context.area: + return + + can_draw = context.window_manager.bas_nondestructive.show_overlays + from .ops import tool_text, StrokeTool as Tool + + if self.op_mode == 'BRISTLE': + # Filter 1. + if not self.cursor_origin: + return + + # Origin Graphic. + Draw_Text(self.cursor_origin[0]-6, self.cursor_origin[1]-14, "*", 28, 0, 1, .1, .1, 1) + if self.cursor_active: + Draw_2D_Circle(self.cursor_origin, 10, 16, (1, 1, 0, 1)) + + # Filter 2. + if not self.cursor_points: + return + + # Draw cursor points. + for i, point in enumerate(self.cursor_points): + Draw_Text(point[0]-6, point[1]-8, "•", 24, 0, 1, .1, .1, .5) + Draw_2D_Circle(point, self.cursor_points_size[i], 8, (1, 1, 0, 1)) + return + + if self.op_mode == 'CURVE': + if self.bez_screen_point: + if can_draw: + Draw_2D_Lines(self.bez_screen_point, (.7, .1, .2, .4)) + for p in self.bez_screen_point: + Draw_Text(p[0]-4, p[1]-4, "•", 14, 0, 1, .5, .2, 1) + if self.bezier_points: # self.bez_space_point: + # SEARCH FOR ACTIVE POINT + minDist = 2000 + idx = 0 + #prev = None + # for p in self.bez_space_point: + # OBTENEMOS LAS COORDENADAS EN PANTALLA + # v = convert_3d_spaceCoords_to_2d_screenCoords(context, p) + # if v: + for p in self.bezier_points: + #self.bez_screen_point[idx] = v + dist = distance_between(p, self.mouse_pos) # v + if (dist < minDist): + minDist = dist + self.active_point = p + self.active_point_index = idx + # DRAW POINT + if idx == 1: + Draw_Text(p[0]-6, p[1]-8, "•", 24, 0, 1, .1, .1, 1) + Draw_Text(p[0]-6, p[1]-14, "*", 28, 0, 1, .1, .1, 1) + Draw_Text(p[0], p[1] + 10, str(int(self.bez_length)) + " / " + str(self.bez_point_count), 15, 0) + else: + Draw_Text(p[0]-6, p[1]-8, "•", 24, 0, 0, .4, 1, 1) + Draw_Text(p[0]-6, p[1]-14, "*", 28, 0, .1, .4, 1, 1) # .6, .8, 1, 1) # v + # if can_draw: + # DIBUJAMOS CADA LINEA + # if prev: + # Draw_2D_Line(prev, p, (.7, .1, .2, .4)) + # prev = p + idx += 1 + if minDist > self.dist_threshold: + self.active_point = None + # DRAW ACTIVE POINT + elif self.active_point: + Draw_Text(self.active_point[0]-6, self.active_point[1]-14, "*", 28, 0, 1, .1, .1, 1) + Draw_2D_Circle(self.active_point, 10, 16, (1, 1, 0, 1)) + if self.props.curve_mode_follow_cursor: + if distance_between(self.curve_drag_point, self.mouse_pos) < minDist: + self.active_point = None + self.curve_drag_point_active = True + else: + self.curve_drag_point_active = False + Draw_Text(self.curve_drag_point[0]-6, self.curve_drag_point[1]-14, "♦", 28, 0, 1, .1, .1, 1) + return + + n = 0 + minDist = 2000 + prev = [0, 0, 0] + near_2d_point = [0, 0] + if self.space_points: + self.snapping = False + for point in self.space_points: + # OBTENEMOS LAS COORDENADAS EN PANTALLA + v = convert_3d_spaceCoords_to_2d_screenCoords(context, point) + if v != None: + self.strokes[n].mouse = self.screen_points[n] = v + dist = distance_between(v, self.mouse_pos) # ya es abs ! + if (dist < minDist): + if self.active_point != point: # Comprobar si no es el punto ya activo + if abs(dist) < self.dist_threshold: # Si está dentro del rango + # self.near_point = point # guardar referencia coordenadas + near_2d_point = v + self.active_point = point + self.active_point_index = n + minDist = dist + if can_draw: + # DIBUJAMOS CADA LINEA + # if n != 0: + # Draw_2D_Line(prev, v, (.7, .1, .2, .5)) + + # DIBUJAMOS CADA PUNTO + Draw_Text(v[0]-6, v[1]-8, "•", 24, 0, .6, .8, 1, .8) + prev = v + n += 1 + + if minDist > self.dist_threshold: + self.active_point = None + + if can_draw: + # Start / End. + Draw_Text(self.screen_points[0][0]-6, self.screen_points[0][1]-14, "*", 28, 0, 1, .7, .2, 1) + Draw_Text(self.screen_points[-1][0]-6, self.screen_points[-1][1]-14, "*", 28, 0, 1, .7, .2, 1) + + if self.active_point: + # DIBUJAMOS PUNTO ACTIVO + if self.using_tool: + Draw_Text(near_2d_point[0]-9, near_2d_point[1]-13, "•", 36, 0, 1, .6, .8, .8) + Draw_2D_Circle(near_2d_point, 10, 16, (1, 1, 0, .8)) + + # if self.active_tool == Tool.Rope: + # p = self.screen_points[self.active_point_index] + # Draw_2D_Circle(self.mouse_pos, 24, 16, (1, 1, 1, 1)) + # Draw_2D_Line(self.mouse_pos, p, (1, 0, 0, 1)) + # Draw_Text(p[0]-9, p[1]-13, "•", 36, 0, 1, .1, .1, 1) + else: + active_point_2d = convert_3d_spaceCoords_to_2d_screenCoords(context, self.active_point) + Draw_Text(active_point_2d[0]-9, active_point_2d[1]-13, "•", 36, 0, 1, .6, .8, .8) + Draw_2D_Circle(active_point_2d, 10, 16, (1, 1, 0, .8)) + + if self.active_tool == Tool.Rope: + p = self.screen_points[self.active_point_index] + Draw_Text(p[0]-9, p[1]-13, "•", 36, 0, 1, .1, .1, 1) + if self.active_tool == Tool.Erase: + self.update_points_in_area(context) + for i in self.points_in_area: + p = self.screen_points[i] + Draw_Text(p[0]-9, p[1]-13, "•", 36, 0, 1, .1, .1, .9) + elif self.active_tool in {Tool.Magnet, Tool.Liquify}: + self.update_points_in_area(context) + self.update_points_in_area_distances(context) + rad2 = self.ups.size / 2 if self.ups.use_unified_size else self.brush.size / 2 + if self.active_tool == Tool.Magnet: + for idk, i in enumerate(self.points_in_area): + p = self.screen_points[i] + d = self.points_in_area_distances[idk] + co = (1 if d > rad2 else d / rad2, 1 if d < rad2 else 1 - abs((d - rad2) / rad2), 0, 1) + # print(co) + Draw_Text(p[0]-9, p[1]-13, "•", 36, 0, *co) + else: + for idk, i in enumerate(self.points_in_area): + p = self.screen_points[i] + d = self.points_in_area_distances[idk] + co = (1 if d < rad2 else 1 - abs((d - rad2) / rad2), 1 if d > rad2 else d / rad2, 0, 1) + # print(co) + Draw_Text(p[0]-9, p[1]-13, "•", 36, 0, *co) + + # DRAW HELP TEXTS + num_tools = Tool.ALL.value + header_y = self.y + tool_height * num_tools + half_spacing = spacing / 2 + height = tool_height * num_tools + spacing * 2 + header_height + + Draw_2D_Rectangle(self.x, self.y - spacing, width, height, (0, 0, 0, .5)) + if is_inside_2d_rect(self.mouse_pos, self.x, self.y - spacing, width, height): + if context.tool_settings.sculpt.show_brush: + context.tool_settings.sculpt.show_brush = False + mouse_y = self.mouse_pos[1] + tool_y = 0 + for i, tool in enumerate(reversed(tool_text)): + if mouse_y < self.y + tool_height * (i + 1) - half_spacing: + tool_y = self.y + tool_height * i - half_spacing + self.hover_tool = tool[1] + break + if tool_y != 0: + Draw_2D_Rectangle(self.x, tool_y, width, tool_height, blue) + else: + self.hover_tool = Tool.NONE + else: + self.hover_tool = None + if not context.tool_settings.sculpt.show_brush: + context.tool_settings.sculpt.show_brush = True + Draw_2D_Line([self.x, header_y], [self.x + width, header_y], (1, 1, 1, .5)) + Draw_Text(self.x + spacing, header_y + header_height / 2.5, "Tools", 18, 0) + + tx = self.x + spacing + for tool in tool_text: + Draw_Text( + tx, self.y + tool_height * (num_tools - tool[1].value), # position + str(tool[1].value) + " : " + tool[0], 18, 0, # text and size + *orange if self.active_tool == tool[1] else white # color + ) + + +''' + if self.screen_points: + Draw_2D_Lines(self.screen_points) + Draw_2D_Lines(self.screen_points[:-1]) + Draw_2D_Points(self.screen_points) + if self.active_point: + Draw_2D_Points([self.active_point], (1, 0, 0, 1)) + + if self.space_points: + Draw_3D_Lines(self.space_points) + Draw_3D_Points(self.space_points) + if self.active_point_3d: + Draw_3D_Points([self.active_point_3d], (1, 0, 0, 1)) +''' diff --git a/atelier/experimental/non_destructive/ops.py b/atelier/experimental/non_destructive/ops.py new file mode 100644 index 0000000..ce17b99 --- /dev/null +++ b/atelier/experimental/non_destructive/ops.py @@ -0,0 +1,789 @@ +import bpy +from bpy.types import Operator +from mathutils import Vector +from mathutils.geometry import interpolate_bezier +from enum import Enum +from time import time as Time +from ...utils.geo2dutils import ( + distance_between, get_nearest_2d_point, is_inside_2d_circle, + direction_normalized, centroid_of_triangle, min_distance_line_point, + perpendicular_vector2, angle_from_vector, rotate_point_around_another, + angle_between_vectors, mathutils_vector_to_numpy_array +) +from ...utils.space_conversion import raycast_2d_3d, convert_3d_spaceCoords_to_2d_screenCoords, convert_2d_screenCoords_to_3d_spaceCoords +from .draw import draw_stroke_callback +from ...utils.draw2d import Alignment, Color, Space, Region, Label#, Canvas, CanvasModal, CanvasOperator +from ...utils.others import ShowMessageBox, blender_version +from bpy.props import BoolProperty, StringProperty +from numpy import arange +from math import sqrt, log, radians +import random + +extended = blender_version()[1] >= 2.90 + +def hello_world(): + print("Hello World") + +class StrokeTool(Enum): + Draw = 1 + Move = 2 + Liquify = 3 + Extrude = 4 + Erase = 5 + Curve = 6 + Rope = 7 + Magnet = 8 + ALL = Magnet + NONE = 0 + +tool_text = ( + ("Draw", StrokeTool.Draw), + ("Move", StrokeTool.Move), + ("Liquify", StrokeTool.Liquify), + ("Extrude", StrokeTool.Extrude), + ("Erase", StrokeTool.Erase), + ("Curve", StrokeTool.Curve), + ("Rope", StrokeTool.Rope), + ("Magnet", StrokeTool.Magnet) +) + +class BAS_OT_rewind_start_rec(Operator): + bl_idname = "bas.rewind_start_rec" + bl_label = "Start Recording" + bl_description = "Start Recording Sculpt Strokes" + bl_options = {'REGISTER', 'UNDO'}#, 'BLOCKING'} + + draw_curve : BoolProperty(default=False) + op_mode : StringProperty(default='DEFAULT') + + def change_tool(self, ascii): + if ascii.isnumeric(): + num = int(ascii) + if num < StrokeTool.ALL.value: + self.active_tool = StrokeTool(num) + return True + return False + + def invoke(self, context, event): + #toolbar = None + #for reg in context.area.regions: + # if reg.type == 'TOOLS': + # toolbar = reg + + #if not toolbar: + # return {'FINISHED'} + + #bpy.utils.register_class(CanvasOperator) + + #self.canvas = CanvasModal(Space.VIEW_3D, Region.WINDOW)#, context.area.x, context.area.y) + #self.canvas.lock_to_area(context.area) + #button = self.canvas.new_button([toolbar.width*2 + 20, 60], [120]*2, Color.Aquamarine, Color.Gold, hello_world).set_alpha(.8) + #button.add_label("Click Me", 16, Alignment.Center, Color.Black) + + #if not self.canvas.start(): + # return {'FINISHED'} + + #Canvas().start().new_button([60]*2, [120]*2, Color.Aquamarine, Color.Gold, hello_world, Label("Click Me")) + + #CanvasOperator.canvas = self.canvas + #bpy.ops.canvas.operator() + #return {'FINISHED'} + + self.area = context.area + self.mouse_pos = Vector((0, 0)) + self.point_pos = Vector((0, 0)) + self.prev_dir_mouse = Vector((0, 0)) + self.move_ui_offset = Vector((0, 0)) + self.curve_drag_point = Vector((0, 0)) + self.start = False + self.stroke_finished = False + self.active_point = None + self.active_point_index = -1 + self.snap_point = None + self.props = context.window_manager.bas_nondestructive + self.strokes = context.window_manager.bas_nondestructive.strokes + self.strokes.clear() + self.space_points = [] + self.screen_points = [] + + self.using_tool = False + + self.dragging_ui = False + self.x = 90 + self.y = 45 + + self.liquifying = False + self.snapping = False + self.can_snap = False + self.dist_threshold = 10 + self.active_tool = StrokeTool.Draw + self.points_in_area = [] + self.points_in_area_distances = [] + self.hover_tool = None + self.inverted = False + self.num_points = 0 + self.bezier_points = [] + self.bez_screen_point = [] + self.dragging_bezier = False + self.dragging_bezier_paint = False + self.bez_length = 1 + self.bez_point_count = 10 + self.bez_first_time_project = True + self.curve_drag_point_active = False + + ''' Bristle Brush Properties ''' + self.cursor_origin = None + self.cursor_points = [] + self.cursor_points_size = [] + self.cursor_drawing = False + self.cursor_active = False + + bpy.ops.ed.undo_push(message="Dummy") # TODO. DELETE PLS u.u + bpy.ops.ed.undo_push(message="Dummy") + + context.window_manager.modal_handler_add(self) + args = (self, context) + self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_stroke_callback, args, 'WINDOW', 'POST_PIXEL') + return {'RUNNING_MODAL'} + + def get_dict(self): + strokes = [] + # expected a each sequence member to be a dict for an RNA collection + for i, stroke in enumerate(self.strokes): + d = { + 'name' : "Stroke_" + str(i), + 'is_start' : stroke.is_start, + 'location' : stroke.location, + 'mouse' : stroke.mouse, + 'pen_flip' : stroke.pen_flip, + 'pressure' : stroke.pressure, + 'size' : stroke.size, + 'time' : stroke.time + } + if extended: + d2 = { + 'mouse_event' : stroke.mouse, + 'x_tilt' : 0.0, + 'y_tilt' : 0.0 + } + d.update(d2) + #print(d) + strokes.append(d) + return strokes + + def get_hit_point(self, context): + hit, pos, normal, index, obj, matrix = raycast_2d_3d(context, self.mouse_pos) + return pos if hit else None #convert_2d_screenCoords_to_3d_spaceCoords(context, self.mouse_pos) + + def recalculate_screen_points(self, context): + for i, p in enumerate(self.screen_points): + p = convert_3d_spaceCoords_to_2d_screenCoords(context, self.space_points[i]) + + def start_trick(self, context): + self.redo_stroke(context) + self.redo_stroke(context) + + def add_stroke(self, context, event): + hit_point = self.get_hit_point(context) + if not hit_point: + return + #print("add stroke") + stroke = self.strokes.add() + stroke.is_start = False + stroke.mouse = self.mouse_pos + stroke.time = Time() - self.start_time + + stroke.size = self.ups.size if self.ups.use_unified_size else self.brush.size + stroke.pressure = event.pressure + stroke.location = hit_point + + self.space_points.append(hit_point) + self.screen_points.append(self.mouse_pos) + + def do_stroke(self, context): + #print("end stroke") + bpy.ops.ed.undo_push(message="Stroke") + bpy.ops.sculpt.brush_stroke(stroke=self.get_dict(), mode='NORMAL' if not self.inverted else 'INVERT', ignore_background_click=True) + + def do_Stroke_no_undo_push(self): + bpy.ops.sculpt.brush_stroke(stroke=self.get_dict(), mode='NORMAL' if not self.inverted else 'INVERT', ignore_background_click=True) + + def redo_stroke(self, context): + # bpy.ops.ed.undo_history(item=0) x number of strokes + #context.window_manager.print_undo_steps() + #try: + # bpy.ops.ed.undo_redo(context) # NOTE: Check if this is actually working. + #except Exception as e: + # print(e) + #try: + # bpy.ops.ed.undo() + # self.do_stroke(context) + #except Exception as e: + # print(e) + try: + bpy.ops.ed.undo() + # TODO: PROJECT SCREEN POINTS AND FORGET OTHER PROJECTIONS + except RuntimeError: + return None + finally: + self.do_stroke(context) + + def move(self, context): + hit, pos, normal, index, obj, matrix = raycast_2d_3d(context, self.mouse_pos) + if hit: + self.strokes[self.active_point_index].location = self.space_points[self.active_point_index] = pos + if distance_between(self.prev_drag_pos, self.mouse_pos) > 20: + self.prev_drag_pos = self.mouse_pos + self.redo_stroke(context) + + def liquify(self, context): + # GET AFFECTED POINTS + rad = self.ups.size if self.ups.use_unified_size else self.brush.size + strength = context.window_manager.bas_nondestructive.liquify_strength + #affected_points = [] + #for i, point in enumerate(self.screen_points): + # if is_inside_2d_circle(self.mouse_pos, point, rad): + # affected_points.append(i) + + if not self.points_in_area: + return + # CALCULATE DIRECTION + dir = direction_normalized(self.prev_drag_pos, self.mouse_pos) + + # MOVE POINTS IN DESIGNED DIRECTION IWTHIN A FALLOFF + for i in self.points_in_area: + dist = distance_between(self.screen_points[i], self.prev_drag_pos) + factor = abs(1 - dist / rad) + self.screen_points[i] += dir * factor * 10 * strength + + # REFRESH STROKE + if distance_between(self.prev_drag_pos, self.mouse_pos) > 24: + self.prev_drag_pos = self.mouse_pos + # self.redo_stroke(context) + try: + bpy.ops.ed.undo() + except RuntimeError: + return None + + # PROJECT POINTS TO NEW POINTS + for i in self.points_in_area: + hit, pos, normal, index, obj, matrix = raycast_2d_3d(context, self.screen_points[i]) + if hit: + self.strokes[i].location = self.space_points[i] = pos + self.strokes[i].mouse = self.screen_points[i] + + #affected_points.clear() + self.do_stroke(context) + + def pull(self, context): + diff_mouse = distance_between(self.prev_drag_pos, self.mouse_pos) + self.mode = -1 if self.active_point_index == 0 else 1 if self.active_point_index == (self.num_points - 1) else 0 + + + if self.mode == 0: + rango_L = range(self.active_point_index, 0) + rango_R = range(self.active_point_index, self.num_points - 1) + # Store distances. + distances_L = distances_R = [] + #print("dL") + for i in rango_L: + #print(i) + distances_L.append(distance_between(self.screen_points[i - 1], self.screen_points[i])) + #print("dR") + for i in rango_R: + #print(i) + distances_R.append(distance_between(self.screen_points[i + 1], self.screen_points[i])) + else: + rango = range(0, self.num_points - 1) if self.mode == -1 else reversed(range(0, self.num_points - 1)) + # Store distances. + distances = [] + for i in rango: + #print(i) + distances.append(distance_between(self.screen_points[i + 1], self.screen_points[i])) + + # Move active point + #hit, pos, normal, index, obj, matrix = raycast_2d_3d(context, self.mouse_pos) + #if hit: + # self.strokes[self.active_point_index].location = self.space_points[self.active_point_index] = pos + # self.screen_points[self.active_point_index] = self.mouse_pos + + # CALCULATE DIRECTION + if diff_mouse > 6: + mouse_dir = direction_normalized(self.prev_drag_pos, self.mouse_pos) + + self.screen_points[self.active_point_index] = self.prev_drag_pos + mouse_dir * diff_mouse + + # CALCULATE DISPLACEMENT FOR EACH POINT BASED ON DISTANCE DIFFERENCE. + if self.mode == 0: + i = self.active_point_index + #print("L") + for prev_dist in distances_L: + #print(i) + dir = direction_normalized(self.screen_points[i], self.screen_points[i - 1]) + self.screen_points[i - 1] = self.screen_points[i] + dir * prev_dist + i-=1 + i = self.active_point_index + #print("R") + for prev_dist in distances_R: + #print(i) + dir = direction_normalized(self.screen_points[i], self.screen_points[i + 1]) + self.screen_points[i + 1] = self.screen_points[i] + dir * prev_dist + i+=1 + else: + inc = self.mode * -1 + i = 0 if self.mode == -1 else self.num_points - 1 + for prev_dist in distances: + #print(i) + dir = direction_normalized(self.screen_points[i], self.screen_points[i + inc]) + self.screen_points[i + inc] = self.screen_points[i] + dir * prev_dist + i+=inc + + # REFRESH STROKE + if diff_mouse > 24: + self.prev_drag_pos = self.mouse_pos + # self.redo_stroke(context) + try: + bpy.ops.ed.undo() + except RuntimeError: + return None + + # PROJECT POINTS TO NEW POINTS + for i in range(0, self.num_points): + hit, pos, normal, index, obj, matrix = raycast_2d_3d(context, self.screen_points[i]) + if hit: + self.strokes[i].location = self.space_points[i] = pos + self.strokes[i].mouse = self.screen_points[i] + + self.do_stroke(context) + + def extrude(self, context, event): + self.add_stroke(context, event) + self.redo_stroke(context) + + def update_points_in_area(self, context): + # GET AFFECTED POINTS + rad = self.ups.size if self.ups.use_unified_size else self.brush.size + self.points_in_area.clear() + for i, point in enumerate(self.screen_points): + if is_inside_2d_circle(self.mouse_pos, point, rad): + self.points_in_area.append(i) + + def update_points_in_area_distances(self, context): + self.points_in_area_distances.clear() + for i in self.points_in_area: + self.points_in_area_distances.append(distance_between(self.mouse_pos, self.screen_points[i])) + + def erase(self, context): + if not self.points_in_area: + return + + for i in self.points_in_area: + self.screen_points.pop(i) + self.space_points.pop(i) + self.strokes.remove(i) + + self.points_in_area.clear() + self.redo_stroke(context) + + def magnet(self, context, inverted): + if not self.points_in_area: + return + if not self.points_in_area_distances: + return + if distance_between(self.prev_drag_pos, self.mouse_pos) > 10: + sign = 1 if not inverted else -1 + mult = 10 + rad = self.ups.size if self.ups.use_unified_size else self.brush.size + for idk, i in enumerate(self.points_in_area): + dist = self.points_in_area_distances[idk] + factor = dist / rad * sign + dir = direction_normalized(self.screen_points[i], self.mouse_pos) + self.screen_points[i] = self.screen_points[i] + dir * factor * mult + + self.prev_drag_pos = self.mouse_pos + + try: + bpy.ops.ed.undo() + except RuntimeError as e: + return None + + # PROJECT POINTS TO NEW POINTS + for i in self.points_in_area: + hit, pos, normal, index, obj, matrix = raycast_2d_3d(context, self.screen_points[i]) + if hit: + self.strokes[i].location = self.space_points[i] = pos + self.strokes[i].mouse = self.screen_points[i] + + self.do_stroke(context) + + def from_blender_bezier_curve(self, bez_points, count=10): + # Get a list of points distributed along the curve. + points_on_curve = interpolate_bezier( + bez_points[0].co, + bez_points[0].handle_right, + bez_points[1].handle_left, + bez_points[1].co, + count) + return points_on_curve + + # bez_points should be list of Vector type. + def from_quadratic_bezier_curve(self, bez_points, count=24): + points_on_curve = [] + t_inc = 1 / count + prev_point = None + dist = 0 + for t in arange(t_inc, 1, t_inc): # 0, 1 + t_inc, t_inc + p = pow(1 - t, 2)*bez_points[0] + 2*t*(1 - t)*bez_points[1] + pow(t, 2)*bez_points[2] + points_on_curve.append(p) + if prev_point is not None: + dist += distance_between(p, prev_point) + prev_point = p + return points_on_curve, dist + + # bez_points should be list of Vector type. + def from_cubic_bezier_curve(self, bez_points, count=10): + points_on_curve = [] + t_inc = 1 / count + for t in arange(0, 1 + t_inc, t_inc): + A = pow(1 - t, 3)*bez_points[0] + B = 3*t*pow(1 - t, 2)*bez_points[1] + C = 3*pow(t, 2)*(1 - t)*bez_points[2] + D = pow(t, 3)*bez_points[3] + points_on_curve.append(A + B + C + D) + return points_on_curve + + def quadratic_bezier_length(self, bez_points): + a = Vector((bez_points[0][0], bez_points[0][1])) + b = Vector((bez_points[1][0], bez_points[1][1])) + c = Vector((bez_points[2][0], bez_points[2][1])) + + v = w = Vector((0, 0)) + v.x = 2*(b.x - a.x) + v.y = 2*(b.y - a.y) + w.x = c.x - 2*b.x + a.x + w.y = c.y - 2*b.y + a.y + + uu = 4*(w.x*w.x + w.y*w.y) + + if(uu < 0.00001): + return float(sqrt((c.x - a.x)*(c.x - a.x) + (c.y - a.y)*(c.y - a.y))) + + vv = 4*(v.x*w.x + v.y*w.y) + ww = v.x*v.x + v.y*v.y + + t1 = float(2*sqrt(uu*(uu + vv + ww))) + t2 = 2*uu+vv + t3 = vv*vv - 4*uu*ww + t4 = float((2*sqrt(uu*ww))) + + return float(((t1*t2 - t3*log(t2+t1) -(vv*t4 - t3*log(vv+t4))) / (8*pow(uu, 1.5)))) + + def update_bezier_curve_points(self, context, event): + self.bez_point_count = int((max(90, min(self.bez_length, 1000)) / 1000) * 100) + self.bez_screen_point, self.bez_length = self.from_quadratic_bezier_curve(self.bezier_points, self.bez_point_count) + + def update_bezier_drag_point(self): + if self.props.curve_mode_follow_cursor: + v_AC = direction_normalized(self.bezier_points[0], self.bezier_points[2]) + normal = perpendicular_vector2(v_AC) + d_AC = distance_between(self.bezier_points[0], self.bezier_points[2]) + mid_AC = self.bezier_points[0] + v_AC * (d_AC / 2.0) + d_base_handler = min_distance_line_point(self.bezier_points[0], self.bezier_points[2], self.bezier_points[1]) + if d_base_handler < 15: + self.curve_drag_point = mid_AC + normal * -30 + else: + bez_t05 = pow(1 - .5, 2)*self.bezier_points[0] + 2*.5*(1 - .5)*self.bezier_points[1] + pow(.5, 2)*self.bezier_points[2] + # 1st Method. + #v_MH = direction_normalized(mid_AC, self.bezier_points[1]) + #self.curve_drag_point = bez_t05 - v_MH * 30 + # 2nd Method (centroid of triangle). + self.curve_drag_point = centroid_of_triangle(self.bezier_points[0], bez_t05, self.bezier_points[2]) + + + #print("Drag Point:", self.curve_drag_point) + + def project_curve(self, context, event): + self.strokes.clear() + self.start_time = Time() + size = self.ups.size if self.ups.use_unified_size else self.brush.size + for p in self.bez_screen_point: + hit, pos, normal, index, obj, matrix = raycast_2d_3d(context, p) + if hit: + stroke = self.strokes.add() + stroke.is_start = False + stroke.mouse = p + stroke.time = Time() - self.start_time + stroke.size = size + stroke.pressure = event.pressure + stroke.location = pos + if self.bez_first_time_project: + self.do_stroke(context) + else: + self.redo_stroke(context) + + def project_cursor(self, context, event): + self.strokes.clear() + #size = self.ups.size if self.ups.use_unified_size else self.brush.size + #size = size / len(self.cursor_points) + size = 5 + for i, p in enumerate(self.cursor_points): + hit, pos, normal, index, obj, matrix = raycast_2d_3d(context, p) + if hit: + stroke = self.strokes.add() + stroke.is_start = False + stroke.mouse = p + stroke.time = Time() - self.start_time + stroke.size = self.cursor_points_size[i] + stroke.pressure = event.pressure + stroke.location = pos + self.do_Stroke_no_undo_push() + + def isLeft(self, a, b, c): + return ((b[0] - a[0])*(c[1] - a[1]) - (b[1] - a[1])*(c[0] - a[0])) > 0 + + + def finish(self): + self.remove_graphics() + self.area.tag_redraw() + + def apply_tool(self, context, event): + if self.active_tool == StrokeTool.Liquify: + self.liquify(context) + elif self.active_tool == StrokeTool.Move: + self.move(context) + elif self.active_tool == StrokeTool.Rope: + self.pull(context) + elif self.active_tool == StrokeTool.Erase: + self.erase(context) + elif self.active_tool == StrokeTool.Magnet: + self.magnet(context, event.alt) + + def move_ui(self): + self.x = self.mouse_pos[0] - self.move_ui_offset[0] + self.y = self.mouse_pos[1] - self.move_ui_offset[1] + + def modal(self, context, event): + if event.type == 'ESC': + if event.value == 'PRESS': + self.finish() + return {'FINISHED'} + elif self.area != context.area: + self.finish() + return {'FINISHED'} + + if self.change_tool(event.ascii): + return {'RUNNING_MODAL'} + + # TODO: Filter if it's not a brush + self.brush = context.tool_settings.sculpt.brush + self.ups = context.tool_settings.unified_paint_settings + self.mouse_pos = Vector((event.mouse_region_x, event.mouse_region_y)) + + self.area.tag_redraw() + + if self.stroke_finished: + if self.op_mode == 'BRISTLE': + # Está usando el cursor. + if self.cursor_drawing or (event.shift and event.type in {'LEFTMOUSE', 'PEN'}): + if event.type in {'LEFTMOUSE', 'PEN'} and event.value == 'RELEASE': + self.cursor_drawing = False + return {'RUNNING_MODAL'} + # Moving cursor points. (using threshold) + d_diff_mouse = distance_between(self.prev_cursor_pos, self.mouse_pos) + if d_diff_mouse > 4: + dir_mouse = direction_normalized(self.cursor_origin, self.mouse_pos) + dir_angle = angle_from_vector(dir_mouse) + prev_dir_angle = angle_from_vector(self.prev_dir_mouse) + diff_angle = radians(dir_angle - prev_dir_angle) + # Transpose and rotate points. + offset = self.mouse_pos - self.prev_cursor_pos + d_drag_mouse = distance_between(self.cursor_origin, self.mouse_pos) + dir_drag_mouse = direction_normalized(self.cursor_origin, self.mouse_pos) + for i in range(0, len(self.cursor_points)): + self.cursor_points[i] = rotate_point_around_another(self.cursor_origin, diff_angle, self.cursor_points[i]) + offset + #self.update_bezier_drag_point() + self.cursor_origin = self.mouse_pos + # Project points. + self.prev_cursor_pos = self.mouse_pos + self.prev_dir_mouse = dir_mouse + self.project_cursor(context, event) + # Se hace click. + elif event.type in {'LEFTMOUSE', 'PEN'} and event.value == 'PRESS': + # Se hace sobre el origen del cursor ? + if distance_between(self.cursor_origin, self.mouse_pos) < 14: + self.cursor_drawing = True + self.start_time = Time() + else: + self.cursor_points.append(self.mouse_pos) + self.cursor_points_size.append(1 + random.randrange(0, 9)) + return {'RUNNING_MODAL'} + elif event.ctrl and event.value == 'PRESS': + self.cursor_origin = self.mouse_pos + else: + self.cursor_active = distance_between(self.cursor_origin, self.mouse_pos) < 14 + return {'PASS_THROUGH'} + elif self.op_mode == 'CURVE': + if self.dragging_bezier_paint: + if event.type in {'LEFTMOUSE', 'PEN'} and event.value == 'RELEASE': + self.dragging_bezier_paint = False + return {'RUNNING_MODAL'} + # MOVE CURVE BRUSH. + d_diff_mouse = distance_between(self.prev_drag_pos, self.mouse_pos) + if d_diff_mouse > 20: + dir_mouse = direction_normalized(self.curve_drag_point, self.mouse_pos) + # 1ST METHOD: + #v_AC = direction_normalized(self.bezier_points[0], self.bezier_points[2]) + #normal = perpendicular_vector2(v_AC) + #v1 = mathutils_vector_to_numpy_array(normal) + #v2 = mathutils_vector_to_numpy_array(dir_mouse) + #diff_angle = angle_between_vectors(v1, v2) + #print("1:", diff_angle) + # 2ND METHOD: + dir_angle = angle_from_vector(dir_mouse) + prev_dir_angle = angle_from_vector(self.prev_dir_mouse) + diff_angle = radians(dir_angle - prev_dir_angle) + #print("2:", diff_angle) + # TRANSPOSE AND ROTATE POINTS. + offset = self.mouse_pos - self.prev_drag_pos + d_drag_mouse = distance_between(self.curve_drag_point, self.mouse_pos) + dir_drag_mouse = direction_normalized(self.curve_drag_point, self.mouse_pos) + for i in range(0, len(self.bezier_points)): + #dist = distance_between(p, self.mouse_pos) + #dir = direction_normalized(p, self.mouse_pos) + #p = rotate_point_around_another(self.curve_drag_point, angle, p) + dir * dist + dir_drag_mouse * d_drag_mouse + self.bezier_points[i] = rotate_point_around_another(self.curve_drag_point, diff_angle, self.bezier_points[i]) + offset #dir_drag_mouse * d_drag_mouse #+ dir_mouse * d_diff_mouse + self.update_bezier_drag_point() + # UPDATE and PROJECT. + self.update_bezier_curve_points(context, event) + self.prev_drag_pos = self.mouse_pos + self.prev_dir_mouse = dir_mouse + self.project_curve(context, event) + elif self.dragging_bezier: + if event.type in {'LEFTMOUSE', 'PEN'} and event.value == 'RELEASE': + self.dragging_bezier = False + return {'RUNNING_MODAL'} + else: + self.bezier_points[self.active_point_index] = self.active_point = self.mouse_pos + self.update_bezier_curve_points(context, event) + self.update_bezier_drag_point() + #self.bez_length = self.quadratic_bezier_length(self.bezier_points) + elif self.active_point or self.curve_drag_point_active: + if event.type in {'LEFTMOUSE', 'PEN'} and event.value == 'PRESS': + if self.curve_drag_point_active: + print("Active Drag Point") + self.dragging_bezier_paint = True + else: + self.dragging_bezier = True + self.prev_drag_pos = self.mouse_pos + return {'RUNNING_MODAL'} + else: + self.update_bezier_drag_point() + elif self.using_tool: + if event.type in {'LEFTMOUSE', 'PEN'} and event.value == 'RELEASE': + self.using_tool = False + return {'RUNNING_MODAL'} + self.apply_tool(context, event) + elif self.dragging_ui: + if event.type in {'LEFTMOUSE', 'PEN'} and event.value == 'RELEASE': + self.dragging_ui = False + return {'RUNNING_MODAL'} + self.move_ui() + elif event.type in {'LEFTMOUSE', 'PEN'} and event.value == 'PRESS': + # Change tool by clicking. + if isinstance(self.hover_tool, StrokeTool): + if self.hover_tool == StrokeTool.NONE: + #print("MOVE UI") + self.dragging_ui = True + self.move_ui_offset[0] = self.mouse_pos[0] - self.x + self.move_ui_offset[1] = self.mouse_pos[1] - self.y + else: + self.active_tool = self.hover_tool + return {'RUNNING_MODAL'} + # Check if there's a valid active point. {MOVE, ROPE...} Modes that need a point. + elif self.active_tool in {StrokeTool.Move, StrokeTool.Rope}: + if self.active_point_index == -1 or not self.active_point: + return {'PASS_THROUGH'} + if self.active_tool == StrokeTool.Rope: + self.num_points = len(self.screen_points) + # Modes that are only one-click action. (no drag and so) + elif self.active_tool == StrokeTool.Extrude: + self.extrude(context, event) + return {'RUNNING_MODAL'} + self.prev_drag_pos = self.mouse_pos + self.using_tool = True + return {'RUNNING_MODAL'} + elif event.type == 'I' and event.value == 'PRESS': + self.inverted = not self.inverted + self.redo_stroke(context) + return {'RUNNING_MODAL'} + return {'PASS_THROUGH'} + + # START RELEASE + elif self.start: + if event.type in {'PEN', 'LEFTMOUSE'} and event.value == 'RELEASE': # event.type not in {'PEN', 'RIGHTMOUSE', 'MOUSEMOVE'} or + # RELEASE CURVE DRAW + if self.op_mode == 'CURVE': + dist = distance_between(self.bezier_points[0], self.mouse_pos) + dir = direction_normalized(self.bezier_points[0], self.mouse_pos) + mid_point = self.bezier_points[0] + dir * (dist / 2.0) + self.bezier_points.append(mid_point) + self.bezier_points.append(self.mouse_pos) + self.update_bezier_curve_points(context, event) + #self.bez_length = self.quadratic_bezier_length(self.bezier_points) + else: + self.do_stroke(context) + self.start_trick(context) + self.stroke_finished = True + return {'RUNNING_MODAL'} + elif self.op_mode == 'DEFAULT' and distance_between(self.mouse_pos, self.point_pos) > 20: + self.point_pos = self.mouse_pos + self.add_stroke(context, event) + return {'PASS_THROUGH'} + elif self.op_mode == 'BRISTLE': + self.cursor_origin = self.mouse_pos # update origin point + return {'PASS_THROUGH'} + + # START PRESS + elif event.type in {'PEN', 'LEFTMOUSE'} and event.value == 'PRESS': # and event.alt: + hit_point = self.get_hit_point(context) + if not hit_point: + return {'PASS_THROUGH'} + #print("start stroke") + self.start = True + self.start_time = Time() + self.point_pos = self.mouse_pos + if self.op_mode == 'DEFAULT': + stroke = self.strokes.add() + stroke.is_start = True + stroke.mouse = self.mouse_pos + stroke.time = 0 + + stroke.size = self.brush.size + stroke.pressure = event.pressure + stroke.location = hit_point + + self.space_points.append(hit_point) + self.screen_points.append(self.mouse_pos) + elif self.op_mode == 'BRISTLE': + self.cursor_origin = self.prev_cursor_pos = self.mouse_pos + else: + self.bezier_points.append(self.mouse_pos) + return {'RUNNING_MODAL'} + + return {'PASS_THROUGH'} + + def remove_graphics(self): + try: + if hasattr(self, '_handle'): + bpy.types.SpaceView3D.draw_handler_remove(self._handle, "WINDOW") + del self._handle + if hasattr(self, 'canvas'): + self.canvas.stop() + del self.canvas + except Exception as e: + print(e) + + +classes = ( + BAS_OT_rewind_start_rec, +) \ No newline at end of file diff --git a/atelier/experimental/non_destructive/test.py b/atelier/experimental/non_destructive/test.py new file mode 100644 index 0000000..b9200d1 --- /dev/null +++ b/atelier/experimental/non_destructive/test.py @@ -0,0 +1,127 @@ +import bpy +from bpy.types import Operator + + +class BAS_OT_rewind_start(Operator): + bl_idname = "bas.rewind_start" + bl_label = "Start Rewind" + bl_description = "Start Rewind" + bl_options = {'REGISTER', 'UNDO'}#, 'BLOCKING'} + + def execute(self, context): + self.area = context.area + self.mouse_pos = Vector((0, 0)) + self.point_pos = Vector((0, 0)) + self.start = False + self.stroke_finished = False + self.active_point = None + self.active_point_index = -1 + self.snap_point = None + self.strokes = context.window_manager.bas_nondestructive.strokes + self.strokes.clear() + self.space_points = [] + self.screen_points = [] + + self.using_tool = False + self.liquifying = False + self.snapping = False + self.dragging = False + self.dragging_multiple = False + self.snapping = False + self.can_snap = False + self.dist_threshold = 10 + self.active_tool = StrokeTool.Draw + self.points_in_area = [] + self.hover_tool = None + + context.window_manager.modal_handler_add(self) + args = (self, context) + self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_stroke_callback, args, 'WINDOW', 'POST_PIXEL') + return {'RUNNING_MODAL'} + + def add_stroke(self, context, event): + hit_point = self.get_hit_point(context) + if not hit_point: + return + print("add stroke") + stroke = self.strokes.add() + stroke.is_start = False + stroke.mouse = self.mouse_pos + stroke.time = Time() - self.start_time + + stroke.size = self.brush.size + stroke.pressure = event.pressure + stroke.location = hit_point + + self.space_points.append(hit_point) + self.screen_points.append(self.mouse_pos) + + def get_dict(self): + strokes = [] + # expected a each sequence member to be a dict for an RNA collection + for i, stroke in enumerate(self.strokes): + d = { + 'name' : "Stroke_" + str(i), + 'is_start' : stroke.is_start, + 'location' : stroke.location, + 'mouse' : stroke.mouse, + 'pen_flip' : stroke.pen_flip, + 'pressure' : stroke.pressure, + 'size' : stroke.size, + 'time' : stroke.time + } + #print(d) + strokes.append(d) + return strokes + + def do_stroke(self, context): + print("end stroke") + bpy.ops.ed.undo_push(message="Stroke") + bpy.ops.sculpt.brush_stroke(stroke=self.get_dict(), mode='NORMAL', ignore_background_click=False) + + def redo_stroke(self, context): + bpy.ops.ed.undo() + # TODO: PROJECT SCREEN POINTS AND FORGET OTHER PROJECTIONS + self.do_stroke(context) + + def modal(self, context, event): + if event.type == 'ESC': + if event.value == 'PRESS': + self.finish() + return {'FINISHED'} + elif self.area != context.area: + self.finish() + return {'FINISHED'} + + if self.start: + if event.type in {'PEN', 'LEFTMOUSE'} and event.value == 'RELEASE': # event.type not in {'PEN', 'RIGHTMOUSE', 'MOUSEMOVE'} or + self.do_stroke(context) + self.stroke_finished = True + return {'PASS_THROUGH'} + elif distance_between(self.mouse_pos, self.point_pos) > 20: + self.point_pos = self.mouse_pos + self.add_stroke(context, event) + return {'PASS_THROUGH'} + + elif event.type in {'PEN', 'LEFTMOUSE'} and event.value == 'PRESS': # and event.alt: + hit_point = self.get_hit_point(context) + if not hit_point: + return + print("start stroke") + self.start = True + self.start_time = Time() + self.point_pos = self.mouse_pos + stroke = self.strokes.add() + stroke.is_start = True + stroke.mouse = self.mouse_pos + stroke.time = 0 + + stroke.size = self.brush.size + stroke.pressure = event.pressure + stroke.location = hit_point + + self.space_points.append(hit_point) + self.screen_points.append(self.mouse_pos) + return {'RUNNING_MODAL'} + + return {'PASS_THROUGH'} \ No newline at end of file diff --git a/atelier/experimental/non_destructive/ui.py b/atelier/experimental/non_destructive/ui.py new file mode 100644 index 0000000..846bdaf --- /dev/null +++ b/atelier/experimental/non_destructive/ui.py @@ -0,0 +1,31 @@ +from bpy.types import Panel +from ..panel import BAS_PT_experimental_panel + +class BAS_PT_non_destructive_sculpting(Panel): + bl_parent_id = "BAS_PT_experimental_panel" + bl_label = "Non-Destructive Sculpting" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Sculpt" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + return context.object and context.mode == 'SCULPT' + + def draw(self, context): + main = self.layout + main.scale_y = 1 + rewind = context.window_manager.bas_nondestructive + + main.operator('bas.rewind_start_rec', text="Start").op_mode = 'DEFAULT' + main.operator('bas.rewind_start_rec', text="Start (Curve)").op_mode = 'CURVE' + main.operator('bas.rewind_start_rec', text="Start (Bristle)").op_mode = 'BRISTLE' + + main.separator() + + props = main.box() + props.prop(rewind, 'show_overlays') + props.prop(rewind, 'liquify_strength') + props.separator() + props.prop(rewind, 'curve_mode_follow_cursor') diff --git a/atelier/experimental/panel.py b/atelier/experimental/panel.py new file mode 100644 index 0000000..6a4a95e --- /dev/null +++ b/atelier/experimental/panel.py @@ -0,0 +1,14 @@ +from bpy.types import Panel + +class BAS_PT_experimental_panel(Panel): + bl_label = "Experimental" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Sculpt" + + @classmethod + def poll(cls, context): + return context.object and context.mode == 'SCULPT' + + def draw(self, context): + pass diff --git a/atelier/icons/__init__.py b/atelier/icons/__init__.py new file mode 100644 index 0000000..47e5ae6 --- /dev/null +++ b/atelier/icons/__init__.py @@ -0,0 +1,78 @@ +# ----------------------------------------------------------------- # +# ICONS // PREVIEW COLLECTION +# ----------------------------------------------------------------- # +from os.path import dirname, basename, isfile, join, realpath +import glob +from bpy.utils import previews +from enum import Enum + +preview_collections = {} + +class Icon(Enum): + ARROW_UP = 'arrowUp_icon' + ARROW_DOWN = 'arrowDown_icon' + BMARKET = 'bMarket_icon' + BRUSH = 'brush_icon' + BRUSH_ADD = 'brushAdd_icon' + BRUSH_REMOVE = 'brushRemove_icon' + BRUSH_RESET = 'brushReset_icon' + BRUSH_SAVE = 'brushSave_icon' + CUBEBRUSH = 'cubebrush_icon' + DYNTOPO = 'dyntopo_icon' + DYNTOPO_BRUSH = 'dyntopoBrush_icon' + DYNTOPO_CONSTANT = 'dyntopoConstant_icon' + DYNTOPO_RELATIVE = 'dyntopoRelative_icon' + DYNTOPO_MANUAL = 'dyntopoManual_icon' + DYNTOPO_HIGH = 'dyntopoHighDetail_icon' + DYNTOPO_MEDIUM = 'dyntopoMidDetail_icon' + DYNTOPO_LOW = 'dyntopoLowDetail_icon' + FALLOFF = 'fallOff_icon' + FRONTFACES = 'frontFaces_icon' + MASK = 'mask_icon' + MASK_CAVITY = 'maskCavity_icon' + MASK_CLEAR = 'maskClear_icon' + MASK_EXTRACT = 'maskExtractor_icon' + MASK_INVERT = 'maskInvert_icon' + MASK_SHARP = 'maskSharp_icon' + MASK_SMOOTH = 'maskSmooth_icon' + MASK_TOPOLOGY = 'maskTopology_icon' + MIRROR = 'mirror_icon' + PAYPAL = 'paypal_icon' + RAKE = 'rake_icon' + SEPARATOR = 'separator_icon' + STROKE = 'stroke_icon' + STROKE_AIRBRUSH = 'strokeAirbrush_icon' + STROKE_ANCHORED = 'strokeAnchored_icon' + STROKE_CURVE = 'strokeCurve_icon' + STROKE_DOTS = 'strokeDots_icon' + STROKE_DRAGDOT = 'strokeDragDot_icon' + STROKE_LINE = 'strokeLine_icon' + STROKE_SPACE = 'strokeSpace_icon' + TEXTURE = 'texture_icon' + TEXTURE_NEW = 'textureNew_icon' + TEXTURE_OPEN = 'textureOpen_icon' + + def __call__(self): + return preview_collections["main"][self.value].icon_id + +def get_icon(icon_idname): + return preview_collections["main"][icon_idname.value].icon_id + +def load_icons(): + print("[ATELIER SCULPT] Loading Icons...") + icons = glob.glob(join(dirname(__file__), "*_icon.png")) + pcoll = previews.new() + + for img in icons: + #print(img) + if isfile(img): + pcoll.load(basename(img)[:-4], img, 'IMAGE') + + preview_collections["main"] = pcoll + + # DEBUG + #for key, val in preview_collections["main"].items(): + #print("Key:", key, "Value:", val) + +def remove_icons(): + previews.remove(preview_collections["main"]) diff --git a/atelier/icons/__pycache__/__init__.cpython-37.pyc b/atelier/icons/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b26f523f10be64702fdc0e544ddd77c015fd028 GIT binary patch literal 2821 zcmb_eOLN;s5(Yq!1i^` zm;oqRF8QSF|FDm#%1_8I!6k>B{1;NG^7i1Ns9d?EYQf^5`|F;W?w;<(Y_;1mhtFUC z`J?@FljDA2O!q=B6VmG31rfMCXvzHCYstpGuvol2fAbhR!JAu z$SPbRYjBlZfotR{TqoDyEpi>+CU3zF@;1CfZop0Q4%{L);azeIZj*Q64!I5M+l}A3-6Qn-~;kLd`Lckd*nm-h}?tw&*& z*_{k9ol>DQU+%jZmQQGr(=dq^QO>Ov+}7_~UM--06vl}Y7Dkn*;rdSEm5Z+xx??9? zI)1+7^#a@V4vz+I;&=sqtV5)9`@5p~Ox z&o%o?TE>1QM+Q++#AK}isB?&55u6+wd0}BrHwVlG#xhrFCGOK?Ny~gHIL$rD#oYqX z7jB-da~_M5J?zaYwU7U}i$j8q=LUDiJ;w1dYYu{zih<*W8{+F|9?9zDlkL{VVSPY!RnHiB2c;Vq}%XdTK z()I0V84^s%C@i7b=e;jJo5geLEs}WV5Q1CHl*IKtmzwjXzer|m!+2)fUg#yZ-CLZd zl5Nj1<81qH%p2ww$3JVc+U=DR4XYW|SZlNGRJQFPB1@mqE!+MM^FNn>q!@<2XHO>} z=hQ&o)1baJR*VO#l~%WmsksY{>}4y10km?`P{;a16`CbysusvOV=5?U^ru^D#;5hc zBhAt$IAnJ1ai)dRK|lSRimqzL0KAK8d#Cg1`hcB=HiDqNWA#}{|VV1f2vL?yIX8jC^L+< z*wAJG56z-BS#H1KNh%!{ADu5zx2PF<6X}bOc?Fpo{<9YGA0$|dIOlmT;a~8HkW>=! zoF~G$@REPdCDj+Tm)vvyY4401aNpj0E+q9c;hd+NXZ#B(X)v1ITmCCxlM#scvchxU zZSHYrd?J(TS9rIsiUE4tegGmXkvZ4K8hTIJ!74fbKL9op2?e8bL%a_ z!&kq(e*HSF4#&!lnn$6TRu|L@lT{S>YFySa)sQAF)EaYcpvGFZd#Ax9=ow+ z?&tn76Y`L-bES81NE`c$)7~=i{5TJc=;DQrN=%;R6U+E4FEWoPTl|tGnPIFlQt7L_ ZqyC+3l6_HbVL)1ZOAv+bU!;oE{U5z31Iz#b literal 0 HcmV?d00001 diff --git a/atelier/icons/__pycache__/icons.cpython-37.pyc b/atelier/icons/__pycache__/icons.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9fd705ebaa2865b5211807a4f8b863f8e8e2055 GIT binary patch literal 553 zcmXw#L5mYH6vvZHrjxB56|A5a@mddaP!N$tw(Gj0pocCZgwbI-dDciW6O*ay?oEn! zKSF!-E9L5Gzk&!gS?z=T-aqe!{9kgI=R*|YVEpa>P662w!NpStHn=bZ$_Vp-`&&4`FY(*X2cBS%u@#sP68Y@lj%36pp$gVf-K}oRHNwZX@FJ%*Zmn_VgXZ!z#d=lU4 U#sRl5!y`iR7&9^qn&CU-FN%ntp8x;= literal 0 HcmV?d00001 diff --git a/atelier/icons/arrowDown_icon.png b/atelier/icons/arrowDown_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a3e9c18dbbf23fad16e409c0a20d85098b2603dc GIT binary patch literal 291 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg`m7HxOnNnd0mS6qGJ; zjVKAuPb(=;EJ|f?PR%K5LsM4@!=S zx`aD=O=9h6m>~4WBvIsu-n&m+dldaX-bjDJy0BCE*MZ#NtjPh43j|K=k5=E}q;S6S3j3^P6r`UgQu&X%Q~loCIA}XV#)vj literal 0 HcmV?d00001 diff --git a/atelier/icons/bMarket_icon.png b/atelier/icons/bMarket_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..184bf6e74fffcc00a65a885e106a1e9dd9a44b83 GIT binary patch literal 3906 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 Qng9R*07*qoM6N<$f|IX^fdBvi literal 0 HcmV?d00001 diff --git a/atelier/icons/brushAdd_icon.png b/atelier/icons/brushAdd_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1a4a9df0516f88027f4adbd424285651f6f41da8 GIT binary patch literal 555 zcmV+`0@VG9P)z>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0005D zNklBgbz_6s_BmlNMfDgdSNZJSHYM~-o tCbazsm;{=EFTgk8ci8sB#@(0bL002ovPDHLkV1hPf?#2KB literal 0 HcmV?d00001 diff --git a/atelier/icons/brushRemove_icon.png b/atelier/icons/brushRemove_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..64c51c178634357b7ca5471075ad5db8a9fc8f27 GIT binary patch literal 830 zcmV-E1Ht@>P)z>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0008X zNklsO+>wk636V&+SVT{`kZr+4NU*R@H_^JN`m;8zJ}#UlzX)4f`?W6m+&$-;Z_fMs zJfG)t&NoGb<&3lf@c#j*s?})61cv1tO9xO@*Wx~`!!*9f$9P-LUoHi}9e5h&BNd2W zaSZzk$h(W$Af;>284<6Z2w(#q!gV+or=t;n;5dFLAg{@J?>sh55fLe+N70X8BI2x* zX`mgOa09MH2hKqY0>5Dd?_sx`zc~rS`{*fYS48Za{}fc!MYst!;|jE)6|?vmU*Kgq zzw-AlFVF{YNm+g?B5qyW7*K`8N7#JB*^pYYX z)z1m5>TcXqs&VFYMB~w^DQ}F8rJaWlo1C4!9ET!ewtiMzRqMZz>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT000B- zNklJBA16&7G z0yARvXMoj`o-P=Gnau#U0Sja5JwUCbfjrkGpcz;OoD`MR4R(b1Gq-gQ#k>c*$UtlU_wwWm$bg<=M_aC0XMg`wNY1B7f;f1 zNqrdsnAuF=08kcG>m@BOhB+`D*auu5)Sc;cdNz;>@)1e1vjKP%crhg1X-U(HWPLSo zIHo%2?sLp+4zNF_zf{tV*5UkH30Q|)&5Md939paYna%!P3PW>yA# z1)Lk+?{rC{1vs7v)B#&!_Z+!<6eBhX=mu^Ks$G&6CjnFgJ3{!kNm^1M^ZB6!m&WSF zRlqmyekKND1Mol)KbJHw37`RZJs31fT33McYM?959Jt-xKgjU3n}9W;XdRMtZ4$sk zKx?pXlJtBY=W~I*;Xs1(d)<9gmIAdhBzQ^uCBy+N16~XE&q->|1E3pN6k}fR?%Q)z z$ko8(z$D7b;qK2yQ51QW=3-{A0uP1)cS=%e62K)u zZ(uWSkZ@WF+~n@-g1iLS=k9%3oL&U{Aj!?_L*UkM{!fx-W~u|HfvG{fLekMOR#6mv z8amzBn&R$5IYM3!Yz+=y0PX`6wtFRAmI**-c+&u%NLmn>t_>Y7p6$=L`;$2UYys{{ zwB@n>(jn=dOaM|qPs~w$V`F1idwcu$!1;-}pu*kv=K#SipC{#3Tb?Rp@GenAwG4YVMUZY-Sfr>NB&&z-_=AfCZZYV5+1a zi}@F<1YXMU7Z?Esfs>(;y%lVJ0R{?c=SBXEt_L0l%7Mwiu`n%f1%3@9`-r5Gv;H?U pvna5QLV=kaa%1TK`#C)u>reaP_0Y}DFbn_y002ovPDHLkV1l9E@aF&k literal 0 HcmV?d00001 diff --git a/atelier/icons/brushSave_icon.png b/atelier/icons/brushSave_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9a81407374301d7e50d3ddf724175f6765c777ab GIT binary patch literal 519 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 literal 0 HcmV?d00001 diff --git a/atelier/icons/dyntopoHighDetail_icon.png b/atelier/icons/dyntopoHighDetail_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dbc55d95c5d42a19997648fa6e60ad14b4611c66 GIT binary patch literal 1646 zcmV-!29f!RP)3%n-(Zz9P^GC`-LXiZ=I zD)mSbhv!`@9A4Zx_h(6QyWPIZTKij)Tl@X~RcV@j8o);Y+zMbPfCnVK8vsfE6u^Fx zPfL16(!sJUKa-~EUjcNpEW61$_ZI-GS(d$xq_5Xp_ePRpl2MpA22k%P24IXaQD`?L z&DyafO=yqfI3|E-$Ued=iKJLkV^{}(g@uKYSEEa6)ChzAGM-w75wIwV-$`m@S$3^+ z?w0_@d%fN@&1UnH06r?|oyM5kC4Iseb6Zta#u)QslArV5|Agc-03Pi1dY>O19sLu@ zGg+41;+*>yoy_aXbPb32n7LF*-UL zksJdsapJ^@D9kxNyypww0FDR@mkOG ze9PS2TvDT%pPx^M*IRc8(=`1;9LHY-fM&C~AHYMBOi>hfmt}dh)9JjyTKnEKO^-U~ z-c%ID6=|A&yC{nHFvplI|k83&00Rz8?T3JqF+rNl%hI4&XaUl6yT)Y+loO5r@^ZbV3&Pi+S9g@t#!ooId?amNrB5Uo=g@uJ}l8m+X zjy%sNgUyZB+P45er_;HvD2l6J5rOx9UmVBxhQIfaye}}lh2+fX)2FjIj^9CY##(#1 zwf6NSXW}@%d2w;EO>)K*du+9b~c7-U(tbwdQ!+P6FBZVE2E zzSHUK0Dz^XrFNd@yCg--E461xlEdu`g0+LzjH~tV`)_SFi=e>8TQFIcX~90Txpzu%up)ATfe6_Ra9HG;9V2>!(bBr9v}mjJ|5Q&TI}+JlnDTdmfMMNu4< zghr$BY*kekNN(}o{~~Pc``-J<8jZ&DRaJeDNYcAV-U;Ai#+cgy(0l(BfCnZ0faK34eHFk1B>w^6T(8%= zW^8Qi?~+!tESuh_B=Z2y0ysN8Jv{)>1#qt4@B5%%0I(dQ+6sW>@T`RXWn8>=-R0%w z0f2Lo&P-2F55hI<0uVNmP&&O_lFiP}UQ+kfI5w-#&dy#^mtmVN$&7REZvf&f%dT|J z{Q(ydT-j(e_DlM(G3M66U~nhNj{~@kq>=PA$peGI;77)op96TPs;W;l8jU$gXR|E3 z-Z?iXX|>&Mzj?!|7{F?H1fT@4T2+<2_a%UV_kKXK1Tc_PdGB8W;3bt&6jhbW3kHP~$||K$&pjqAbJpF literal 0 HcmV?d00001 diff --git a/atelier/icons/dyntopoLowDetail_icon.png b/atelier/icons/dyntopoLowDetail_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9b581db8da8442815f1b8cbc00234812b2404b35 GIT binary patch literal 805 zcmV+=1KRwFP)^5{&z5Mu+!;Ot+jIiE|T1j<9MrDt^Uf(X|-D8rBbOTB3A+Y41!>1a&q$XKM4?# z3nwQhPXOEppn8Umlv4M!*1NvEawgH-WprPK!i;efo=a=H8<9aQN+r_-t8eC2}>Dy1H0h?J`56bFDMW6V^c0Bh|{ zU%VG2Yo4$#)>kVzPx%4>OcW3Sz=VkW{e8)I-s%AWBs1I@0VMZ^5U@v5dU6E7{tyB_ z6$&^#J>3FuRKWGzL%vava~Q|*b|%j^(-?EzIoE)HTi--cbeE*-4T?x$jCmy@3!a=V z$yyXe@6!X`!=|;~wbssx$R>dA0L}n>1hDKY4ge&r*7^>~hsoSC0N+Ty2!i06ulNWf z06A`$yzOot5_X3W(Dj&6JcNKBg#v;g z*ui-lH4r2l9`Z~q1)Ff^3rHHs^&FyZay|RtoU0>e7er)LYyG8A002Z$v;yE6vO968 zl$!T-xs^)gKq)m3;A?8gipbM)x%|jiE4M8-#!Ojj=SW^k+Vbt9w!GPFj)h@(OGIWw jBo2aL+qd!&M)-r@rvegD<}7)(00000NkvXXu0mjf89rmT literal 0 HcmV?d00001 diff --git a/atelier/icons/dyntopoManual_icon.png b/atelier/icons/dyntopoManual_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..55162dfef4b0375902d5d033a19a87255fe4f5a5 GIT binary patch literal 1861 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 literal 0 HcmV?d00001 diff --git a/atelier/icons/dyntopo_icon.png b/atelier/icons/dyntopo_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3c1db4828e77aa7fe9e9eaf7fe5b40a7711c2225 GIT binary patch literal 442 zcmV;r0Y(0aP)kdg00009a7bBm003M@ z003M@0Th%&K>z>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0003+ zNkl)M zN%#nxqjMTqcssK@^G~p+Gqd5;G#V`pqZT-T{peRel$5gd2XxV7@^K9Spdyjq34jEL zWwRnek+A4r)hHJ+0qED(nhp#077b_HVwJ=0DxIh zSvQuiQGf}F&((o6V7_e(xx9=64*R&4$t##Qp4}v-BFKqRg9v(fo9a{{eWHeEVs6pa zjim7aI3=%S_dfyG-@=;0Uh176plhf?9z6giL|a`*T8*3w*pRg{D>pvvyrg#fVkW>W zsciW60Ov{C6Iri{Rtqi!n2?0bssKRhPKR@IA#?s#ERMt_9S)aygQX@t1V0y0lb;T@ kX@Ewfsi}^4J_GQxKUCn>0V})s9smFU07*qoM6N<$f+BRR^#A|> literal 0 HcmV?d00001 diff --git a/atelier/icons/fallOff_icon.png b/atelier/icons/fallOff_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3460ce0f166b102a7076fb9be9fd3a8ac9e815b3 GIT binary patch literal 502 zcmVz>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0004j zNklTYV!}6fHFptBy((Rv<9BNVnRkex% zt|z(M0$?6*v6f_SlP;*L=QxSQBo|u%?CLrL_9Yo@dJa_8{jMmVB{|h3h^lIc-Pn`l zXA6J}c!*a?jx~wmsw&K4kmP#{fLVOSa*~5hKBKC7hdIn8`OxGytf~#HCYfyo@B+ti zI?2-(00XRGm}I7jkW|%UoWac`cUk}(=tO3mq~Aozs_Hgw;8BvZEdX}n2l|*vvbO0` zT2+_u;9ny8zguxtEn`29CVAZgU>YN=<9uh-D;OnN-;}was;=Q4{&x8YCz8CJ*kDps sx9|p2U19b+?eBMX{bYMI+Y$i30cIZR!6Xx#_y7O^07*qoM6N<$f`v-geEkdg00009a7bBm003M@ z003M@0Th%&K>z>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0003Q zNklT(Ai&$~diGyPN`T876^<0K}w!y#_=;M=t*aw8+Yo zfwmX`J+6TdUc~+a=r$o`k6~Kw*pX)k&<3|Gc%R4<>n?E2UZ#7r;Q|psCZID1=DM)8vi!37IsWSz*iL`~Uy|07*qoM6N<$f`*Hq7XSbN literal 0 HcmV?d00001 diff --git a/atelier/icons/maskCavity2_icon.png b/atelier/icons/maskCavity2_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5e4cbf0681a2ca469a90c76d2a4f85c7d305c4a5 GIT binary patch literal 2740 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 Uf`E6RFwl1lp00i_>zopr0G@<=l>h($ literal 0 HcmV?d00001 diff --git a/atelier/icons/maskInvert_icon.png b/atelier/icons/maskInvert_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..295bc46066212ac09e2236e5baf6bc0c05865f84 GIT binary patch literal 276 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg`m7HxOnNnd0mS6qGJ; zjVKAuPb(=;EJ|f?PR%KSpupo2 z_}h>1OS~*=fjaMu2f02=e3<`x+ReNkRZ{#%@_+=h-pjq8O&dh?ncqB8Wzf7A6Gi&Uh)HP?SN^p6Np2SI)YRvi7#T>K9^N S7z2S$W$<+Mb6Mw<&;$Vf?_3Q4 literal 0 HcmV?d00001 diff --git a/atelier/icons/maskSharp_icon.png b/atelier/icons/maskSharp_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..906aee1610ff32c44521852bdfb65510ccc8502f GIT binary patch literal 3900 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 literal 0 HcmV?d00001 diff --git a/atelier/icons/maskSmooth_icon.png b/atelier/icons/maskSmooth_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..83f8a6a0112869650f8ac16e2cef4a05d4ebcc11 GIT binary patch literal 5227 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 mayy85gil&rVYAX9X8WNB|8RBvx=!KdMT000M4 zNklGZIoD20j!psK3ua5xTd9QPzbsEOma z^&H39IgS$$LTItEj4@^y#*ASYDZ?h5#{QE`CQlGTqH7)irSy4^$MeZjhAy`; z#@NKf#0!d|^siX}s;Yjop`qcg58L>!LWsp;-zJ3oZOs9ol(ui!u%QP4P?@(ao0ynr zCxm>sW&lJYkzZP^*6vj>{yv0kHv5h&%RhUh04Sx%>2$s)isBC*#Sxav<=zhj0hIgV>Z2yIOy5{C&PAI$@xs%ov zSYE;yKU3@-MNuFYixnMjwOZlo)vMt1`4%<4v9S>h!zjL75rV^q4==bPG)<$0LgAVy zif_#|e9n6V&CSiN_W^`Lq1`r{?N23!qXt5Vo7>;s-VV2J-CA_4!{LCTp`itzZ`!m8 zvf0HQIYJ1=$H$j7w_+`m$-EK_2H!`6gM<9~_3INn&)dv6p{AxL&~<&u$#->i!NG$E zi$3>yy|9#1MhHP$TN_-saKV(*)O9^GIXPK}hKGm0XS3NpFe7&I==}CqO`QM zKwn?qy#76V_CRlMZ%G=GB*F0Tu<4T{lgV@-N-1t=Xvmq#;0_%+1n173D{1mxuNMXe z2Ilp5c6P#z8#hYQ5Cj3DQFEn%d+D_mF~(qQY;2n6`MQ;gUA%Y^_V3?c(&V>p-3mQD zJ@ficojL_aj~*>a!{u@<>2FGi9;baip9?X@AR3Kc6GgGjjG!#bkk99r+`p$!pN5W( z4pTnh?Af!hefxG(0P^|#HNW5gB4Ug|I2=AANz%{F2=3dr4=!E0w5S0aH*SRf{(j)N z;*NUf&Yf`m`t=1h5JDh|B7{OA)B8W0&HhxD<#!Nc3@D{8kH<4-9i0aOAq0XTKqL}*Se2!ELP-36AVevB!sT-P1!Mem zGlh-5zCP&g?uN<9NnnhDBuTJi#|}7h$;wtot-_COeVVlz_8ox4FJ##0096PN~hDG5<-?$8Y|1qP$(376=QtbOmH>F z@adVEnZ3zm@;yaS9_ob4b~mW1s_1gLh6F)qHls5$GZUSfntEDM6s;WdEbHZ{s;ZpR z>FmcCKev+cnx+lJ+5+9!(QOUn#ZdlQMo%X77#|)=PlJsknE8u`E%kNflAk@S2e4E8$sV%i+#uzgSg+f->^^g31|92}s8~k5wz;3s1 z1^|K(k`Y2p2%(Ml3?65Uea;vQ1HfQ9o&FagWL18cuKq4x1yBW01yBW01yBW01@PFm Z{{l{S+j{gOYrOyf002ovPDHLkV1jynuWz>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0009v zNkl%+8c8d{0L}qR z0_cB#6UR!s(>*JxAPnFFkmtSn0&J9Y$_#T4P#9nzoxn+8i=>avxfMVa(BIR!ASpi# z;4+YF-48%1kPi$1DkWWV&do7b>*Y;8Q_@}M++tu8@ENEzdm3bVmnAI<1E>SCt@{ng z@U}7vluD{{&h4{_FtAcH;C(UrKWq$uI!QTU05{C{{WI;bE8Sdb8n6|ZZ#p-D3ZMb# zZ9ys3F5eEWOIjEPP!BBh{I^ijS(8P8JHW(l4Db;!TT+YZFEhL7ZSrf9vcmwb06E_3 z8-Qxyg&j>1(5D-KZ@^)o9vBU*wcYC4g{zWs!vHPmhYM&Hv>buhLw~VZ$eTIZ1Y|nm9#EEZw!E>=K-?Afd9=7Nju{O$guNE_kK2W zffi3~u!UeOB!BQ^5#YA>vuXqSN^0*(PPiW^PLTEQlC-_2NolH`;jn+4{4J0vsiVhf zxpQuc8COtp+6t_<=R-ih0G$qCvZM!pT^Bg#HUoPD(yK;EBTeTukQI=cl}I|6h{B{A zSRDh=Bq__}jpl;>k4UoCO|{#S9EFZXnDkGR4^uC@5Xx z8c`CQpH@JX zrcQf?(j_v=YIhj!Ih0NmxX8TYKhuQI-U-~tYZrWT(c6+;!1$(aMb4HM<^|>gSD1Yh z9O75HJ6PMV@R;P#;H$;%5Tn7npdcdTwbU}Es+53hmIs(}cZetW%go(xe!+i%>48oA bKXb0Vyp3_QeBcqFiy1s!{an^LB{Ts580}{< literal 0 HcmV?d00001 diff --git a/atelier/icons/paypal_icon.png b/atelier/icons/paypal_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dd35897ee327e7a054c818d8353de081c5389639 GIT binary patch literal 900 zcmV-~1AF|5P)QrjQ{`u8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0009I zNkl{9cDljhz?llda==t&TOW0>wiYN@25rr`pPO3Z#2Gy?;P|6~SG5z%U*It);LHz-4Wf=F?x;|XrLe=mQT=`h8sK&yaMh4my{{*3^{u^n%E9p(7HEF zfhCLGf`10ufpkk9&nZ2WD!uE0#iiy_;{lGUW}guoN&#tKGN`f>G-)a6_XZZ>2P^P@@ a74{3MLoe~UeRJOc00007C#J literal 0 HcmV?d00001 diff --git a/atelier/icons/paypal_icon_2.png b/atelier/icons/paypal_icon_2.png new file mode 100644 index 0000000000000000000000000000000000000000..a2e1ba9fd67559ce61f4aeac2ed8806b92ed37b7 GIT binary patch literal 1015 zcmVPx&u}MThR9FekS4(RYK@jeq*(FAih}i^@AQ9q�Q9ygBMSVH!ry)D+qcD_!GQ% z6MukLg`ns`2!e>Yc0CcEpSmfq>9s;>H` zs;j1lv42cl<=^wXQSdLQWD$8D^uA%d#Te(D(>xl%`5;Cs$%3)l7^Vt=p@S0(@ zWAQDFX8;lR8&Ux{?pJEj{bks>sJvTPJ+LJupxkw9DDbN~pcJk;7OS!VMmoYK(@Ljh0uzmpA z2#|AZRE62-tW3dXu+kwsasLUrt7S^{_4TFu! z5jX@%pmSPPX)N{I4hx(0;60ZQ$pEzBCo2vrpi!dme#lyN%VF*4&HyyA0m~J^HvA#v z-YywHXb-3~7y*vQPo?Y1zZwa!Kgs~KDIni&0~KJx`|EU`DYp;jQ*tBHMxc@W+d{BZ zrVzM!u?gSM-k{n>b71*uTmt?Iw##x>{VrCiQmK_4!9M3UShnn_RDSX}cUg@0)FPk_ zKdqqrlW~q;wPS3hgVI6`s$>hc28WAiG||YLxZ2;HsR7W2pA4>)ob+Tme=wTya4mxw z4@j%F2}2_=y;P*LLTiZg>EV-!M`Wx9KpO#sIb*wSM=QMJh+Z^Ib`95RVblO;lhRVrNzgTj$QkG64Kwb| zp+t-O_a(kxI=~}EEeZgEQ%rA=bK)bs_eM@7#&tSDGRl8Q1+cF;0%}vlnK>2YAdF7{ zv?(Y#VkOK-%yehMbb{mrpf5;B{8h`@)X5>UhWfj{gy;l{3BZd$DFjPJTYF5y2AHii z6`h?RSqzJ>qZizT!fut)!n*xf#g};RrTLDhR0VhlKCfDt!uNc|^?FsZUp(G${>cWV zAdo)IXAk2HUj-SYWOAda2xtKqu7^Vk4?b0JUck3SDhON@KAM=G#%7k!<3M*2iuJnO l5NPnvlP40BdJF%#^apS2iuVKWfPMe~002ovPDHLkV1m?+-BSPn literal 0 HcmV?d00001 diff --git a/atelier/icons/rake_icon.png b/atelier/icons/rake_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4f4c24eaf52799f59154ae7dfd19247aec8e2e31 GIT binary patch literal 1109 zcmV-b1giUqP)z>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT000Bv zNklXv6`N>5Em9C`F)t=yu+V}*y22KUyI$|ind8e{S(c4v+f4*< z-tNH6|98(h|1&eBs`xht{pSI?ySr(2cJ^^&%n@ttZQlC{W6bU(Nv>|=JR=~R;#om07OKps!OWYW zX!hrg(Sb2$&^b4@Xd5zfj)+)9es8hh8pW>~V_tC19dD@*F!Q?rZqMiQgOy69T+4A2 z@#jV4P(#7h#DBHc?n=}2GCx0`5D}|t$l#__wGV&*FbxIQ@0>fn zka$x7W6Ww1xya1Ds`@#Ao9j~YNk>P=wo<87wbpid@8^hU6@d2vY^XtG;yaylhg&)e znE9}(J__I&RXt~n=?Nix1Hjhhbex%|MdTF#`&4y=h*kqQ0$@YUYR9|iszOP&30R#YX_Wh!&9weewMD!jrpYq<1w0s&`Ykv@tSyk=NqVZZB z$7hlx`PN!{uZWz_a*W0X1K?#<9l56Nxqxr?0ocRLXNjoKdtWieJP<obUsT1^nq2c4arFO^EA>fc$eN~Bx!0J=jD0`MIXUAES4Ow)9(iABN8U*&SSr>oU!xowwHBDxj8&zTLktLh1B?G4`hYJ){< zZTJ`w4Twn6764{0sOkiO5mh}!M0YUrcoapiW__YZM82%GZ6bOHz}^tTA8o&u6VY7& zP6N1`h+YpN?9U*3RrOs9p}Gp{qo%sc0P zTe61<5v>KV7QmFMDiM7M;6Y}7NJK6!>&u9UCID<@=A$Aquz>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0008b zNklcSGHk(pfBD$g+eX`Pab+~+EWRLAlL?4r3F0{N(n_g z=tU1w1uvx_w7K;lUNwI~@Zcf2we`FMy(F`<``cvZH@{vcvefFw!&%(Co~b8hO`BLj>vzhzl=ZQ3Uf0x-rr%ChXnyKa6Yz}(#2 z?|GiDyi3}T42YuW2jD@7`WXvI)ARz+0U|&E3;esjzP`J%vT_Hw2*e?}=YN1SO(S3% zC~Ck=eY&-^wQ_ZJRe)2WX2&={0~i3q`YBsL<-KQXYpYyZTH39%^WFe6H6RB3{#jL3 zWUbv>SXg-MocpGRg$1N(D!_>vAob?^_j#ZYk=FeD{6i7>&U@bs4Tymz(64{;Km|1F z>jG%0Y9%6H#BqGbId?uJAOdEAyw1Sr;eir3p{lQgMC5A``PF;h3RMH`8nD0VD5M0M zqktN57I+GruRG`PfNr-d0HcehY1-c0-0WFvUjt`nXK6GVpD!*huD=Nwf!qVGPt&H- z?RMqO1=BQ*H#Rm(=Nuw3y1~!O%ggu1EmRk9Q&o54IDY26UpmaB9M=WsfPVn#_xrTl zZFY8c5D_|^4!{fGs;X{HTDa5cd^H#h{&3F8K^gc6I1AhYE>H3L3gBZ^eKq0V@|v~w zfwi_h32-0y@d%xC3-|@F<7&eg^Qm+0+g`7CrK+lE93aFu*<;{)RV^oEq_I^YB5R8bUv<#|58Uwa_|UR4jA0mhgZc(T2{{bf~EK>+~K+h)WV$jv;@ zZyy_=M%)bzXsD{2b`*yMG*z`YPQc8hR{Rivld5Wu6VOuC;WPpN0j6Sw(|W(jLjV8( M07*qoM6N<$f+uf+F#rGn literal 0 HcmV?d00001 diff --git a/atelier/icons/strokeAnchored_icon.png b/atelier/icons/strokeAnchored_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4e1a484a84b04a6774d8ad65abf87c650096ee67 GIT binary patch literal 1219 zcmV;!1U&nRP)z>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT000C| zNkl}&1)s!OaRh;J13UnA*WF|9Gdq}g;e3|KP|D3_EANTG>jqt+$xO?vNJm+`5 z-*e9Qmyt{+`9D7y=M;btLRVlgPz+oF3p*2hb8^mCV&va z1Hf}YU*J1nmZUYOBe4)dKCl=V?oz!iX=+*kA%tvTwPp4j@VulYX%6oR40020lXR#J zCTcC;eL#8if#Luj0jhu=z)DG_DGnS4j0bKoOG*1cBT$!0dLe{TpPLJ;mQ<1sz)E1W zkxr8IMeLw?z*yi?%PrGOC+!1!fOjQ56Wf;od}5XC1n!gc!@mH85c&WgSwib2jgB2s z-LP-B$dShlmT;1=M$3Nifo<(lmo|rYDq7rX}ARNl*_YB zQe|ZAB47h>9q^%~yW;?60tw(SP$Fr4q<-%KBQ48fNypLv$hJ!s0QHjYjIB2SHv=0a z4UYqu0ZenClt`+NB;Z=0&`EcDJKF!;cKo%ZTVsvb0NiA7BjNxa1?IU)Dq5Y-^?d`l z3;5m)98Gh14_{IMtdle*MG3A2K9Dpv4qzm(9LNC{OG-pG*Vvkm+nj3B0H_2i)AYbl zpbogi=U2o50qh>7D-A*Sej}T^s@>kN@|RpG1K6hfrO;m zR7UissSdzoN$VnO+_ri z2QAUM6!tv?SOi>e#>%41ri$Y+&hh@Hd5)xaQZ!r+(8pi*N;;h4H9VqYm5F zKr$Pc;`C~A!!7LOEKq1!7n-GdU_u;dTQlJc9!B$B+qIU@mUb?dB|F}udyD77CtDM+ z!&9;2_kL4-(lNaq_zKwOcYzaTYOoz}kjvK#IAGwjBsHDxSUwJz>=4N|oy{K0kNXL; zB4c?Mht_Uq$b3mh&h*73$K@Ci(R;VZK+<9+4%w(STW&kf`n#cLydn=Ex&HSbk@9S5 h>xs|pm(%~^^ADT-E#H7Qf_4A^002ovPDHLkV1kHm9=!kn literal 0 HcmV?d00001 diff --git a/atelier/icons/strokeCurve_icon.png b/atelier/icons/strokeCurve_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d8dc04ecf12813877960020d0ca1c6d5a66898e9 GIT binary patch literal 513 zcmV+c0{;DpP)z>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0004u zNklGRtrhCCeG43?^leGMvRKiws7S3}-Wm zQD;(8A|EHBeB>k7qE|U2;k7)&J6Yb-^OVlB-Pbc*zx#gf>%Q)%Hy8|b`Sg|mZ2_vP zmt@d7psFrmrWqbfa=kS`RZZft0U>Hw}`6%HkNu;_rQ8pjT7!A7jdc5K52e8#CH zH%C5-t7;l2n}ie1%$7HGKEN;h#J48#LtISqbfjKTRkJvPHTaIVc#N0$gs)A~h30)9 z@00u(sToz(1m-b@`#AHj*(`cDtg0v2jDtz;wc3uW>L_M$F3IKA?*mnJ15-)HI=PTk z)pM*&vb8I~Yb+$$-4);s`bqY71^9>;Nv662yu+;|r&|Y9)vM-gA7E#aXRQIMY9HvqoL53Bz>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0005j zNklE!ClAgl9359g6-?$zn~olf6#uKV}9?o-t3b^me| z(*RQ%Af+?|+p!i;BjR>T08&Z?I&l;`^Yv%kM1MrQsTyD|4&Ve9Hb}J$aI9*8B^baK zw3XzS5wWalfG(UtH#!=m2@FQW#;O5kVh4J$3PqNlV;~~VR_=oAfPFZG6?l#FxDXNJ zO#!5owqZ~HtPdmN(r@mcgD%X%7~V%jttlI(l)AI6*5%SGT*kTbL;rdZ<(}Tc=92Ul zd+{V9>Qw?P!dNguM`??=S9DgoMYCHKE*kX~SaL=06v3sOowID+N5^bR-B8xdbx z0nm|$%5KcVJv>0I+5R_`11Y6IJBA}-qGjVarBsyP^jEe2|3H#=d?#jO6dxnvYfAu9 zN^RJX^LsUha54M*dn*7o<+$CNH`*8maT3p(4V%dT^yjddUp~d4FCsow4e%K2OQ)D| z+`-|97^xcIGaav}%B*IGEFJ0fuo6r~cIMld<7q?7~VsiioSze@0Up Z;0J>80MPOB3ZDP~002ovPDHLkV1i5S1Qq}Q literal 0 HcmV?d00001 diff --git a/atelier/icons/strokeDragDot_icon.png b/atelier/icons/strokeDragDot_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..853b23e78097fad7f3bb6e16e20a495a83881646 GIT binary patch literal 568 zcmV-80>}M{P)z>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0005Q zNklFeM1A{V|B&(xL zEQVXuZC8k-l=u+IpMg8>XZc>~`u*TVFAWqe&+J zC_pJC7)L*9^XSar5r&eyY@qW9y-E5z1-OIV=xO6H-!YctU;~|T4B}dnBW(ecQU)-A z6&-Z+Ew(l3!UJr=u_Ra80XU5jEb0K@8%B~`XrNcaYb?R`Bu`rb+`w=P@8?z>1?Q@8 z)~kHXuED9w(%vL9^8t)ue}@1tji*&6X7~QQ4q!1xlbo3k;5<(Lf(7T3j5e`gGw$PK zlC`ZY*nzuP)&amfY)SH_iRMGB$F3w3tpIv(4?8=!hPRUpH_^L@-0mFMjxnrj zk%c;*U@*!120DG!pn9F8uhUVy2bZz23eVh{s>BZ^dE7)}FRo(WZ0kFHj5bv58^Wpz zWE!t<6K9irS@7q$l(G`5u@oQhEXhpwkEjdK1?U3&PW=G;;`_mPQG9>^0000z>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0006S zNkl4T!~Y74{s+~TNeRscVGv$O#)W%bdnE;3uwC&dvP&No0h-8LrLBk zDxmG%*o{lDX<9yokFY1nkzoSb?!hixG=u+v?{PTEzG0WJ?fuw^3m4!gu$<)aVXvU= zqqqZGv2pg>znkFuhxtI;eG~kG1^Bl(l;pW#M%4Bx+>ZPO{9uw7hB;x|XD9f1Gx+cL z3I~!b53%COS`Xk>oR19)@Mn@79+CxZH{m7RjB{u3-|!`#Nb>5CFlxJmgSZ9fEWnTB z$t150NyE0M<7M24vuE&MC-@_Ci~U;#wB3RiC-{~H_%S?|;8XGkS$yA6kMHO^XqADtrg z=#Wm>_EJ2Lt8nIQ>lb{0y-7YEQcBxiF~Kj#8MAXg<9*zduadhhhq=I|BlO7iv4xuETS+%*%hig$5ml5d6&CT(xReYkAO^UrWwk{{M> pGHQDRw&O>z>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0008y zNkl9RW_j3YktI)bz;2R+f~W|Bj*<~(7z&ycCMX3ZxhV@f*g3Q$ z%#u>s5tS8&kkv&+Aku+?q??K|FoUuc6?W7_b9b{YzQk{K!g1+V~@VO^4^S^+4fj7BdG!xKrib;|AEN{^E zVi9)Y`XsNn0dNtX$0$6UWJ!Z|4whgKZc6e>8vy5EEk@(9B=-*Bm!;@OZ;~}_0F1|a z9FLVrZfVfY#4-%Rf+Wv&jLpM~Nq!y>pp-HK6O+7JN;v^A6 zv0eBf$;piXrIa3giy=uymQs$yMx2J%lFVoTn2HrR91A-mTS{4s+ps9fgZl+2rHsV~ z7>VnWtShAqt^j9YLy}1i08{WZj=)EF5`$|+2G{D0!Kq2M{tKX#atwZ|^Y=dfsL>mS z@#w*4NltG7xDqSt2Mj~O?rO^6I2J?jW0EoZtH7R0`!U|a4*ZE(I0K*Hyd=B308GXz z48?u5b?@D03i{ELz>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0007| zNkl9>EzJq3P_rIl3j1SFK_Ns%L==^)Aee@VbYU<>V1&338Hz%X zn1!}5$jD8gMiemxMT-^@1=*s6gcgR75FRyB3;H|bZF4UC80I?7oon|jpZ7lR`JQvn z`=0Y_wcBlj9Id$kN-1|?CB9B_{JaB{Ql7zMxE@zxsD}Q)N7$I;HSEfP?G6>0BpfT z5B_~g*7pNoJI1^G<}8lkax9qUBvzYn16`?C3i{O}-B|U^#Xsd3~0yxC5IT(oac-``L&d#GgsN=+c(I!krE2 zjU>tc&;JsN%C|fK7!TQf@O`vJdIzmDaqc20K8jQ)fw+3d7{Sd!&cls zOE>LIFpaS!Z}kc=iANgJ;UqVfQr6&;#@T@1sl(^!?{OfLX0;pK)jjS=j{ zC5_ciVq21f4evep94$;F+1d-h)i^w(9k*-J&e)%1T^Ik4xCXx@Sut;bn&iE@>eejb z)3`0k(JtPNc&$RONOGzdfCuXTdee;1blvuC=kVT&gBAXvB%jV3peDKwyK!%QJ)Ob! zB(KilyP%fiQf%l{L#x+kc`4;GtipHy^*S!49KanIPx8uw0D6Spgq@g7GBOB&z>%8+1ijbW?9;ba!ELWdK8EY;$>YAX9X8WNB|8RBvx=!KdMT0009w zNklr#R}?T8lr?p>%eqToM#q!uMeH~&ThEpFu!|e zxSS(Q)8u1@P*t@O_^z+7@5f?EPN$RZ?rvchU`tVyZz?J(iY8^VSwf)@Uawc!0r)2v z41Utu+FCRz5{Ynlct|W3!{Km`cOWFC z{LKQ4jg8UN)b!TE)6-LScXu%i<5_@8((0LrD5uL}Uz*Vjx>Pjhv3Wg8%$&vSQohr{6@lgWHl0GODV;OOXx zmX;RV0Hvj+D2hTVmE!*XzVIj1G!3`gjnC&p2x04|*xK4+W@hFMpsuctj*bqdrlx3U zXee5^w6s+4+|$$ZUh9uBJ3Gt4!ND6qWo0FPzn{&`O{%J@YzGVt4UtNvmMy^T?Je{3 z^K5T#zxC)|3ZQ8kxm@m*1xO?kTwY!hi9~D%WV2b^Za2W!)>XW)uz<_uA`l4Jvg4=PVR?C(R4OHes;cI|zyQ%`l)1S%YHMq4 z2ZY06bX_MFi~R>^YinbFf1h|fj-n{G1H4`@=jZ1?NGXFtRaLX5rUt_>SY2IxXYq4E z4-XHtx3^;$Mz56eC!wmUSyEC$b8|BzBO|s0&d$#0?d=7qlT!X6R8=(r`uqDI7i7P8bF7X@)rx>@pu>>9=08j zPNxY30+^;*Bc=S)0{H!Yy1Ke-?+4e{*I8U#=4P18QFudlZqu(7eh$;rvD zQp&ccfB|3{P(DCY{1@=;<4^)1Ow+{abbbna_5lF5Qp(o_quIwE&;JFk8qm2y?6Hji O0000Bc!NW|f{*AH?sDDXH3 zZg%}XUoZN|Y;#8e!H?hXTq^fkIcdx6rRogZmivY-oofly!gJuHsyB$i(8R&Ouuc)k b*!Q>3`dFX+g}MA?q96%RS3j3^P6y)-Y5{5f5FYX!;EHa+7XtCUAg*-*w3z84=t8DTpo^I< zgDz#d0=k^(D(Fh4YoM!{u7j>+x&gYL=_cq#rX|qLOt(NwqHryIfLUFOj0MIbV~MfM zSYfO())?!I4aO#;MCl5!5=Hf!2w&Y7(;7xgk0c29_tJKN$fdpTBZBOu6BA6AD6L>b z!gr2-~smgedK-n*>hiEMeR*Njn(5Mi_+)WRokHX^{337_ZnpE7rc4xlWbWqyeK;S_40UQKI@eTrX=w7$m}+J> z&pc5Kc{oxuvz3P?vi{t>ofik%Tp!uSXg(RJ^7$B2J9*v7E7h3Jrlyofw8?;*$K+ho zrrK;QtBQFm&sq04kyZH&CaxM>m8P63nxYOBTYm+gJ24OPRt8Let||7m-6sKe;3xig zGkj#t3BtsBc52CSeBvaIrA1EQg-dJTqtHe8&S|s?T_^FPa1*lb9Y4Hlkp=eRgjkO2 zMj^4}1o4}_{LLQXzQOFTr_u3n+q}z9ddzP7xlQ>jgR)Y7_OCv)xaCx8B_YXATM literal 0 HcmV?d00001 diff --git a/atelier/tools/__pycache__/brush_rmb.cpython-37.pyc b/atelier/tools/__pycache__/brush_rmb.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f72c140c83c2a569758952eec3f67cd71254bb66 GIT binary patch literal 19963 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 literal 0 HcmV?d00001 diff --git a/atelier/tools/__pycache__/brush_utils.cpython-37.pyc b/atelier/tools/__pycache__/brush_utils.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d8b35fe162f5cea94fb4ef18e87d43d827ab18b GIT binary patch literal 2827 zcmbsr%W~YtaR$I*u}>}^%ZFquYs-?ed9WD!l1il#la%BrRiPA$wrtcUQ{c{U2@nfF zGgy(_0#5WPRj%?U*vI@pz91iv^CY*N^b4sR(mhxzv|^tE6ndt6dU|^LiM>vzN#JSy z{U3k)_c9^>MCAH$VDb@s>Hz>wIE{&qqZQMs&pb!GcD;5F_7?g|n) z5z=bH7PX1xyPQ5F!}=?bL=J{jdrvYUgFKaEq+CB1Og@574FCw@Q%-z~Q{U#6?{JXD z9bWr_fHW7S>D9g6XY9!lJC-wb%A}a4FU7of9H{M^6iw!L4$ayiNciBBBo33a!R|N@ zUy8vVZXM&n_|)LTYwyFSz5-Cth(t89N~^FUyR>sBszq*8FF-ABeF;Y3q_i$}D*6NR zhxbaSa3Z^~&n&qEGY?@Ug<0G_1v9+ZdH!0i`qRbz*L!c7y`GH!XGi=TOpSYo&cV@; z`VF44nYoAd9z8yI{OmUm9}FA1lc#CSl*scinJDe5@hr}AT{G6utqNtxr0wJEQae1z z16?=E0{ANn>fTHVmIYywvuew*rX3~X6I~ytNiHsO>49yurE*P=|4ig6VE=lt|BKNl zN=P;O?W56dmOTL3qr)_qf-a-ISR`D?!J~ARz@cH9T$4s0Z2$bjks8Y|%hf30JWbSS zHy3dznsmrwGH~$#f2Epa)H{Yw*U~@q^`R{9lJ|=FmGB-t4m#+ z(fRG~h?ebzgae2skFg|}3dVGkv1!U@G0xkJy_f~@Vx@5$vydaAJ($M9v50jSW?WQP zfSbuaC}M1K1FRv<27uwEP+^Zc<~7v+D-AZ^xENZRh2W}r|7I@ym%Mpcd1HU3@^o6| zz~C`%`1QUNK`sWtV6leO!AU9y`KiF10aGmaLA2llSjCl3Lvfy^GJl;(n{Y!HK9fl+ zkYAVskVQ12r410WDRN4O)52jjq+(W;)yykh$hJn*EFsTeY?U5xw4+YcEgOciR5s0? zUfBYCxoj79v=XhB9W$<#UBlHcmpGmXJ6V_Gsc;IbS2N~u2f0{tF}5m+ymm(9U%1hP z^@_IqE9i)P{p1$xG&AnvIcyEEbu(M#b?(I*)6KFs-71%-x5^bt^4rl{(c9%}u~IA- zz5LDCcyoi4eOO&9`d3!D&hajd=pFWMv2M;;0tt74@*dQh_LtO1 zycgZ)oh?#s6#Zg@4GhQoU;pqMLP@a>*u7|ovbPBPQDLmdnuTYiwTgwES-o)auFNYM z*VZYT)rp0%0^EDa;QgY_p^|+?<=+ZpTeezstGi#`F{j=C`p4B+^m%uKY>{t3&*vm* zQ_zT#&q-o^hL*S+y&wIAclZ+CgZE4=TRu3_&cTy|hmT$XD|z)4;zfI(K7RQ8>661F z2_!PKwFeOyXF-0d>pYYu^_(aT6Te%P2WCpRQ1ga~`Ry#3X#45G$519)=Q7L%Q&2DB zd1n#yheFNbeC}-T?EDNmk%JN?7XIN+bxn;$B6L$_!B{Y?juL}d))APRw}iEEZ1Tn& zr|ZyjWE@=Tjyg@x*#c7_1JOx1pQcz?G5K^`3YErCzS4wUd<(nOtxSp&0gPj)#Li2| zC%sy2V?0zDoWSzS49d<)7z<+#2WqO)>lIU#u9&TCH_lF>s;kq0L+RDf565Q{iG`Pq z(>RrS=?2Bf?19WF%Vm(j#X(;F9a8tr)_Mws7knY#L5sYL;4T9Ei_mtEWxAe=X$FqM za*C=l=C`pHDRz>^U<7#&S-ashn1E-lTUE=jLY6#g} z1MWGKZCvS~Y;%xNt7G&?#QAVjF2P*(5Zp&<%{bL~(eGZn31S1vo!>HkS|AR1=`$bU z=YDHp``@Kp_;t8|Ipsr;=Qqy7{M6XmuOCm0d3=ZgWAwer5%|h?(5`Qo%J0 zD!UkC;r&e{et@zeP^xN;R(i9GV0%F$stf>u?uPx%wVTv4uNf`3!hm{BZv(}Fj)+`M zYYpHU_C>R=XSG2}+wR$B6*=I8*Q!Q~-obP1o;4qQU$eQtM~TW?=AWteh>eS literal 0 HcmV?d00001 diff --git a/atelier/tools/__pycache__/custom_ui_edit.cpython-37.pyc b/atelier/tools/__pycache__/custom_ui_edit.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53b86c9705db4270cf1ecf67b678307e1a0d4209 GIT binary patch literal 6197 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 Vuii`Og@m$I^*3vOu1$K={|g*x+Xest literal 0 HcmV?d00001 diff --git a/atelier/tools/__pycache__/custom_ui_io.cpython-37.pyc b/atelier/tools/__pycache__/custom_ui_io.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6e8cd5d75c4eac8e1a5428691f8c6f1bcb49882 GIT binary patch literal 3900 zcma)(0?KsX00W4GrRuzUeq79*-GzpeKRS=D~Y8ukmSKEBKNcSo4|njDRB(f{)R?fxCNsFnt&fez7Pq;B+U73zP&?e` z0cw|ryoK81ZQeoc^X@~VzjjLY`mAaVhtp_u5Iv2D!x2LpeS)*^J=F4VC^+LLy{}}< zP)u%e>!G2Hu{pL1`x@%G!|fARn#$zPfhljHb`MPPn0wq$-HBIPm}f1|7-MzEW@$b( zO8fAtV)u<-_ew|E^FTS{@C5I9&L0^(RCdBj?1&p_tMrOCEVpPUpIDz>Eq&#UJIYl) zw|JMYePS~MbBtda?;F1|xQ%`G9theLHtPN+%|{1vcds%k`@t_B{PYy<)M1bFBs=Ov zvm%$#NAb>B=9#e2cVxbJ{m7XW``^8VVSb#Aa-L*QPi>lKR_!>GGZ7CXIZBesO)?&5 zMZa5JNwOjq`_U-gnI%L0)*+9INZYJhQxWgS0>)xlxe@1iCad-&O0va0RF|K|#c(u} zMLrqA0w&0~kd>FqooQ4&6CU1QZP8l!(y;wJ<*|Ut=`_xGWhNXu!&WLgM?+=v*<{)e zDsPlyd51;4nt-g}#S5#RECjYFD@U&zvuNR$>4gCq8}27*{1-!f11B$tx7DUza5O4rQA&p+i>Qd{LL@E8U~OhI+uUS5JZ+ga z3t7+dE#K_18>ZOCh=y_UG~!${{trC7it)mnTiiM$+vdg}%xgwzDQj+@7~;LcQPwfz z&J{4M%)(XnF?-u6&CDnM=?S$*b>S)Vn2BfPHz3|=h@Q0(@^>(u)-DsE7rO?;i6=$h-|n|P|O^Vu{_ zMu1?+6>5*6k@7nz7F2|0oB80PYx?vDPA>3ai`OxBM7l^H+Iqvs=lId^0nJs6ThO>&rRfq^coLp4F4w&dJRIomE z@|RtG{kUgR6G6vx#zw^s8`1kJAlA--5bHF*F+wuQ<%C&%Q1O*ev@{f2 z9U*qZECN=VkjAoR<^Sg5%1Feog5oY#A-7Qw6of#JZ9ym+7)%>zKrC$WJ&b9@g7(tD z@GW>#0|T2Nh#?3g{H6qeHF0VP=I)6B7>uzu_KN_Q?%}!-+9*JZUGA1{7Q*>k1WM^D zmjb5z5SX~fOnwaee%hXNN)I+X^q;jCU4%^pM;-z^3plKmKF|oz-oi|*r0BsG0wl!2 z`gB@`DwLn2cWp6C`SW#!09ytsNDQpypXlu^dW){o_Ej+7;OpEtG0T=}$$!)P(AT1I zhg+%zNV_G1v1(JxXY9H8XgTzHfNtXqIuj3Oy##K}N2$mWjq8QTn z&|4RL=*)jeL{0J`64ALs{sTX7bOK4jYQuv}5=?oAyJ!6Sr3?PO0%;>9n>+yZPmmb; zlG7M1qdsW<A7-zlV$2fp|{^&+@>#IN$^Z3J1O9bO^-M16;wDgfneco0(|je7p) zWyayzopHBVgJ*Z@Ykc!fWRo3J5%Km}oa&a-%%e!fM;Eh<(5LXX;XRM2TbyC!}Gc5U&8G*YJk z4cg*1wP~g)hgb!BOXtilk^M&um9!R8e~@j}JljYRDH0%cq=bqti%)bZ~>@ky1x_zo9yySb+op z|5U+}gIo7raqCRq`rdB8RV&uY<>-5P#i3Vbj(A*HUQNpDItyT&_hpWdgy;3Wd fPWAkECOMy_@lPn3m82$Z%fIfw=HKvd`aAyvCZ|Kg literal 0 HcmV?d00001 diff --git a/atelier/tools/__pycache__/custom_ui_ops.cpython-37.pyc b/atelier/tools/__pycache__/custom_ui_ops.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0a9f16b6f4558c029618153f03a25bdda22482b GIT binary patch literal 4577 zcmb_gTW{1x6!zGAzhxoG2EwHd_f@5o(mo)BP?Am3rlJc<$jfR~%h{O(6R*7;dx5ON z6TI>t;IV(9zohbOrAqt-UfOexFT2TZfC7#@JLk-d=Xk#FT%471xuC$caQdI${;Dd< z-zdaa7CK9Cdw)X170zs>%UXw&=b=1U0GLy%9bxJXYZkOLFXR? z#SO=gg?DN+e|y7NvE9x-tSuTw&+P1*d%`d}_`LYiptA(GcM}?+v=~=f>NcEKOXF%Q z#kE#iWVp&xubIg5^xf!8S~;Er+w#HrmE|qt!6U=u+^~GnHGJ3T^gZA08hvYUcE|J< zCk_S5V0M{v9oOi-t2bzkzP$m)X1Ulk`?epXchLL}z7Jmbn}WLmH`#lrJONAhHCgi+ z_Fn?J&dj0mM&;^b#+4TV+u}Oyv=HPQ z%gx5x{rhXHL2iAcxv};0+G<^sMOY*=xH2O}&y_wvkn)7R8)Q4K9O8*O6Aw_u6n` zuH&_rePLTdZgu*0&u{Pc9XMRq_S(t$8g9>9>>UJI@mzHJz9@kO9(e>Suu`GIGHh_= zLjWyC3!8vs&M+La3;YNQhS7C--^PB)Fn;fwb~KXTu?>r(5agh1n>)e|D$wI1v>jea zBBg>U!!RAk_314=!;mwer6U??rcsxJ27Q$bFR&E-RT$|%gQ_W>CaPwDs#%_Ur2tj) z0DU1?h)zO^uKQGkY~7VW)Z{z|7m=?gj}v(Y^PBX5kh=JQA$i~Z3s;><#2`pp0QU3A z?7amO684_)*&7w5w=sXg_fF#P1z0H&=)vrez2rr--GVIRgbFnEl)Q+2;;q(mdvXf3 zs)R_X$Wz!LWy6M`K#VTI9dk&nsKlZAM^A2qNOBe*TEk`zn={y)#bzEGbYOW7n-dvv z9!Bvsk23=GM;L(tVF@Sd(8R%OG<0!b127=NGr)jsP>bH9V+&J`z&?$zA5(@{Pc?Ar z5a7vZqG7)BhP_o7pbF840eUJ$AC2Npsu`p4vU2 zGvbPgHUy;_Ibb8s83`&FdXfxr0|X8@VBUWLncz@8WSAicA;5*F2%#i1q+vc}pXzZ_ zPq!SZOu~2`adWOS`EUm38gAd_&UIf8XXuW27M;gJ$lv+s@mIZ( z;6qP~D4e3>A*#x&a2&D@4b(ovY=MeNDPs-v9+FvMGZ2I_K;07k&alC?h`uKn9<#Wk z;c~c;Y}hAMaO*?%M0uedYHy&zdy6$3YhwNT46>d3%;=g9)U+~~hSPHQ1rP~JB7<9| z?e0PTOKcC);;C@_Kz-Id_69*FT>239@^E(rtEQ_TF*{t-tQS5KPVrSnvfUzzL${|M zW!`Hra1U+|Gka77sZ}*slsM9XJGuUE(u>O;-tXB~XQJOx*>m!ERQ9aK4;b^lBR+*^ zNa*3qj>lgQeM%koDe{SIQ$|dm|A0AE{VXJmPiE?7$G4t}g!jWrNDCQ#6D%5!#iSjA zl2k%1m>pF@VJrq-#FbF8=U5Pn8QrXZcB;jszKxrYq*_&?_KS#cA>uT}+A+Ha^I^h; z>SSyzBtUJGN|z+-Mxl2y_;z2WcnJV-FERD#ocgg9TU|I40Zt70B^DY`BA4A0Y>7oAuK{ zey4Y^=pXciCoxWhG6hBYETihXHD|#mtK2m^VzF--^yO>7=cq~5Ct)q|gDfa@P0NY@ zZ$RH;5-VLQ`s8KoVQ?a1N;CjIhHcaHVDI5^Q4d`z$lVP;D(^9nyi3rOSQS%+R;^Vl R)qJ&3ovW6sQz{$&{Ri7g6zBi| literal 0 HcmV?d00001 diff --git a/atelier/tools/__pycache__/dyntopo.cpython-37.pyc b/atelier/tools/__pycache__/dyntopo.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7e22edd3e8b60dfe1347619781282636e432d0d GIT binary patch literal 1149 zcmaJ=OK;Oa5Z?78?}V!2fLabgoN|c}NC=?{ku-!Bp%gW350=Hs@oZYBj%{|=&{W*$ zwGw}WzqB{RsegeJvrd9q39;6W=dnBA%=mM^*{l&<-_L%2+;Rx{WsLPgo4m#?pP&E& zG$wQEQEGH6w&u2H&mGU9 zQgo<=-t4Doe8NKvt$m@nvtEDkhv?zNqd+|C80+wCusjD4iI>0u7fRO_FN3o~cm>LU z`_zW&HAP=-hd><~SH!DA6RT?J?S5~}4vrWs5|w5t3(tb&l(Wx4ob$^ElR$R=718AK z(fjmE`@|e;PgBtzEbq4mTm@0w9!Hm4yG5287wg4log|oZT?Rf4@>mUzI#ifp>ylJ~ z;xe>WjheUFySU{H#aA*Tiq5Q=%^cKSpy#%L!c&@+XBDu{E%Dq$);?BEx@#TdHpWlx z#A`|Gj$H@mjN&}pk)VjEE4v4y@lkJdq}~4E@pw;HhWn%a@!p$3ry=U*L^(_o#V?e0 zWylk*>o}_Q5Opn&81PJRDMi_wZYun?Omd~7OB&}YN)z2$xsWgjs&sv2OPscuu;lTy0Q^D37UKS%@4obpB^T0vH}rd1 zHV9PUAEv<^%kcX#Pk@W|FwGMP48YpuKkdGF=F3n-nUa10fF=1o#p8&JaZ$QI%@byt zlKzSx-E5)D{DOzM;tj->8z=~EST(w_e5@9|eEc7}byv4XotmyNX6VA0t}!-GA&-sT zVC+*K#H&bk60-;jR#i}qg9(py3pGGCnbNT%VPg^c9%Dh0q{uULNydcvm&8LO%$RP8 zZDSh3niJCWQKOD|tPOhKDk7s!NmnP?qHDgI6o#rxJHtO&7J$qACe5wX1(M0HD?7`M NUKy@QgVdlK)*rvILihjx literal 0 HcmV?d00001 diff --git a/atelier/tools/__pycache__/falloff.cpython-37.pyc b/atelier/tools/__pycache__/falloff.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3434b4865f145f648763d02d725f5d210b628c1d GIT binary patch literal 1320 zcmaJ>O>ZMb5bgQ&*s;B=mJ1SxXoUp6>#+=3%p1 z*An)2M>qM^$OFG!|G{(@ul3L7= zB{@P~wE7om0!mIF4fEprsrL{UMDE6YMZQEvVig2i_^&aSi$M68|6mcxxa^b()keg> z`LZj*mrQg-f;H=jE@rg{A0LiWKAZEq8*Q(^Daq2Bn~#>cIe2sn*v+$4R({ahf!!}^V50%Tq|CaNcn36YsNCazyNCPLa)TTL*MvyIhsw=`FJ{qWO6z^ zO;3(rjfR+jLZZF~m^nUqC-tVYl>b|0d+(sCeuL!1JVb_+*?$ef!H|Z zK(y4YxrDwG$X#_t0fa_YE9exew(`pAgxrvZw}zS-I`T>ut(8v^n0++NOK|ocU9ms( zZa(=xMI79@n~wG%;hbvAIrKQ+DAA&dIrKUIxy`G)o$j*YYvCl2;A@pHWd%cg6LQ-s z*g+61u@9m~tH!`EwHiY|Lm;)ih9{iob*-%HHN&~482%Ha3zs6*)G@RSp`I7I`Jw0d pQKUbmGt@*7+3Q9cNM`tj+$!x(JmwOTm)o$uq5myI^-BDt`!7tTVY>hT literal 0 HcmV?d00001 diff --git a/atelier/tools/brush_management/__init__.py b/atelier/tools/brush_management/__init__.py new file mode 100644 index 0000000..60cd432 --- /dev/null +++ b/atelier/tools/brush_management/__init__.py @@ -0,0 +1,34 @@ +def register(): + from bpy.utils import register_class + + from .ops import classes as OPS_CLASSES + for cls in OPS_CLASSES: + register_class(cls) + + from .ui import classes as UI_CLASSES + for cls in UI_CLASSES: + register_class(cls) + + from .data import BrushManagementPG + register_class(BrushManagementPG) + + from bpy.types import Scene as scn + from bpy.props import PointerProperty as Pointer + scn.bas_brush_management = Pointer(type=BrushManagementPG) + +def unregister(): + from bpy.utils import unregister_class + + from bpy.types import Scene as scn + del scn.bas_brush_management + + from .data import BrushManagementPG + unregister_class(BrushManagementPG) + + from .ops import classes as OPS_CLASSES + for cls in reversed(OPS_CLASSES): + unregister_class(cls) + + from .ui import classes as UI_CLASSES + for cls in reversed(UI_CLASSES): + unregister_class(cls) diff --git a/atelier/tools/brush_management/__pycache__/__init__.cpython-37.pyc b/atelier/tools/brush_management/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2215ae91e6825aacb5dab2f7d684f529f6e48a9e GIT binary patch literal 1081 zcmZWoOK;RL5VrH!?5j@-R2&f3Tq*=75JG)X1cI^X?R9vK721zZ-Dy)h^sPOWqxiS1ViZ98q?tg+lP2e<#jk) zs0!&>sOmEW|EP*GyHiy|-#N@-Mj@6~h}IV5j6zUXo7JGPPmTJVPD3XLpe;lVjgB%6;_(UMQ~b4nZ-e0*xyPyqf&4q%gbak+#BFD6&ejL{%g;vdoI z1?c(!FU6T0LC)`)!Yd2pDtc>Z*3sbm8gmW2cKCNNEK!&=bUQ#X(!8u`U#9W~oE-X= aCS3;?dg-pZVgEr^uv+kk`t?xT{q;ZjpY+H8 literal 0 HcmV?d00001 diff --git a/atelier/tools/brush_management/__pycache__/data.cpython-37.pyc b/atelier/tools/brush_management/__pycache__/data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8dcff1ee5ae13f3c317ff95e1612d7294fec8a10 GIT binary patch literal 1237 zcmZWpO>fgM7R@QtGzB#Vh20f)Lh}p z4?#lwAYVE07dT-%Zr5$8;`({t$4{U4wcTN>)d1>w{pA;Y;Q{bRgZZN{c&A=5Py+_o zi9zB7PT~fxqp=%T5-;!)Kk$=kP)%w<-2sOHSFraTV2?Q8D!~f&&p^NWS1EwLD_djE zQo^&@A!m6i{UOiAb>EZCk2Fu_IoTXC7BA515zQ8$J!TQj2%iTgT6q2}4F6UU0s;qv zz`X#Zf*tJQ$`6-#*c+7Cx43F?&EmSnD;75_UbVPsam(T-7Pl>4vv}R&4TCpv$KtNV zTNZB*0Nzn8+Li038J`hFQ<4x>^Wk~XIcL*pOnN5O8*_3Kkz3Wr8YWjLk2C2}ln~j; z3=LlDcEYrz6+3qE2;H!pN12r<=yVU(NHP*tSc?g!j&8>D`J4zo54;B}LcLPsv0X zE?p}5`($pCqS!dQX1BJIlQfGM6>>wUn6=HySg~bzk4<-%xJ#D1hW^O#va&gG%I>D2 zJ}|pXv{pw&DSATVnKgVg(RSO$0#jia=|J)PzSw?GWN6llXfKV?gve*5U|Ap$P-kI8 zp~)WieOZT4ZyqJ6k&O+lEujkl@8iVXS}W~)YL0FYG^oX<;T7IHjLwbL#|w=vps!g1b4OX uiW;CuY*-~2D={ey$$ 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 literal 0 HcmV?d00001 diff --git a/atelier/tools/brush_management/data.py b/atelier/tools/brush_management/data.py new file mode 100644 index 0000000..c52c1da --- /dev/null +++ b/atelier/tools/brush_management/data.py @@ -0,0 +1,19 @@ +from bpy.types import PropertyGroup, Brush +from bpy.props import ( + EnumProperty, BoolProperty, IntProperty, PointerProperty as Pointer +) + + +class BrushManagementPG(PropertyGroup): + toggle_brush_preview : BoolProperty(default=True, name="Toggle Brush Preview") + toggle_brush_favourites : BoolProperty(default=True, name="Toggle Favourite Brushes") + toggle_brush_pertype : BoolProperty(default=True, name="Toggle Per Type Brushes") + toggle_brush_recents : BoolProperty(default=True, name="Toggle Recent Brushes") + + show_brush_options : BoolProperty(default=True, name="Show Brush Options") + show_brush_favourites : BoolProperty(default=True, name="Show Favourite Brushes") + show_brush_pertype : BoolProperty(default=True, name="Show Per Type Brushes") + show_brush_recents : BoolProperty(default=True, name="Show Recent Brushes") + + show_only_icons : BoolProperty(default=False, name="Show Only Brush Icons") + recent_brushes_stay_in_place : BoolProperty(default=False, name="Recent Brushes: stay in place") diff --git a/atelier/tools/brush_management/ops.py b/atelier/tools/brush_management/ops.py new file mode 100644 index 0000000..4b5db88 --- /dev/null +++ b/atelier/tools/brush_management/ops.py @@ -0,0 +1,86 @@ +import bpy + + +class BAS_OT_brush_fav_remove(bpy.types.Operator): + bl_idname = "bas.brush_fav_remove" + bl_label = "Remove Fav Brush" + bl_description = "Remove Active Brush from Favourites." + + nBrush: bpy.props.StringProperty() + + def execute(self, context): + from .ui import favBrushes + brush = bpy.data.brushes.get(self.nBrush, None) + if brush: + favBrushes.remove(brush) + return {'FINISHED'} + + +class BAS_OT_brush_fav_add(bpy.types.Operator): + bl_idname = "bas.brush_fav_add" + bl_label = "Add Fav Brush" + bl_description = "Add Active Brush to Favourites." + + nBrush: bpy.props.StringProperty() + + def execute(self, context): + brush = bpy.data.brushes.get(self.nBrush, None) + if not brush: + return {'FINISHED'} + from .ui import favBrushes + if brush in favBrushes: + return {'FINISHED'} + favBrushes.append(brush) + return {'FINISHED'} + +class BAS_OT_change_brush(bpy.types.Operator): + bl_idname = "bas.change_brush" + bl_label = "" + bl_description = "Change actual brush to selected one." + + nBrush: bpy.props.StringProperty() + + def execute(self, context): + brush = bpy.data.brushes.get(self.nBrush, None) + if not brush: + return {'FINISHED'} + context.tool_settings.sculpt.brush = brush + return {'FINISHED'} + +classes = ( + BAS_OT_brush_fav_add, BAS_OT_brush_fav_remove, + BAS_OT_change_brush +) + + +''' +class NSMUI_OT_toolHeader_brushRemove(bpy.types.Operator): + bl_idname = "nsmui.ht_toolheader_brush_remove" + bl_label = "" + bl_description = "Remove Active Brush plus Unlink" + def execute(self, context): + brush = bpy.context.tool_settings.sculpt.brush + st = brush.sculpt_tool + try: + if brush in favBrushes: + favBrushes.remove(bpy.data.brushes[brush.name]) + except: + pass + try: + + bpy.data.brushes.remove(brush, do_unlink=True) + # Seleccionar automaticamente una brocha del mismo tipo + for b in bpy.data.brushes: + if b.sculpt_tool == st and b.use_paint_sculpt: + bpy.context.tool_settings.sculpt.brush = b + return {'FINISHED'} + # Sino hay ninguna del mismo tipo entonces se cogerá la primera en buscar + for b in bpy.data.brushes: + if b.use_paint_sculpt: + bpy.context.tool_settings.sculpt.brush = b + return {'FINISHED'} + except: + pass + + return {'FINISHED'} +''' diff --git a/atelier/tools/brush_management/ui.py b/atelier/tools/brush_management/ui.py new file mode 100644 index 0000000..9e5a30e --- /dev/null +++ b/atelier/tools/brush_management/ui.py @@ -0,0 +1,339 @@ +from bpy.types import Panel +from bl_ui.properties_paint_common import UnifiedPaintPanel +from ...icons import Icon +import bpy + + +# BRUSHES MAIN PANEL +class BAS_PT_Brushes(Panel, UnifiedPaintPanel): + bl_label = "Brushes" + bl_category = 'Sculpt' + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = ".paint_common" + # bl_options = {'DEFAULT_CLOSED'} + bl_order = -1 + + def draw(self, context): + if(context.mode == "SCULPT"): + props = context.scene.bas_brush_management + + try: + sculpt = context.tool_settings.sculpt + settings = self.paint_settings(context) + + brush = settings.brush + except: + pass + + layout = self.layout + split = layout.split(align=True) + + split.prop(props, 'toggle_brush_preview', icon='IMAGE_PLANE', text="", toggle=True) + split.prop(props, 'toggle_brush_favourites', icon='SOLO_ON', text="", toggle=True) + split.prop(props, 'toggle_brush_pertype', icon='IMGDISPLAY', text="", toggle=True) + split.prop(props, 'toggle_brush_recents', icon='RECOVER_LAST', text="", toggle=True) + + if props.toggle_brush_preview: + layout.use_property_decorate = False # No animation. + settings = self.paint_settings(context) + try: + brush = settings.brush + + row = layout.row() + row.column().template_ID_preview(settings, "brush", cols=4, rows=7, hide_buttons=True) + + if props.show_brush_options: + col = row.column(align=True) + col.scale_y = 1.5 + col.scale_x = 1.3 + col.operator("brush.add", text="", icon_value=Icon.BRUSH_ADD()) # DUPLICATE BRUSH + col.operator("brush.reset", text="", icon_value=Icon.BRUSH_RESET()) # RESET BRUSH + col.operator("bas.brush_remove", text="", icon_value=Icon.BRUSH_REMOVE()) # DELETE BRUSH + col.operator("bas.brush_render_icon", text="", icon='RESTRICT_RENDER_OFF') # RENDER CUSTOM ICON + except: + pass + + + count = 0 + if props.toggle_brush_favourites == True: count = count + 1 + if props.toggle_brush_pertype == True: count = count + 1 + if props.toggle_brush_recents == True: count = count + 1 + + layout = self.layout + + if count > 1: + if props.toggle_brush_favourites: + row_box = layout.row(align=True) + box = row_box.box() + box.scale_y = 0.6 + box.scale_x = 0.6 + row = box.column().row() + + if props.show_brush_favourites: + row.prop(props, "show_brush_favourites", icon="DOWNARROW_HLT", text="Favourites", emboss=False) + BAS_PT_Brushes_Favs.draw(self, context) + else: + row.prop(props, "show_brush_favourites", icon="RIGHTARROW", text="Favourites", emboss=False) + + row_box.prop(props, 'show_only_icons', text="", icon='FILE_IMAGE') + + if props.toggle_brush_pertype: + box = layout.box() + box.scale_y = 0.6 + box.scale_x = 0.6 + row = box.column().row() + + if props.show_brush_pertype: + row.prop(props, "show_brush_pertype", icon="DOWNARROW_HLT", text="Per Type", emboss=False) + BAS_PT_Brushes_ByType.draw(self, context) + else: + row.prop(props, "show_brush_pertype", icon="RIGHTARROW", text="Per Type", emboss=False) + + if props.toggle_brush_recents: + row_box = layout.row(align=True) + box = row_box.box() + box.scale_y = 0.6 + box.scale_x = 0.6 + row = box.column().row() + + if props.show_brush_recents: + row.prop(props, "show_brush_recents", icon="DOWNARROW_HLT", text="Recent Brushes", emboss=False) + BAS_PT_Brushes_Recent.draw(self, context) + else: + row.prop(props, "show_brush_recents", icon="RIGHTARROW", text="Recent Brushes", emboss=False) + + row_box.prop(props, "recent_brushes_stay_in_place", text="", icon='LOCKED' if props.recent_brushes_stay_in_place else 'UNLOCKED') + + elif props.toggle_brush_favourites: + layout.separator() + BAS_PT_Brushes_Favs.draw(self, context) + + elif props.toggle_brush_pertype: + layout.separator() + BAS_PT_Brushes_ByType.draw(self, context) + + elif props.toggle_brush_recents: + layout.separator() + BAS_PT_Brushes_Recent.draw(self, context) + + +# RECENT BRUSHES +recentBrushes = [] +class BAS_PT_Brushes_Recent(Panel): + bl_label = "Recent Brushes" + bl_context = 'NONE' + + bl_category = 'Sculpt' + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + + def draw(self, context): + if(context.mode == "SCULPT"): + try: + props = context.scene.bas_brush_management + activeBrush = context.tool_settings.sculpt.brush + # RECENT BRUSHES + length = len(recentBrushes) + n = 6 + if length == 0: # or recentBrushes == [] # EMPTY LIST + recentBrushes.append(activeBrush) + elif length < n+1: + if activeBrush in recentBrushes: + if not props.recent_brushes_stay_in_place: + recentBrushes.remove(activeBrush) + recentBrushes.append(activeBrush) + elif length == n: + recentBrushes.pop(0) + recentBrushes.append(activeBrush) + else: + recentBrushes.append(activeBrush) + except: + pass + # print (length) + # print(recentBrushes) + col = self.layout.column() # define una fila + for b in reversed(recentBrushes): + # col.label(text=b, icon_value=bpy.data.brushes[b].preview.icon_id) # SOLO PREVIEW + col.operator('bas.change_brush', text=b.name, icon_value=bpy.data.brushes[b.name].preview.icon_id).nBrush = b.name + + + +# FAV BRUSHES +favBrushes = [] +class BAS_PT_Brushes_Favs(Panel): + bl_context = 'NONE' + bl_label = "Favorite Brushes" + # bl_options = {'DEFAULT_CLOSED'} + + bl_category = 'Sculpt' + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + + def draw(self, context): + props = context.scene.bas_brush_management + try: + activeBrush = context.tool_settings.sculpt.brush + + row = self.layout.row(align=True) + brushName = activeBrush.name + row.operator('bas.brush_fav_add', text="ADD", icon='ADD').nBrush = brushName + row.operator('bas.brush_fav_remove', text="REMOVE", icon='REMOVE').nBrush = brushName + row = self.layout.row() + # print (length) + # print(recentBrushes) + except: + pass + + # RECENT BRUSHES + length = len(favBrushes) + + for region in context.area.regions: + if region.type == "UI": + width = region.width + break + #print(width) + + k = 0 + i = 1 + if props.show_only_icons: + #if length != 0: + + col = self.layout.grid_flow() + + if width > 420: + i = 10 + colY = 1.6 + colX = 1.6 + elif width > 350: + i = 8 + colY = 1.8 + colX = 1.8 + elif width > 280: + i = 6 + colY = 2 + colX = 2 + elif width > 230: + i = 5 + colY = 2 + colX = 2 + else: + i = 4 + colY = 1.8 + colX = 1.8 + + col.scale_x = colX + col.scale_y = colY + + for b in favBrushes: + try: + if b.name == brushName: + act = True + else: + act = False + except: + act = False + #row.template_icon(icon_value=bpy.data.brushes[b].preview.icon_id, scale=5) + # col.label(text=b, icon_value=bpy.data.brushes[b].preview.icon_id) # SOLO PREVIEW + k = k + 1 + if k == 1: + col = col.row() + #col.label(text=b, icon_value=bpy.data.brushes[b].preview.icon_id) # SOLO PREVIEW + col.operator('bas.change_brush', depress=act, text="", icon_value=bpy.data.brushes[b.name].preview.icon_id).nBrush = b.name + if k == i: + col = self.layout.column() + col.scale_x = colX + col.scale_y = colY + k = 0 + else: + if width > 280: + i = 2 + if width > 420: + i = 3 + if length != 0: + #col = row.column() # define una fila + #col.separator() + col = self.layout.grid_flow() + col.scale_y = 1.1 + col.scale_x = 1.1 + + for b in favBrushes: + try: + if b.name == brushName: + act = True + else: + act = False + except: + act = False + #row.template_icon(icon_value=bpy.data.brushes[b].preview.icon_id, scale=5) + # col.label(text=b, icon_value=bpy.data.brushes[b].preview.icon_id) # SOLO PREVIEW + if i == 1: + col.operator('bas.change_brush', depress=act, text=b.name, icon_value=bpy.data.brushes[b.name].preview.icon_id).nBrush = b.name + else: + k = k + 1 + if k == 1: + col = col.row() + col.operator('bas.change_brush', depress=act, text=b.name, icon_value=bpy.data.brushes[b.name].preview.icon_id).nBrush = b.name + if k == i: + col = self.layout.column() + col.scale_y = 1.1 + col.scale_x = 1.1 + k = 0 + + +class BAS_PT_Brushes_ByType(Panel): + bl_context = 'NONE' + bl_label = "Related Brushes" + # bl_options = {'DEFAULT_CLOSED'} + + bl_category = 'Sculpt' + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + + def draw(self, context): + try: + brush = context.tool_settings.sculpt.brush + #sculpt = context.tool_settings.sculpt + i = 1 + for region in context.area.regions: + if region.type == "UI": + width = region.width + break + #print(width) + if width > 280: + i = 2 + if width > 420: + i = 3 + col = self.layout.column() # define una fila + # BRUSH LIST + k = 0 + for b in bpy.data.brushes: + if (b.sculpt_tool == brush.sculpt_tool) and b.use_paint_sculpt: + icon = bpy.data.brushes[b.name].preview + if b.name == brush.name: + act = True + else: + act = False + #icon.icon_size = (2,2) + if i == 1: + col.scale_y = 1.1 + col.scale_x = 1.1 + col.operator('bas.change_brush', depress=act, text=b.name, icon_value=icon.icon_id).nBrush = b.name + else: + k = k + 1 + if k == 1: + col = col.row() + col.operator('bas.change_brush', depress=act, text=b.name, icon_value=icon.icon_id).nBrush = b.name + if k == i: + col = self.layout.column() + k = 0 + except: + pass + + +classes = ( + BAS_PT_Brushes, + BAS_PT_Brushes_ByType, + BAS_PT_Brushes_Favs, + BAS_PT_Brushes_Recent +) diff --git a/atelier/tools/brush_thumbnailer/__init__.py b/atelier/tools/brush_thumbnailer/__init__.py new file mode 100644 index 0000000..1bfa73d --- /dev/null +++ b/atelier/tools/brush_thumbnailer/__init__.py @@ -0,0 +1,31 @@ +def register(): + from .ops import classes + from bpy.utils import register_class + + from .ui import BrushThumbnailerOptions + register_class(BrushThumbnailerOptions) + + for cls in classes: + register_class(cls) + + from .data import BrushThumbnailerPG + register_class(BrushThumbnailerPG) + + from bpy.types import WindowManager as wm + from bpy.props import PointerProperty as Pointer + wm.bas_brush_thumbnailer = Pointer(type=BrushThumbnailerPG) + +def unregister(): + from bpy.utils import unregister_class + from bpy.types import WindowManager as wm + del wm.bas_brush_thumbnailer + + from .data import BrushThumbnailerPG + unregister_class(BrushThumbnailerPG) + + from .ui import BrushThumbnailerOptions + unregister_class(BrushThumbnailerOptions) + + from .ops import classes + for cls in reversed(classes): + unregister_class(cls) diff --git a/atelier/tools/brush_thumbnailer/__pycache__/__init__.cpython-37.pyc b/atelier/tools/brush_thumbnailer/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1de401bd7e9a94779ceb9846411cb148d8504b9 GIT binary patch literal 1084 zcmZWoOK;RL5VrH)ZML*kOX0>fmkN~=2qF4Vk&q~bf+|%(A-lG=k(>wPgm$Hb3tTzy zGm!X2d*#%>z=;{B+w!oKvFDqI$1`7cN24Lb_2AWyvp8#cYkfxS zV!dVi>|NN|h}eXD*ghdoPkFiDImXi=95eYU$OoNk&Vd@{OPz;yuXV)WD{tK-6bvgH z9$s>+BI0v@qEfA;zC@MuWsuWM*AfzAKxA_l8h!TWtjWhknkfk{t2!$Sji$AKO-IiV z9i3!_D9>M{MLLy$z^$V)D=_5|l$C^fhLBUwDjALDc3EklPRB4%NA?B+=J|v1=+^LsZ zB$*+iiyp2a;wci52o)RxU5}GpX^O=Q78EkWf}U`u?GZO~z>P*9g|LJhY?4?eLBoX= z5+f4yI0#7)A%K3#o-^31k2ohOBCn5LBa?AAo|GL{Nzh<>v4N-g@TN!#F~6{BGj~j* lso5c4=5QCShmmEPZInMUnVcqF@Hj6;qvR7aGrBcc{SBp}_3Quu literal 0 HcmV?d00001 diff --git a/atelier/tools/brush_thumbnailer/__pycache__/data.cpython-37.pyc b/atelier/tools/brush_thumbnailer/__pycache__/data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..428550051b706f1888df5ca7c769e896467cea89 GIT binary patch literal 2048 zcmZ`)OK%)S5T2QxecHQm>;M4*X@`fIKuky=B*A38HgOabKZtFRR*OcnTkYLZ=0#6W z63-Q`{sC~}io_q_AAkfWPRt1@rm^fL-Hzooa=$_{5U0tWS;Te9ZTk_4W>6g1@ztXMv)o#_Vb!)nIM}rcW z-)LZR{p(V<4&{B#s*KT!X6adTPlOQ{^7xJjlgO%M^BZC4O_Nr`)H@>E88rQlyXkFt_<0a6+AeW&G6{te( zq{>aGuhB{a=8BbhXcj9iTq(mTSb)>82upAVo;#_a#`DOpzzc8|UWAuUO2~P+;GBb3 zP7K7aBCf)DV6Y4q;I$JId9N2TZ@`;MW~IPy71%29#R9*LxD4;0x0hfQ-c>d(Bd-SU z!4B5FHcre7W2f#K%FBIwd9sY>* z$sltFVZiz`fdL6v)y$0DlxJ1oM^56&tmL}^66bMNjg!7Sj(BDUj?c3>;PF7Xk#xf# ztM!MrGBBo7Y?;H~ex>nOdw5liC+*>P-_rP_E#`6$l|L7r@khFWWXcdb3I+3dJjzUB zj`vV&e1^!+M9O5uyvKt$UD^+vC?189c?g*k3_WgDL<=RvDIyC>HkLdRi-ea5&mV;Y z$FosoC`Vgq{V?XN<3%GU{qi6jq6y{@Qf>$y0H+>ca>UsniDl?NmBm%IvwLv+Cfh+5 z9);oo3AzDW*&iexrY9J#urH7uN6vsRW7(8vGf(PC%x$$pd?s^ zRmv*cot?wZJ^VUB;&|!BY5097=D@J37{81DNsV1mxb`B4AvwA1N5FF_a z(&atDFZm8|7IT?ItE>YLonU~55R>z1IAc%goBIk6-FjIwhEO3 z7nS6XWl=qxrM2zjJgUq*pcPXhEgIzacCphx?X*s-dE|8xQI0&_Q_7{8Pru|nl{S=~ zYV@q36jdkfSdFY^+tjqSoi%J5i<5XXpSSI=@Q~yyf;u-lW!p{=gc8q27{s8bal<5jxbVnNl-afJWPI1imtlV^fL&y%3i aLEF#rkk4+Ah1y!4< literal 0 HcmV?d00001 diff --git a/atelier/tools/brush_thumbnailer/__pycache__/ops.cpython-37.pyc b/atelier/tools/brush_thumbnailer/__pycache__/ops.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7eb2f1fd55f264fe37fc263f3ca1f22fbf154f4d GIT binary patch literal 5390 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 literal 0 HcmV?d00001 diff --git a/atelier/tools/brush_thumbnailer/__pycache__/ui.cpython-37.pyc b/atelier/tools/brush_thumbnailer/__pycache__/ui.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22514b34405a68ebe0af7c3468d5eb17cc4bed5b GIT binary patch literal 2329 zcmbVOOK;mo5awHyEGx3pJldv>`i}Z=g9hj$D4NQa?5aiNRFYeO5U^NjHZm28WS3MN z6Y{CK8t9>y=9Ju8pnsr0rF-qE|Dfopvm`AmuS;ogJ+m|O&CIvETy3tdmK1!hefv9k zP*#+`q;m2YD13=;RKo!jP(5X;I;yJdD4>CUUjdz|KkAMKMo+1mf1{UDSA}_t`pg?i z?c~u=_!8gfDh^C>R8TNVb#%}i19Zm(!?D103Sc=l6r3X1P`s}=B`6{Ciny@JXVHFt ze>NTZ)b$wO4P!U(Bd506=#g7}GL&`_pE{5r^GMdx(ArY0D16s$eNV16g|^ogMrXIv z5>{_C^TIf(b(s&0*S2O+987CjY=wLIfUwj!S*z81b)_(s3t>|B{BN7`JSs1xl- zpKA~GN6N$i6|`gZ$e0@w6Lc_+k-oX{(0Zh#{|k?lBNNQIN$j~fDb97UFronVv4M7d zQi9?g?I)H0j4{i2*4#q860AV^SVK>1vVw7|P&w9)5G_SI165dq^%Ti`C^Hohu{VDH(L zWfi>%Z{_`H3r?$LWr>rw7ga51dk3!kk8E{#_o-}aOS#@#cr*=BpjF{bv$GEZa`R9f(rU5Wv-K74EdUZ`$>|RKH;PMCvqp{cD zO-el$$F4t)YS)F`YxVo>&Q7ncrR+rZP+qu29tLx?g>Uuh}z9dyOw4kT-l~NVG zEv>5CC};Ckw3O7U_V^5XTN!$CDLo8J_hNp-ZOEcJc%y^2y4(h;7V!;pyzs+^6HxJ)mhezICCQl>E*2mCN~1eWoM z-uzSKBwe7T;S)Kh;~;;WL{%JY)mZ-1Q){QLi{dam+>jMSTwdDg{1TE}c`o6E{dv~i M7b SINGLE + use_tint : BoolProperty(default=False, name="Use Tint") + + mode : EnumProperty ( + items = ( + ('MANUAL', "Manual", "Snapshot based on the actual viewport view"), + ('AUTO', "Automatic", "Pre-made setup. Advanced settings") + ), default='MANUAL', + name="Mode", description="Snapshot mode" + ) + inverted : BoolProperty(default=False, description="Invert brush effect") + override_size : BoolProperty(default=False, description="Override Original Brush Size") + override_strength : BoolProperty(default=False, description="Override Original Brush Strength") + brush_size : IntProperty(default=32, min=1, max=500, name="Brush Size") + brush_strength : FloatProperty(default=.5, min=0.1, max=2, name="Brush Strength") + use_text : BoolProperty(default=False, name="Use Text") + text : StringProperty(default="My Brush", name="Text") + text_size : FloatProperty(default=.3, min=.1, max=1.5, name="Text Size") + text_color : FloatVectorProperty ( + size=4, default=(.9, .5, .2, 1), min=0, max=1, subtype='COLOR', + name="Text Color", description="New brush icon tint color for mesh" + ) diff --git a/atelier/tools/brush_thumbnailer/ops.py b/atelier/tools/brush_thumbnailer/ops.py new file mode 100644 index 0000000..4722d60 --- /dev/null +++ b/atelier/tools/brush_thumbnailer/ops.py @@ -0,0 +1,257 @@ +import bpy +from . presets import * +from math import radians +from ...utils.easing import * +from ...utils.others import blender_version +from ...utils.space_conversion import convert_3d_spaceCoords_to_2d_screenCoords +extended = blender_version()[1] >= 2.90 + + +class BAS_OT_brush_render_icon(bpy.types.Operator): + bl_idname = "bas.brush_render_icon" + bl_label = "Render Custom Brush Icon" + bl_description = "Create a Custom Icon for the Actual Brush based on the Viewport" + + @classmethod + def poll(cls, context): + return context.sculpt_object + + def sculpt_base_mesh(self, context, props): + lista = [] + n = len(sphere_standard_05) + size = props.brush_size + start = True + for i, p in enumerate(sphere_standard_05): + m = convert_3d_spaceCoords_to_2d_screenCoords(context, p) + d = { + 'name' : str(i), + 'is_start' : start, + 'location' : p, + 'mouse' : m, + 'mouse_event' : m, + 'pen_flip' : False, + 'pressure' : max(0.08, min(QuadEaseInOut().ease(i/n), 1)), + 'size' : size, + 'time' : 0.1 * i, + 'x_tilt' : 0.0, + 'y_tilt' : 0.0 + } + if extended: + d2 = { + 'mouse_event' : m, + 'x_tilt' : 0.0, + 'y_tilt' : 0.0 + } + d.update(d2) + start = False + lista.append(d) + bpy.ops.sculpt.brush_stroke(stroke=lista, mode='NORMAL' if not props.inverted else 'INVERT', ignore_background_click=False) + + def execute(self, context): + scene = context.scene + props = context.window_manager.bas_brush_thumbnailer + original_obj = target_obj = context.active_object + #scene.cursor.location = (0, 0, 0) + #scene.cursor.rotation_euler = (0, 0, 0) + + if props.mode == 'AUTO': + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.view3d.snap_cursor_to_center() + + if props.use_text and props.text != '': + text_obj = bpy.ops.object.text_add() + text_obj = context.active_object + # text_obj.location = [0, -2, 0] + text_obj.data.body = '' + else: + text_obj = None + + base_mesh = bpy.ops.mesh.primitive_ico_sphere_add ( + subdivisions=7, radius=1.0, calc_uvs=False, enter_editmode=False, + align='WORLD', location=(0.0, 0.0, 0.0), rotation=(0.0, 0.0, 0.0) + ) + target_obj = context.active_object + bpy.ops.object.shade_smooth() + if text_obj: + text_obj.select_set(True) + bpy.ops.view3d.localview() + bpy.ops.view3d.view_axis(type='FRONT', align_active=True) + bpy.ops.object.mode_set(mode='SCULPT') + tool_settings = context.tool_settings + sculpt = tool_settings.sculpt + ups = tool_settings.unified_paint_settings + if props.override_size: + if ups.use_unified_size: + size = ups.size + ups.size = props.brush_size + else: + size = sculpt.brush.size + sculpt.brush.size = props.brush_size + if props.override_strength: + if ups.use_unified_strength: + strength = ups.strength + ups.strength = props.brush_strength + else: + strength = sculpt.brush.strength + sculpt.brush.strength = props.brush_strength + x = sculpt.use_symmetry_x + y = sculpt.use_symmetry_y + z = sculpt.use_symmetry_z + sculpt.use_symmetry_x = False + sculpt.use_symmetry_y = False + sculpt.use_symmetry_z = False + self.sculpt_base_mesh(context, props) + if props.override_size: + if ups.use_unified_size: + ups.size = size + else: + sculpt.brush.size = size + if props.override_strength: + if ups.use_unified_strength: + ups.strength = strength + else: + sculpt.brush.strength = strength + sculpt.use_symmetry_x = x + sculpt.use_symmetry_y = y + sculpt.use_symmetry_z = z + context.active_object.data.use_auto_smooth = True + if text_obj: + text_obj.show_in_front = True + text_obj.color = props.text_color + text_obj.data.size = props.text_size + text_obj.data.body = props.text + text_obj.data.offset_x = -1 + text_obj.data.offset_y = -1 + text_obj.rotation_euler[0] = radians(90) + text_obj.data.space_character = 1.1 + # 0.5 -> 0.01 + # 0.2 -> x + text_obj.data.offset = 0.01 * props.text_size / 0.5 + text_obj.data.fill_mode = 'FRONT' + # text_obj.data.bevel_depth = 0.02 + # text_obj.data.render_resolution_u = 12 + + #bpy.ops.view3d.zoom(delta=2) + #bpy.ops.view3d.zoom(delta=2) + #bpy.ops.view3d.zoom(delta=2) + #bpy.ops.view3d.zoom(delta=1) + + # Link active object to the new collection + # C.scene.collection.objects.link(base_mesh) + + # And finally select it and make it active. + # base_mesh.select_set(True) + # view_layer.objects.active = base_mesh + target_obj.data.remesh_voxel_size *= 1 + else: + original_obj.data.remesh_voxel_size *= 1 + text_obj = None + + space = context.space_data + brush = context.tool_settings.sculpt.brush # Get active brush + brush.use_custom_icon = True # Mark to use custom icon + + # BACKUP DATA + overlays_state = context.space_data.overlay.show_overlays + gizmo_state = context.space_data.show_gizmo + resX = scene.render.resolution_x + resY = scene.render.resolution_y + displayMode = context.preferences.view.render_display_type + oldpath = scene.render.filepath + lens = context.space_data.lens + + withAlpha = props.use_alpha + withTint = props.use_tint + light = space.shading.light + bgColor = space.shading.background_color + shadingBgType = space.shading.background_type + film = scene.render.film_transparent + color_type = space.shading.color_type + obj_color = target_obj.color + show_cavity = space.shading.show_cavity + + # PRIMEROS PREPARATIVOS :) + context.preferences.view.render_display_type = 'NONE' + context.space_data.overlay.show_overlays = False + context.space_data.show_gizmo = False + scene.render.resolution_x = 128 + scene.render.resolution_y = 128 + context.space_data.lens = 100 # props.focal_length + space.shading.light = 'MATCAP' + space.shading.show_cavity = True + if withAlpha: + scene.render.film_transparent = True + else: + scene.render.film_transparent = False + space.shading.background_type = 'VIEWPORT' + space.shading.background_color = props.bg_color + if withTint or text_obj: + space.shading.color_type = 'VERTEX' # 'SINGLE' + # space.shading.single_color = props.tint + target_obj.color = props.tint + + from os.path import join + # temp_dir = bpy.app.tempdir # TEMPORAL FOLDER OF THE ACTUAL BLENDER PROJECT + from ... import root + icons_dir = join(root, "user_data", 'sculpt_brush_icons') + filename = brush.name + "_icon.png" + filepath = join(icons_dir, filename) + + # RENDER SETTINGS + scene.render.image_settings.file_format = 'PNG' + scene.render.filepath = filepath + bpy.ops.render.opengl(write_still=True) # RENDER + SAVE (In filepath as PNG) + + render_image = bpy.data.images["Render Result"] # GET RENDERED IMAGE + render_image.name = '.' + filename # CHANGE RENDERED IMAGE' NAME TO GENERATE FILENAME + bpy.ops.image.pack() # PACK IMAGE TO .BLEND FILE + + # ASIGN ICON (RENDER) TO BRUSH + bpy.data.brushes[brush.name].icon_filepath = filepath + + # RESTORE DATA + scene.render.resolution_x = resX + scene.render.resolution_y = resY + context.space_data.overlay.show_overlays = overlays_state + context.space_data.show_gizmo = gizmo_state + context.preferences.view.render_display_type = displayMode + scene.render.filepath = oldpath + context.space_data.lens = lens + scene.render.film_transparent = film + space.shading.show_cavity = show_cavity + space.shading.light = light + + if withAlpha == False: + space.shading.background_color = bgColor + space.shading.background_type = shadingBgType + if withTint or text_obj: + space.shading.color_type = color_type + # space.shading.single_color = (1, 1, 1) + target_obj.color = obj_color + try: + bpy.data.images.remove(bpy.data.images[filename + ".001"]) + except: + pass + + # PREPARE NEW RENDER IMAGE SLOT FOR ANOTHER ICON + bpy.ops.image.new(name="Render Result") + + if props.mode == 'AUTO': + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.view3d.localview() + bpy.data.objects.remove(context.active_object) + + if props.use_text and props.text != '' and text_obj: + bpy.data.objects.remove(text_obj) + + original_obj.select_set(True) + context.view_layer.objects.active = original_obj + bpy.ops.object.mode_set(mode='SCULPT') + + context.area.tag_redraw() + return {'FINISHED'} + + +classes = ( + BAS_OT_brush_render_icon, +) diff --git a/atelier/tools/brush_thumbnailer/presets.py b/atelier/tools/brush_thumbnailer/presets.py new file mode 100644 index 0000000..f96c049 --- /dev/null +++ b/atelier/tools/brush_thumbnailer/presets.py @@ -0,0 +1,5 @@ +from mathutils import Vector + + +sphere_standard_05 = [Vector((-0.8223704099655151, -0.4357900619506836, -0.38016390800476074)), Vector((-0.7872512340545654, -0.4527812600135803, -0.3795601427555084)), Vector((-0.7571473121643066, -0.4705839157104492, -0.3786749541759491)), Vector((-0.7315515279769897, -0.4890754818916321, -0.3775286078453064)), Vector((-0.7099559307098389, -0.5081329941749573, -0.37614113092422485)), Vector((-0.6918535232543945, -0.5276340246200562, -0.3745327591896057)), Vector((-0.6767364740371704, -0.5474555492401123, -0.37272369861602783)), Vector((-0.6640974283218384, -0.5674751400947571, -0.37073397636413574)), Vector((-0.6534291505813599, -0.5875698924064636, -0.36858394742012024)), Vector((-0.6442235708236694, -0.6076170802116394, -0.3662935793399811)), Vector((-0.6359735727310181, -0.6274940967559814, -0.36388319730758667)), Vector((-0.6281716823577881, -0.6470780968666077, -0.36137285828590393)), Vector((-0.6203101873397827, -0.6662465333938599, -0.3587827980518341)), Vector((-0.6118819713592529, -0.6848764419555664, -0.3561331033706665)), Vector((-0.602379322052002, -0.7028453350067139, -0.35344401001930237)), Vector((-0.5912946462631226, -0.7200303077697754, -0.3507356345653534)), Vector((-0.5781208276748657, -0.736308753490448, -0.34802818298339844)), Vector((-0.562349796295166, -0.7515578866004944, -0.3453417718410492)), Vector((-0.5434747934341431, -0.7656550407409668, -0.3426966071128845)), Vector((-0.5209876298904419, -0.7784774303436279, -0.3401128053665161)), Vector((-0.5209876298904419, -0.7784774303436279, -0.3401128053665161)), Vector((-0.4956172704696655, -0.7910780906677246, -0.33728083968162537)), Vector((-0.4685702323913574, -0.8044676184654236, -0.33388882875442505)), Vector((-0.4400578737258911, -0.8184608221054077, -0.32995349168777466)), Vector((-0.41029036045074463, -0.8328717947006226, -0.32549136877059937)), Vector((-0.3794788122177124, -0.8475151658058167, -0.32051917910575867)), Vector((-0.3478337526321411, -0.8622052073478699, -0.3150535821914673)), Vector((-0.3155655860900879, -0.8767563700675964, -0.30911120772361755)), Vector((-0.28288567066192627, -0.8909831643104553, -0.30270877480506897)), Vector((-0.2500041723251343, -0.9046996831893921, -0.2958628833293915)), Vector((-0.2171318531036377, -0.9177207350730896, -0.2885902225971222)), Vector((-0.18447959423065186, -0.9298604726791382, -0.28090745210647583)), Vector((-0.15225815773010254, -0.940933346748352, -0.2728312313556671)), Vector((-0.12067794799804688, -0.9507538080215454, -0.2643781900405884)), Vector((-0.08994996547698975, -0.9591362476348877, -0.25556501746177673)), Vector((-0.06028449535369873, -0.9658949375152588, -0.2464083880186081)), Vector((-0.03189265727996826, -0.9708445072174072, -0.2369249016046524)), + Vector((-0.0049849748611450195, -0.9737992286682129, -0.22713126242160797)), Vector((0.02022773027420044, -0.97457355260849, -0.21704412996768951)), Vector((0.043534934520721436, -0.9729818105697632, -0.20668013393878937)), Vector((0.043534934520721436, -0.9729818105697632, -0.20668013393878937)), Vector((0.0661504864692688, -0.9701900482177734, -0.19579355418682098)), Vector((0.08937180042266846, -0.9674390554428101, -0.18416373431682587)), Vector((0.11311334371566772, -0.964655876159668, -0.1718449890613556)), Vector((0.13728970289230347, -0.961767315864563, -0.15889157354831696)), Vector((0.16181522607803345, -0.9587004780769348, -0.1453578919172287)), Vector((0.1866043210029602, -0.9553821086883545, -0.13129816949367523)), Vector((0.21157169342041016, -0.9517391920089722, -0.11676676571369171)), Vector((0.23663169145584106, -0.9476988315582275, -0.10181795060634613)), Vector((0.26169878244400024, -0.9431876540184021, -0.08650608360767365)), Vector((0.286687433719635, -0.9381328821182251, -0.07088544964790344)), Vector((0.3115121126174927, -0.9324612617492676, -0.0550103560090065)), Vector((0.3360874056816101, -0.9260997176170349, -0.0389351025223732)), Vector((0.36032766103744507, -0.9189752340316772, -0.022714020684361458)), Vector((0.38414740562438965, -0.9110147953033447, -0.006401406601071358)), Vector((0.40746110677719116, -0.9021451473236084, 0.00994841754436493)), Vector((0.4301832914352417, -0.8922934532165527, 0.02628115378320217)), Vector((0.452228307723999, -0.8813865184783936, 0.04254249110817909)), Vector((0.4735108017921448, -0.8693512678146362, 0.05867811292409897))] diff --git a/atelier/tools/brush_thumbnailer/ui.py b/atelier/tools/brush_thumbnailer/ui.py new file mode 100644 index 0000000..5251b4e --- /dev/null +++ b/atelier/tools/brush_thumbnailer/ui.py @@ -0,0 +1,84 @@ +from bpy.types import Panel + + +class BrushThumbnailerOptions(Panel): + bl_idname = "BAS_PT_brush_thumbnailer_options" + bl_label = "Brush Thumbnailer" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = "NONE" + bl_category = 'Sculpt' + bl_description = "Render Custom Brush Icon Options" + + # BRUSH OPTIONS + def draw(self, context): + col = self.layout + props = context.window_manager.bas_brush_thumbnailer + block = col.box().column(align=True) + block.label(text="Custom Brush Icon") + block.separator() + row = block.row() + row.scale_y = 1.5 + row.prop(props, 'mode', expand=True, text="Manual") + + if props.mode == 'AUTO': + settings = block.box().column(align=True) + settings.label(text="Settings :", icon='SETTINGS') + + row = settings.split(align=True, factor=0.1) + row.prop(props, 'override_size', text="", toggle=False) # Override + ppt = row.row() + ppt.enabled = props.override_size + ppt.prop(props, 'brush_size', text="Brush Size", slider=True) + + row = settings.split(align=True, factor=0.1) + row.prop(props, 'override_strength', text="", toggle=False) # Override + ppt = row.row() + ppt.enabled = props.override_strength + ppt.prop(props, 'brush_strength', text="Brush Strength", slider=True) + + settings.prop(props, 'inverted', text="Invert Brush Direction") + + settings.prop(props, 'use_text', text="Use Text") + row = settings.split(align=True, factor=0.1) + row.label(text='') # Dummy + col = row.column(align=True) + col.enabled = props.use_text + col.prop(props, 'text', text="") + row = col.split(align=True, factor=0.4) + row.prop(props, 'text_color', text="") + row.prop(props, 'text_size', text="Size", slider=True) + + _col = settings + else: + _col = block.box().column(align=True) + + # 4TH ROW + _col.separator() + _col.label(text="Background Color :", icon='IMAGE_BACKGROUND') + row = _col.split(align=True, factor=0.1) + row.label(text='') # Dummy + col = row.column(align=True) + col.prop(props, 'use_alpha', text="Use Alpha") + ppt = col.row() + ppt.enabled = not props.use_alpha + ppt.prop(props, 'bg_color', text="") + + _col.separator() + _col.label(text="Mesh Color :", icon='SHADING_RENDERED') + row = _col.split(align=True, factor=0.1) + row.label(text='') # Dummy + col = row.column(align=True) + col.prop(props, 'use_tint', text="Use Tint") + ppt = col.row() + ppt.enabled = props.use_tint + ppt.prop(props, 'tint', text="") + + #row = _col.row(align=True) + #row.scale_y = 1.2 + #row.prop(props, "focal_length", text="Focal Length", slider=True) + + block.separator() + row = block.row() + row.scale_y = 1.5 + row.operator("bas.brush_render_icon", text="Render Custom Brush Icon", icon='RESTRICT_RENDER_OFF') diff --git a/atelier/tools/dyntopo_pro/__init__.py b/atelier/tools/dyntopo_pro/__init__.py new file mode 100644 index 0000000..2b3aed1 --- /dev/null +++ b/atelier/tools/dyntopo_pro/__init__.py @@ -0,0 +1,31 @@ +def register(): + from .ops import classes + from bpy.utils import register_class + + from .ui import BAS_PT_dyntopo_stages + register_class(BAS_PT_dyntopo_stages) + + for cls in classes: + register_class(cls) + + from .data import DyntopoProPG + register_class(DyntopoProPG) + + from bpy.types import Scene as scn + from bpy.props import PointerProperty as Pointer + scn.bas_dyntopo = Pointer(type=DyntopoProPG) + +def unregister(): + from bpy.utils import unregister_class + from bpy.types import Scene as scn + del scn.bas_dyntopo + + from .data import DyntopoProPG + unregister_class(DyntopoProPG) + + from .ui import BAS_PT_dyntopo_stages + unregister_class(BAS_PT_dyntopo_stages) + + from .ops import classes + for cls in reversed(classes): + unregister_class(cls) diff --git a/atelier/tools/dyntopo_pro/__pycache__/__init__.cpython-37.pyc b/atelier/tools/dyntopo_pro/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fcb6dc1cf13f83260da703fee59bda1cc1c6a8d9 GIT binary patch literal 1053 zcmY*YOK;Oa5Z?7Wi4$7X^0;yJB|_y?gb;0`B7qbI0z#^cjO$%Rmevn;H&r9qT;R$N zLE;zX%Bg>W6Eo{Nz)GH-*M2)Y^Z9f<9uinj-uz5n+l2f=qhA&%FW^%LAQ)lv8@V9N zVm9oQIZG0`8AQzTLBe@*2A}!@f>CA(QqY!A!ffUUtFT)NI%TeK3b(Z=X=r181>f~S zLuTY-*xKN+(ewG}17>={EBw|mnGX5^$rnK}XkDWRVpxn?58__qu)%lQxJk$eR5qO7 zP^coJTR#)2Qe54DiS|VDRjxFbi3xz`@bqMvEZ!w-Q|hX&5~b4>R}ctioo6Ad#Y?c= zWyVVm%AG}(mjD5QnoGR_m!mf|kH%ZOs+GjfZ3mb2X1dn7P|`u$TIbS5#bK#VrMJt$ zlS*O-yE8SQDjA?Xx=dAHSL7R91J(iA8DJY6TV>_ezpd&AkvOs6S7%S-_lipuzdDak z>Usv(#&4^%$jeoHDtO7bd~{x|OQxbGL#8z)I#<~m8H^=2FN9mWZ$dmso6X=sHsu!grg*i>R@#~^&ijzJ|D|1pR% zyJt{CKRL_+7P*CISVTG&fzK0u5wte&fpk0qpDzZ*5IJ=HK#v+=kztw(8Lna41uzX6 z6U-%E52M^$m;DQRj+p7lj!~xL5j7)#4QCyNas->ak79xXrz!VPj8Wj@WQYP8An~8; zNCLgOf(daJdEM!|7&7T56Vc^93LI%C4nf-v{}zcy%s*<{OdZo`Y<5T|R6d00JhDu) YjqwkX!8CBev!Y^a!JnI%;jO{`A4zxUj{pDw literal 0 HcmV?d00001 diff --git a/atelier/tools/dyntopo_pro/__pycache__/data.cpython-37.pyc b/atelier/tools/dyntopo_pro/__pycache__/data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b1d69e67a3761fe171ea60521a932c3f1069d03 GIT binary patch literal 1657 zcmZ`(OK%%D5Z;#_wx!rn(#C1hfR_MWpb?t5Xi>u`;&;=gu4~yjC>s!KaU`v?yWEmo zITCVebM+4?(0|h#uRZxM^wc5OGK`|7L@xP}GxN=GI9u)ZqJwSi=RfG5HOKkOEaol1 z$#d-TOB|TP+|PJzpiB#;JJms|rUlRp)R?o- z(}9F^s2c;GD|n}Vcf>8U^|#nwk4_{A8GXMOZ5G8&tm5d1#~Esjwo=F$2(rh^oW&~P z`5cO#b-(!{k^_+xN=7kbJeSd?f;0iqvn(RRO-8J8nHGXas3PtbQ@vUiI0ZW2yY|au zm$Y$lXOC|NtNVWvp^xd&JlcJ^dHmwlF3uoDo)gfH!MA@p=pAn!9ODf6sW?~S4w}HN z+SbuY?>qd6QjRSsZsX2uy?36d!I+$aItR$fH8oPjk(3e`8FE38JO*h%%!Q_WH0_3g zt|baGsT&N2ahWPzuW0Kf234pqf}&v=AP4uUuJ_&onwve|ON3OUr{WQ;lc8P6aa@2T zan8tur)37x$UN&ec5E8R*`prdWwN={slY44{}pF@daYkS?p!vMaZ(I^F4vD=$ zSyC!Y$rf10t5RU=K{YHJ%5_msb|r=25NxeQ5Z@aSB4$V1}$j@R4mh6Uq?-~Xn6c$W1K8s%_7 zS%s-yfnXLR!kUnP5Y%m9Pn^I3-C?e9CzYT|tY0lwVbu$ZRXO?732I>1mJj7ZW?V)(m7q6w68%c1;LxX+P7!ZXnnk1I=q;yoggNE7s&5rM z+zJS@0(&3o3>;<$E^~qkuQC^F`e^eSb5|^|p`wojton+@8mxI?1s-cbJ#F)NeQij0 z_b59}beg4fbQC3U=t(4|{A}qUQvJIk#yWfQ2ABAUsq{CCTz`}6Xe|8U_>7xMzQbh& zs_fauO`-`mHO3F4snC}My+k(QW2!9A8rfjrz%R}Xm?{QwZpGF)iAiiz2XvQ_V@I;w zQ;Dl_joHVxe2J0LN4=z9>JWDz{^nM^o^)>64dxyZc#g_IVrj4fW}l$l4PG*LQAG@S({SE-olahjOU%momEW3_3{Yy~fkudBE?G&R^j z^Y{8MOMVV#|NCy^r*L0!slwm3!nG{hjC2&fPNNBw60QrLFfRSAbegaT4=G*Y_x&GV zglZ(mnO0%M7?c&RX)eZG4)gLuyp#${Tx2p0(@gcVQ&Z#b`Dm(n2hLYr5Eg0K4bm-s zc88oj`vlzjv+O~yZahj6GL)JIrIVCR1?nwI-%TSii_{MU9kU!+9;hNZ;KFo3W4tII zZY7aVW{%P*Nm89(R8cCw0B`)i6qT{90WmWUiVhXUc#>&kCYe}G~hglXsZRWusu@d@xRll+iNo{TuK zs{pHMO`>s9wokc$xr-=1!wnvmu$^sGvQ9q4=)(V^2Xd;8%jVuVLy3*=Zl!ladIQ!Um>is@NS-D6zTA?@%CR m@?7XbB2o&{u5X@eczxro6jc5hyKGLe7Q8{r=`MB`ZSo&B|Mm6& literal 0 HcmV?d00001 diff --git a/atelier/tools/dyntopo_pro/__pycache__/prefs.cpython-37.pyc b/atelier/tools/dyntopo_pro/__pycache__/prefs.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..620e6179d721863424c6805dd5796aa0f230c295 GIT binary patch literal 4477 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 literal 0 HcmV?d00001 diff --git a/atelier/tools/dyntopo_pro/data.py b/atelier/tools/dyntopo_pro/data.py new file mode 100644 index 0000000..29f0825 --- /dev/null +++ b/atelier/tools/dyntopo_pro/data.py @@ -0,0 +1,33 @@ +from bpy.types import PropertyGroup +from bpy.props import ( + EnumProperty, BoolProperty, IntProperty +) + + +class DyntopoProPG(PropertyGroup): + #________________# + # DYNTOPO # + def update_dyntopo_detailing(self, context): + context.scene.tool_settings.sculpt.detail_type_method = self.detailing + detailing : EnumProperty( + items=( + ('RELATIVE', "Relative", ""), + ('CONSTANT', "Constant", ""), + ('BRUSH', "Brush", "") + ), + default='RELATIVE', + update=update_dyntopo_detailing, + description="Switch between detailing method used for dynamic topology." + ) + stage : EnumProperty( + items=( + ('1', "Sketch", "First Stage: first shapes and volumes"), + ('2', "Details", "Second Stage: bringing details"), + ('3', "Polish", "Third Stage: polish and closing up more detailed shapes") + ), default='1', + description="Switch between Stage. Stages improve and divide the workflow in 3 stages: Sketch, Details and Polish." + ) + toggle_stages : BoolProperty(default=True, description="Switch between Stage Mode (per Stages) and Default Mode (per Levels [1-6]).") + stages_edit_values : BoolProperty(default=False, name="Edit Custom Values", description="Show custom values to be able of editing them") + levels_active : IntProperty(default=0, min=1, max=6) + detail_level : IntProperty(default=1, min=1, max=3) diff --git a/atelier/tools/dyntopo_pro/ops.py b/atelier/tools/dyntopo_pro/ops.py new file mode 100644 index 0000000..5b97581 --- /dev/null +++ b/atelier/tools/dyntopo_pro/ops.py @@ -0,0 +1,50 @@ +from bpy.types import Operator +from bpy.props import IntProperty +from ... import __package__ as main_package + + +class BAS_OT_dyntopo_change_value(Operator): + bl_idname = "bas.dyntopo_change_value" + bl_label = "" + bl_description = "Value for Dyntopo Detail Size" + + value: IntProperty(name="value", default=5) + detail : IntProperty(default=1) # low, mid, high (as numbers 1-3) + + def execute(self, context): + context.scene.bas_dyntopo.detail_level = self.detail + + if(self.value != 0): + if context.scene.tool_settings.sculpt.detail_type_method == 'CONSTANT': + context.scene.tool_settings.sculpt.constant_detail_resolution = self.value + elif context.scene.tool_settings.sculpt.detail_type_method == 'BRUSH': + context.scene.tool_settings.sculpt.detail_percent = self.value + else: # bpy.context.scene.tool_settings.sculpt.detail_type_method = 'MANUAL' // 'RELATIVE' + context.scene.tool_settings.sculpt.detail_size = self.value + return {'FINISHED'} + + +class BAS_OT_dyntopo_change_level(Operator): + bl_idname = "bas.dyntopo_change_level" + bl_label = "" + bl_description = "The more level the more detail !" + + lvl : IntProperty(default=1) # low, mid, high (as numbers 1-3) + + def execute(self, context): + context.scene.bas_dyntopo.levels_active = self.lvl + prefs = context.preferences.addons[main_package].preferences + lvl = self.lvl - 1 + if context.scene.tool_settings.sculpt.detail_type_method == 'CONSTANT': + context.scene.tool_settings.sculpt.constant_detail_resolution = prefs.dyntopo_levels_constant[lvl] + elif context.scene.tool_settings.sculpt.detail_type_method == 'BRUSH': + context.scene.tool_settings.sculpt.detail_percent = prefs.dyntopo_levels_brush[lvl] + else: # 'MANUAL' // 'RELATIVE' + context.scene.tool_settings.sculpt.detail_size = prefs.dyntopo_levels_relative[lvl] + return {'FINISHED'} + + +classes = ( + BAS_OT_dyntopo_change_level, + BAS_OT_dyntopo_change_value +) diff --git a/atelier/tools/dyntopo_pro/prefs.py b/atelier/tools/dyntopo_pro/prefs.py new file mode 100644 index 0000000..a2c0543 --- /dev/null +++ b/atelier/tools/dyntopo_pro/prefs.py @@ -0,0 +1,283 @@ +from .ui import dyntopoStages + + +# Relative +def update_relativeLow(self, context): + dyntopoStages[0].relative_Values = self.dyntopo_relative_low +def update_relativeMid(self, context): + dyntopoStages[1].relative_Values = self.dyntopo_relative_mid +def update_relativeHigh(self, context): + dyntopoStages[2].relative_Values = self.dyntopo_relative_high +# Constant +def update_constantLow(self, context): + dyntopoStages[0].constant_Values = self.dyntopo_constant_low +def update_constantMid(self, context): + dyntopoStages[1].constant_Values = self.dyntopo_constant_mid +def update_constantHigh(self, context): + dyntopoStages[2].constant_Values = self.dyntopo_constant_high +# Brush +def update_brushLow(self, context): + dyntopoStages[0].brush_Values = self.dyntopo_brush_low +def update_brushMid(self, context): + dyntopoStages[1].brush_Values = self.dyntopo_brush_mid +def update_brushHigh(self, context): + dyntopoStages[2].brush_Values = self.dyntopo_brush_high + +''' +dyntopo_properties = { + # DYNTOPO LEVELS + 'dyntopo_levels_relative' : ( + 'FloatVectorProperty', + { + 'name' : "Dyntopo Relative Levels", + 'description' : "", + 'subtype' : 'NONE', + 'default' : [12, 9, 6, 4, 2, 1], + 'size' : 6, + 'step' : 1 + } + ), + 'dyntopo_levels_constant' : ( + 'FloatVectorProperty', + { + 'name' : "Dyntopo Constant Levels", + 'description' : "", + 'subtype' : 'NONE', + 'default' : [35, 50, 65, 80, 100, 125], + 'size' : 6, + 'step' : 1 + } + ), + 'dyntopo_levels_brush' : ( + 'FloatVectorProperty', + { + 'name' : "Dyntopo Brush Levels", + 'description' : "", + 'subtype' : 'NONE', + 'default' : [48, 32, 24, 16, 10, 5], + 'size' : 6, + 'step' : 1 + } + ), + # USE CUSTOM DYNTOPO VALUES + 'dyntopo_use_custom_values': ( + 'BoolProperty', + { + 'name' : "Custom Values", + 'description' : "Use Custom Values for Dyntopo's new system by levels and stages", + 'default' : False + } + ), + # RELATIVE VALUES + 'dyntopo_relative_low' : ( + 'FloatVectorProperty', + { + 'name' : "Relative Low Value", + 'description' : "", + 'subtype' : 'NONE', + 'default' : [14, 12, 10], + 'soft_min' : 10, + 'soft_max' : 20, + 'size' : 3, + 'step' : 1, + 'precision' : 0, + 'update' : 'update_relativeLow' + } + ), + 'dyntopo_relative_mid' : ( + 'FloatVectorProperty', + { + 'name' : "Relative Mid Value", + 'description' : "", + 'subtype' : 'NONE', + 'default' : [8, 6, 4], + 'soft_min' : 4, + 'soft_max' : 10, + 'size' : 3, + 'step' : 1, + 'precision' : 0, + 'update' : 'update_relativeMid' + } + ), + 'dyntopo_relative_high' : ( + 'FloatVectorProperty', + { + 'name' : "Relative Mid Value", + 'description' : "", + 'subtype' : 'NONE', + 'default' : [3, 2, 1], + 'soft_min' : 0.1, + 'soft_max' : 4, + 'size' : 3, + 'step' : 1, + 'precision' : 0, + 'update' : 'update_relativeHigh' + } + ), + # CONSTANT VALUES + 'dyntopo_constant_low' : ( + 'FloatVectorProperty', + { + 'name' : "Constant Low Value", + 'description' : "", + 'subtype' : 'NONE', + 'default' : [20, 30, 40], + 'soft_min' : 0.1, + 'soft_max' : 50, + 'size' : 3, + 'step' : 1, + 'precision' : 0, + 'update' : 'update_constantLow' + } + ), + 'dyntopo_constant_mid' : ( + 'FloatVectorProperty', + { + 'name' : "Constant Mid Value", + 'description' : "", + 'subtype' : 'NONE', + 'default' : [55, 65, 75], + 'soft_min' : 50, + 'soft_max' : 95, + 'size' : 3, + 'step' : 1, + 'precision' : 0, + 'update' : 'update_constantMid' + } + ), + 'dyntopo_constant_high' : ( + 'FloatVectorProperty', + { + 'name' : "Constant High Value", + 'description' : "", + 'subtype' : 'NONE', + 'default' : [95, 110, 125], + 'soft_min' : 95, + 'soft_max' : 200, + 'size' : 3, + 'step' : 1, + 'precision' : 0, + 'update' : 'update_constantHigh' + } + ), + # BRUSH VALUES + 'dyntopo_brush_low' : ( + 'FloatVectorProperty', + { + 'name' : "Brush Low Value", + 'description' : "", + 'subtype' : 'NONE', + 'default' : [65, 55, 45], + 'soft_min' : 50, + 'soft_max' : 100, + 'size' : 3, + 'step' : 1, + 'precision' : 0, + 'update' : 'update_brushLow' + } + ), + 'dyntopo_brush_mid' : ( + 'FloatVectorProperty', + { + 'name' : "Brush Mid Value", + 'description' : "", + 'subtype' : 'NONE', + 'default' : [35, 27, 20], + 'soft_min' : 20, + 'soft_max' : 50, + 'size' : 3, + 'step' : 1, + 'precision' : 0, + 'update' : 'update_brushMid' + } + ), + 'dyntopo_brush_high' : ( + 'FloatVectorProperty', + { + 'name' : "Brush High Value", + 'description' : "", + 'subtype' : 'NONE', + 'default' : [15, 10, 5], + 'soft_min' : 0.1, + 'soft_max' : 20, + 'size' : 3, + 'step' : 1, + 'precision' : 0, + 'update' : 'update_brushHigh' + } + ) +} +''' + +dyntopo_properties = ''' +# DYNTOPO LEVELS +dyntopo_levels_relative : FloatVectorProperty( + name="Dyntopo Relative Levels", description="", + subtype='NONE', default=[12, 9, 6, 4, 2, 1], + size=6, step=1 +) +dyntopo_levels_constant : FloatVectorProperty( + name="Dyntopo Constant Levels", description="", + subtype='NONE', default=[35, 50, 65, 80, 100, 125], + size=6, step=1 +) +dyntopo_levels_brush : FloatVectorProperty( + name="Dyntopo Brush Levels", description="", + subtype='NONE', default=[48, 32, 24, 16, 10, 5], + size=6, step=1 +) +# USE CUSTOM DYNTOPO VALUES +dyntopo_use_custom_values: BoolProperty( + name="Custom Values", + description="Use Custom Values for Dyntopo's new system by levels and stages", + default=False, +) +# RELATIVE VALUES +dyntopo_relative_low : FloatVectorProperty( + name="Relative Low Value", description="", + subtype='NONE', default=[14, 12, 10], soft_min=10, soft_max=20, + size=3, step=1, precision=0 ,update=update_relativeLow +) +dyntopo_relative_mid : FloatVectorProperty( + name="Relative Mid Value", description="", + subtype='NONE', default=[8, 6, 4], soft_min=4, soft_max=10, + size=3, step=1, precision=0 ,update=update_relativeMid +) +dyntopo_relative_high : FloatVectorProperty( + name="Relative High Value", description="", + subtype='NONE', default=[3, 2, 1], soft_min=0.1, soft_max=4, + size=3, precision=1 ,update=update_relativeHigh +) +# CONSTANT VALUES +dyntopo_constant_low : FloatVectorProperty( + name="Constant Low Value", description="", + subtype='NONE', default=[20, 30, 40], soft_min=0.1, soft_max=50, + size=3, precision=1 ,update=update_constantLow +) +dyntopo_constant_mid : FloatVectorProperty( + name="Constant Mid Value", description="", + subtype='NONE', default=[55, 65, 75], soft_min=50, soft_max=95, + size=3, step=1, precision=0 ,update=update_constantMid +) +dyntopo_constant_high : FloatVectorProperty( + name="Constant High Value", description="", + subtype='NONE', default=[95, 110, 125], soft_min=95, soft_max=200, + size=3, step=1, precision=0 ,update=update_constantHigh +) +# BRUSH VALUES +dyntopo_brush_low : FloatVectorProperty( + name="Brush Low Value", description="", + subtype='NONE', default=[65, 55, 45], soft_min=50, soft_max=100, + size=3, step=1, precision=0 ,update=update_brushLow +) +dyntopo_brush_mid : FloatVectorProperty( + name="Brush Mid Value", description="", + subtype='NONE', default=[35, 27, 20], soft_min=20, soft_max=50, + size=3, step=1, precision=0 ,update=update_brushMid +) +dyntopo_brush_high : FloatVectorProperty( + name="Brush High Value", description="", + subtype='NONE', default=[15, 10, 5], soft_min=0.1, soft_max=20, + size=3, precision=1 ,update=update_brushHigh +) +''' diff --git a/atelier/tools/dyntopo_pro/ui.py b/atelier/tools/dyntopo_pro/ui.py new file mode 100644 index 0000000..36cea12 --- /dev/null +++ b/atelier/tools/dyntopo_pro/ui.py @@ -0,0 +1,207 @@ +from bpy import context as C +from bpy.props import FloatVectorProperty +from bpy.types import Panel +from ...icons import Icon +from ... import __package__ as main_package + +# ----------------------------------------------------------------- # +# DYNTOPO SETUP # +# ----------------------------------------------------------------- # + +# Values for Detail Size depending of the METHOD used +# LEFT (LOW) - CENTER (MID) - RIGHT (HIGH) +# RELATIVE & MANUAL --> a menor valor, mayor detalle. Valor en px. +relative_Low = [14, 12, 10] +relative_Mid = [ 8, 6, 4] +relative_High = [ 3, 2, 1] +# CONSTANT --> a mayor valor, mayor detalle. Valor fixed. (Aquí los valores están invertidos) +constant_Low = [20, 30, 40] +constant_Mid = [55, 65, 75] +constant_High = [95, 110, 125] +# BRUSH --> a menor, mayor detalle. Valor en % de detalle. +brush_Low = [65, 55, 45] +brush_Mid = [35, 27, 20] +brush_High = [15, 10, 5] +# LEVEL OF DETAIL GROUPS FOR EACH STAGE # DE MOMENTO AHÍ SE QUEDA AUNQUE SE PODRÍA USAR (para array 3 dimensiones) +# sketch_Values = [relative_Low, constant_Low, brush_Low] +# detail_Values = [relative_Mid, constant_Mid, brush_Mid] +# polish_Values = [relative_High, constant_High, brush_High] +# STRUCT CLASS +class DyntopoStage: + # BRUSH VALUES + relative_Values : FloatVectorProperty( + subtype='NONE', default=[0, 0, 0], + size=3, + ) + constant_Values : FloatVectorProperty( + subtype='NONE', default=[0, 0, 0], + size=3, + ) + brush_Values : FloatVectorProperty( + subtype='NONE', default=[0, 0, 0], + size=3, + ) + + def __init__(self, stage_Name, relative_Values = [], constant_Values =[], brush_Values = []): + self.stage_Name = stage_Name + self.relative_Values = relative_Values + self.constant_Values = constant_Values + self.brush_Values = brush_Values + + def __repr__(self): + return "DyntopoStage[%s, %i[], %i[], %i[]]" % (self.stage_Name, self.relative_Values, self.constant_Values, self.brush_Values) + +# IN LOAD / IF USE CUSTOM VALUES IS CHECKED, GO CREATE DYNSTAGES VALUES WITH PREFS VALUES +try: + prefs = C.preferences.addons[main_package].preferences + if prefs.dyntopo_use_custom_values: + rL = [prefs.relative_Low[0], prefs.relative_Low[1], prefs.relative_Low[2]] + rM = [prefs.relative_Mid[0], prefs.relative_Mid[1], prefs.relative_Mid[2]] + rH = [prefs.relative_High[0], prefs.relative_High[1], prefs.relative_High[2]] + cL = [prefs.constant_Low[0], prefs.constant_Low[1], prefs.constant_Low[2]] + cM = [prefs.constant_Mid[0], prefs.constant_Mid[1], prefs.constant_Mid[2]] + cH = [prefs.constant_High[0], prefs.constant_High[1], prefs.constant_High[2]] + bL = [prefs.brush_Low[0], prefs.brush_Low[1], prefs.brush_Low[2]] + bM = [prefs.brush_Mid[0], prefs.brush_Mid[1], prefs.brush_Mid[2]] + bH = [prefs.brush_High[0], prefs.brush_High[1], prefs.brush_High[2]] + dynStage_Low = DyntopoStage("SKETCH", rL, cL, bL) + dynStage_Mid = DyntopoStage("DETAILS", rM, cM, bM) + dynStage_High = DyntopoStage("POLISH", rH, cH, bH) + # IF NOT, GO CREATE DYNSTAGES VALUES WITH DEFAULT VALUES + else: + dynStage_Low = DyntopoStage("SKETCH", relative_Low, constant_Low, brush_Low) + dynStage_Mid = DyntopoStage("DETAILS", relative_Mid, constant_Mid, brush_Mid) + dynStage_High = DyntopoStage("POLISH", relative_High, constant_High, brush_High) +except: + dynStage_Low = DyntopoStage("SKETCH", relative_Low, constant_Low, brush_Low) + dynStage_Mid = DyntopoStage("DETAILS", relative_Mid, constant_Mid, brush_Mid) + dynStage_High = DyntopoStage("POLISH", relative_High, constant_High, brush_High) + +dyntopoStages = [dynStage_Low, dynStage_Mid, dynStage_High] + +def dynStage_toString(_dynStage): + s_dynStage = "" + if _dynStage == '1': + s_dynStage = "SKETCH" + elif _dynStage == '2': + s_dynStage = "DETAIL" + elif _dynStage == '3': + s_dynStage = "POLISH" + return s_dynStage + +# --------------------------------------------- # +# DYNTOPO STAGES UI PANEL +# --------------------------------------------- # +class BAS_PT_dyntopo_stages(Panel): + bl_label = "DyntopoStages" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = "NONE" + bl_category = 'Sculpt' + bl_description = "Stages Panel. Stages improve and divide the workflow in 3 stages and each one has 3 nice values to work with. (also depending of the detailing method)" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + dyntopo = context.scene.bas_dyntopo + dyn_stage = dyntopo.stage + use_stage = dyntopo.toggle_stages + if(context.sculpt_object.use_dynamic_topology_sculpting == True): + method = dyntopo.detailing + icon_H = Icon.DYNTOPO_HIGH() + icon_M = Icon.DYNTOPO_MEDIUM() + icon_L = Icon.DYNTOPO_LOW() + wm = context.window_manager + # STAGES - SKETCH - DETAIL - POLISH + layout = self.layout + row = layout.row(align=True) + #row.label(text="Stage : ") + if use_stage: + row.label(text="DEFAULT MODE") + row = layout.row(align=True) + row.prop(dyntopo, 'toggle_stages', text="USE STAGES !", toggle=True, invert_checkbox=True) + + else: + row.label(text="Actual Stage : " + dynStage_toString(dyn_stage)) + row.prop(dyntopo, 'toggle_stages', text="", icon='LOOP_BACK', toggle=True, expand=True) + # STAGES + col = layout.column() + row = col.row(align=True) + row.prop(dyntopo, 'stage', text="Sketch", toggle=True, expand=True) + + # DETAIL METHODS + col = layout.column() + row = col.row(align=True) + if method == 'CONSTANT': + icon = Icon.DYNTOPO_CONSTANT() + elif method == 'BRUSH': + icon = Icon.DYNTOPO_BRUSH() + elif method == 'RELATIVE': # RELATIVE OR MANUAL + icon = Icon.DYNTOPO_RELATIVE() + elif method == 'MANUAL': + icon = Icon.DYNTOPO_MANUAL() + row.label(icon_value=icon, text="Detailing Method : " + method) # Stages - Para niveles de Detalle especificados abajo + col = layout.column() + row = col.row(align=True) + row.prop(dyntopo, 'detailing', text="Relative", toggle=True, expand=True) #icon_value=icon1.icon_id + + if not use_stage: + # LOOK FOR ACTIVE STAGE + n = int(dyn_stage) - 1 + # VALUES FOR STAGES + prefs = context.preferences.addons[main_package].preferences # load preferences (for properties) + rowH = self.layout.row(align=True) + rowH.ui_units_x = 5 + rowH.scale_x = 5 + rowH.label(text="Values :") # Valores para el 'Stage' Activo + rowH.ui_units_x = 9 + rowH.scale_x = 9 + rowH.prop(prefs, "dyntopo_use_custom_values", toggle=False, text="Use Custom Values") # OUTLINER_DATA_GP_LAYER + row = self.layout.row(align=True) + if method == 'CONSTANT': + row.label(icon_value=icon_L, text=str(dyntopoStages[n].constant_Values[0])) + row.label(icon_value=icon_M, text=str(dyntopoStages[n].constant_Values[1])) + row.label(icon_value=icon_H, text=str(dyntopoStages[n].constant_Values[2])) + elif method == 'BRUSH': + row.label(icon_value=icon_L, text=str(dyntopoStages[n].brush_Values[0])) + row.label(icon_value=icon_M, text=str(dyntopoStages[n].brush_Values[1])) + row.label(icon_value=icon_H, text=str(dyntopoStages[n].brush_Values[2])) + elif method == 'RELATIVE' or 'MANUAL': + row.label(icon_value=icon_L, text=str(dyntopoStages[n].relative_Values[0])) + row.label(icon_value=icon_M, text=str(dyntopoStages[n].relative_Values[1])) + row.label(icon_value=icon_H, text=str(dyntopoStages[n].relative_Values[2])) + else: + row.label(text="NONE! Select a Stage!") + + self.layout.separator() + + if prefs.dyntopo_use_custom_values: + _col = self.layout.column(align=True) + _col.prop(dyntopo, "stages_edit_values", toggle=True, icon="GREASEPENCIL", text="Edit Values") # OUTLINER_DATA_GP_LAYER + if dyntopo.stages_edit_values: + box = _col.box() + _row = box.row(align=True) + + stage = dyntopo.stage + if method == 'CONSTANT': + if stage == '3': # "Polish": + _row.prop(prefs, "dyntopo_constant_high", text="") + elif stage == '2': # "Details": + _row.prop(prefs, "dyntopo_constant_mid", text="") + elif stage == '1': # "Sketch": + _row.prop(prefs, "dyntopo_constant_low", text="") + + elif method == 'RELATIVE': # RELATIVE OR MANUAL + if stage == '3': # "Polish": + _row.prop(prefs, "dyntopo_relative_high", text="") + elif stage == '2': # "Details": + _row.prop(prefs, "dyntopo_relative_mid", text="") + elif stage == '1': # "Sketch": + _row.prop(prefs, "dyntopo_relative_low", text="") + + elif method == 'BRUSH': + if stage == '3': # "Polish": + _row.prop(prefs, "dyntopo_brush_high", text="") + elif stage == '2': # "Details": + _row.prop(prefs, "dyntopo_brush_mid", text="") + elif stage == '1': # "Sketch": + _row.prop(prefs, "dyntopo_brush_low", text="") diff --git a/atelier/tools/mask/__init__.py b/atelier/tools/mask/__init__.py new file mode 100644 index 0000000..0bcb2f8 --- /dev/null +++ b/atelier/tools/mask/__init__.py @@ -0,0 +1,21 @@ +def register(): + from bpy.utils import register_class + from bpy.types import WindowManager as wm + from bpy.props import PointerProperty as Pointer + from . data import MaskPG + + register_class(MaskPG) + wm.bas_mask = Pointer(type=MaskPG) + + from . cavity import register as REGISTER_CAVITY + REGISTER_CAVITY(register_class) + +def unregister(): + from bpy.utils import unregister_class + from . cavity import unregister as UNREGISTER_CAVITY + UNREGISTER_CAVITY(unregister_class) + + from bpy.types import WindowManager as wm + from . data import MaskPG + del wm.bas_mask + unregister_class(MaskPG) diff --git a/atelier/tools/mask/__pycache__/__init__.cpython-37.pyc b/atelier/tools/mask/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55d93ea63af94105f482daff2dc616e7931a73f5 GIT binary patch literal 904 zcmZ8f&2AGh5VrRx*@R8g0uR7-FA;(h2qDtYmLfF@Ned#fv~1&5wW{|=;|*1$%?Yj? zcnl<-C|6E>1y0O(vngQ9{^|}nl(|13^?-paf$Y=)I>=iEk5QSute`Rwf zEotMAx~;6#UUQ>6%1Pa|g}N(Ss+G3aHfI&9 z>@OIvtjd<{Z@l86hjCAJ((c+pzf&=aONM0UU_$tw54BgsSA?qgHw6|9>`Sbqt=;U;E zel!h7!}q7>AAc}7zyrVY(Q|R3W6E!t9t*vIq$stBq|7rdhGndh7-oy5DoRo2xzd8JD8ewwk}?bjMSV>INAIIx+^?tQ zao_T}dBl!ydNbi-_`r;-He9|ZrdMETvBVOhcKMP2J(G&?sxL=TPCI0M?w(w@C=WhT3^>7_ss-l$2tfs1{ z%AQw;PxC6<2kUTD6zR1WP11s!Yxm$Rzi|#{MkV=jpIlh{y#!#K!qUf}BqCTK(wYN* z;z(DtEI;81Uj*Mg>529LNndnCR~$5J8x03YM8N#8_RqP#x}2^Lg@lUm@jVwvHXdoE3 z5bMY`ieWC9FU|WUAb|WmS^oHL<@X6W+8x)sBNx4(ZZl?kFjjXN%L-AY)(#o_Q1Nt^ z2^bT_f-&U-V12}(UtSn)k|Ng-^&t8asoStM>fhD)F`a9vG<`Oq!?JwB4W};)4(yk7 zl*(L46;FyP7Y)WfL?84Y-KTn?lG13(g#dUuG%`)3nk}ldG}OTJYMQ~E6l!_DT&Xs+ zwG6Z+*TpWrg`3z#uTyv4maD#5l~St~WVe0^KS(PFd}TjqL%*3!*>`VhZ)VwAcWiNc Qu)UsR`!?E^hWM`YAFztmv;Y7A literal 0 HcmV?d00001 diff --git a/atelier/tools/mask/cavity/__init__.py b/atelier/tools/mask/cavity/__init__.py new file mode 100644 index 0000000..0959a8e --- /dev/null +++ b/atelier/tools/mask/cavity/__init__.py @@ -0,0 +1,15 @@ +def register(reg): + from .ops import classes + from .ui import BAS_PT_Mask_By_Cavity + reg(BAS_PT_Mask_By_Cavity) + + for cls in classes: + reg(cls) + +def unregister(unreg): + from .ui import BAS_PT_Mask_By_Cavity + unreg(BAS_PT_Mask_By_Cavity) + + from .ops import classes + for cls in reversed(classes): + unreg(cls) diff --git a/atelier/tools/mask/cavity/__pycache__/__init__.cpython-37.pyc b/atelier/tools/mask/cavity/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e6e5c0ce2c9f74385fe8790f18cf326aadfbd9b GIT binary patch literal 667 zcmZvZ&1xGl5XUvzHQ5?FrxbeZDX=|+kenJqi0zO=p`oc;3PnLIE0Kz5KPqWW4ECvg znL?jvuQ~M=NzZsL!+32pX@7B zTqXDimCji{DDg}jDcS_*45vRqqd;-XDp&)MpIBZcYYuG1R{RHQ_j@b2U^k<6f=WWl z_x&$;3uaBP{!rQ+`km4i-fNHU%jsk;KYWt!3x6l4OF1hRCM@%`Pny;{Dz)dGaa03| zPTxi`3Vl*q|BJZ{7ate16Y<$==f%5AF=^Y2A{63dQ>D6;rEgrQPexrmIyEOjAEe z;^^S*q41?MZSbN{s;Rx01Z|CW^RlyT5TR+T7ZnyL%2=I{($pqMdC)HV3`;UTXh*&I zNF7G_Wqb&Z!vC{8!v-i9Go zxj;5rA;dvf6GlS1VYx#dLEM-Z==H<6DKxF2#}8|X;X5MLX?pT{-|wXc6Oe8`>5MeMcogw@EOHvFQP#v z^l?fkh_xW*!V1zPqsQ4#h{<^Wb+q%1_k>f%y$Ac=)?~8lXTEos`r|Mec-t{e2xaU0 z=`4k_yd(|D43S(?V&leoPyHeNd68#l z*e%$Zk;QHiZEj?CREeBL^%N;6t1arMXl~}_qEY;@a;hjOH=_&D#b|YL33Mv#nv2WQ zI@gGP6U{YZzQqp4amF8X{J%t)Y>^-sDz!&e5Bv=;$@flc(U@;_7)%7oQY3 z3QWt?R{oIRJ()j+bduH{r1P(1K7d% zW62;i`0R(@AOCXw&)bk;4-XEy_aK$3{V>ib!`W;?Mbn>VDGp&@qzDcmTQXsF?rj}D z+H1d)#n#f8d_dv6x8iI?3he#+?fad3d%LY`qVYUTNctQqD)9%D2^;FBC@)bf6A1gs zAf{sFY{xT3lR-9QCY;1%a)>%Fa&MXW*QcCfGD{$v1DvIhQ0ZWXOB={=$yuV2E7wO$wS$>7YqIQ*tyN~qGbTx6^UBLXD^Ws5m()Q=sME$DGdih zXd%*jp-Y%TmyWuYq+LI8D}`(Opg&`G!+yW$EhUkKE-ZRXgK0+PoAXT|NU;@DF;!h{ zsJ5ajHJ}an99V6&rqn<$r<7e#nrZ{2tFW42`QqA#`^DKE?Uo~KjN#EK#=^lE+TS#m zd=2BHsUM$dER0DSfVk3&aY&>TQZB}^-=nc`fJ-N`<#ACBVxRLd&4wvqHE^EYkVN*+ zb3_7VvZKj@;?hh4lOF)m3r)*pSEbc;Ai|Kv!-ah?k%g18!UFoUyo>U42N(AgVYD|( REVrQlAuiJ%qNO#z`UkjV7M}nB literal 0 HcmV?d00001 diff --git a/atelier/tools/mask/cavity/__pycache__/ui.cpython-37.pyc b/atelier/tools/mask/cavity/__pycache__/ui.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5ba7c8b252affe53ac3ed09881d531a67ded8c4 GIT binary patch literal 974 zcmZt_%We}f(B4>fkvYgmWH+6Qm_J*)kdZO3< z100d~B40W23!E5d6R8r+%Hx^wyl1vmsgx1G!TX=&%0lRuVK(3Z@dl2n0b+<@fo3?s z7`;WzV)iX!HpgG>z+%o2)!pBqh3Z(lCp6|_Y`6`8=fcxI5F7=VA+W-M&8)y-cHlB6 z@R%Evm>2l0bb*5{=CiF^6qH#Rd~fT=t>%!NoRVXzE=emVZTdONa$OpvS(L;|mp&eL zKaqouw$2W<(;xJ^+8d7NBF(O!7@JxvuN5}6WNlLIbbHOSqf^p88VtLgu}RW^yFP8W z)d`>_o}wwfv6g6R-PlWeYBOvaaA#q(w7)su(T%fortT6N!CdCu+5gB(8#zNzj^DrD zKBjqX#A`|<3+){CdxN^Aos56Uv=fb!SnrHi6%rax1V4qgdP+o$OZ{N|pfbthNp?~9 zbafHMELo5ljp+nb{1H{as7vh$nkVy2yMm6m(6%(uQkkT6S38P}b6tX9MOb4XA40wI z5uhJPK85Q~+I|_HDK1s`t`|1bv_mr*en{vniYH-9@ECHc^^$qasF}n@6z(@(JP*}a zMro!($`}kOY-U_UTo!W)vm_BJG*QE%q;MWJ(p)=C(nSSgt12LbeeB^X;9WS^d;i^o zhB4{Gn)rmobjAtMWkO~Nn+rql5OOuAV*TQe1S#540ZOHG%t@A~TvvgWd;(Pzd>Qyn zceV{pGd@XVzEWhoC=Rr>QCx48dW6EjY?fTfH*ug~L|FFT{rkl}j#`e%0OGWLs<<#O=w0zb2XLNLLZYhlgU(xr(tNZY za*PkqG&(6tQJo*=Wj>WaG2^Nz5pfJvEuopC$!q4dOhya0str&h7lX5UzBOwKWxyld znHAvE!4r9tLvRyAnFgq#eNK(21|rf&XSu<$vCJtJlY|!8!u3$@1ko*$bFLTRby+_c z&;);9?mkW583`tRxu0&=^&Z}vzNvCul+$!a$x=vouwTteEC^q}G^LNWo;*y=1d6&b zX)XjdCf#nNDkL0FW~y$|rmB=lHFm16E~E0&EGx>Q$+E3_zKE7xr>U5`C>W1Rs0MmS zyt=sGthC51%RV0?e9OL5WFN68Is&>L=eag3moHfI#*PSjmf5B!+)e>E8h8@J3T|+Z z#3~6IDXftgk)X#xOoC_t^dI(2!CrmFDM^t8efDi~*>D?4=&(+L#@dTJcv=kq7D*}g rzqM`Vo^3QWJLan#Hqm>OIJVhV`3K2jTDa($uEb2q=XPdvYqb6ccG2(x literal 0 HcmV?d00001 diff --git a/atelier/tools/mesh_close_gaps/__pycache__/data.cpython-37.pyc b/atelier/tools/mesh_close_gaps/__pycache__/data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..067cc45f094c5672b67f1473927183aca9ba8072 GIT binary patch literal 965 zcmY*Y%}yIJ5cd8h5HblM%)L8fA4Ncd#+M9qqkDu7@AJ$OqP^uLCYE)noa$nM*jyhzCjH3Cw<&+S4)h! z*%_-kjO7x_%-JzxNMzPzv}2*_F~*BRTJG2}jA{2gb%g30(NCQ1)UQu_uM@lvZIYvd zWLK$uZh3MhIgWOf3^Gvwl5il)0&1W|D0#m1>RHmTZxRlGHIiK`kU2e?l$o-L#b;@f z93KjEBnpP~nFL|@mYV4n7I(|Us5(AMu;zEX*sn8g(&bAv+p;qyjK)8WG`_iV8pUy*KKMimW;H7&;_Mk$&^VhvrE;M z;GB}Hf&7H@)&fO;N`Js!d-7lCsWVF{agb8rXl8cy+nL#!VY5~%8}QtJ`X_0Z4C5aZ z7LNnWBY0&K2sNk~8Xo>zp&418Wg>5fcI0?Yl=E^?-piZDoffa_P zSEOdgXymT|$2e%1s(6?%;ip_QY*lT?ljw=yph>5yvLB~!g=&-Ya3KwF^WrH0^9Ww~ z3lL^_CN(_k7+myhYIzQ|y&TJSdo@#g;r^e*6Gq^4z$alEzuRa zO4sQ6r45>o9vbupz4^-U%JdeveOs+Gw>#vpOLjso8QJp_Icv0-Oa-61=?UoE5$N2Z z;1S4v>h=XcWw9GEdGgt;-uLC^KyO8}Sy{ul2-C120Z#X5zvDSw5lCf#oO^n_-RgL` zrxTw_QG@^mhC5q1hJ#YDa=CDyJ>7%=Mk!xU^W-XI=Ub` zpWQu-!>N15#i?|Ip*!Uh*B8v4XmpwxHEfN34YPU*7-YUYu;3Vcu%I!OJMhZyA=K0u zn`3Kizp!4LQZk(qkwJuFFlMCY_wW)JWERDaBwBI7?y+0eV^^-yCFYNO>7dGH5of_ir zImR+#T-bE!YvYCe{I6&K?=eQSoJ9otS6-W4An>2 zdCFqC;1eHsC!NAPxCQs5YhS= z6rW%lXqz=?%5`0`P4^iVZ7QIB=c;%8vpM^C$B#Dv9a}crsGCc-oXWM2jt-A>pIl+H zsC;MV`0zd?03Fvw;Jop?k7`K81OiS*r31%lQ2mM?~mCaRi9$aP?~-e3<%bE z$VXF>*+9!En_;Z0KVnUOF4n-xTly8|Z!lnzWbG){b#R$m-pvpEP5EdPGPptq z4jziTh*8yD*~*$yneiWn_zLv@PIkWS!SE8Y_x)~fJ4sr8>i3Q~Bp@F3nju_1OpIMV zi7DJBj~A-mz0GgF>}8`$_IyftEPLB2gE1pII;Fi7J`l2pnIZbR2u|1@BybZifSt3! zBxUHEd;r8S%cf&4!|#S!1-=fi1yUZQRm%a2KUlZwTDuGutLE(X2jhQpj(pIls3IXa zRS8jLLf|SUA@Ws1pkBiHN`Vl{2cRzXLlRIug^R$3exHQ`r>|Nf#E+qRBfgRvYGTp>ZlLAL%CMWPI omfELlF5oFp(4n6D*WA!vp)acVFpGT)n?mAOs|dMVx0kp61K*Kb+5i9m literal 0 HcmV?d00001 diff --git a/atelier/tools/mesh_close_gaps/__pycache__/ui.cpython-37.pyc b/atelier/tools/mesh_close_gaps/__pycache__/ui.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4cea7206bff0330fb1b97346e509a737b90289a GIT binary patch literal 960 zcmZ`&!EO^V5Vd!`n>0--=mmN~x#kj~asnYl+N4!cl@^kMv|7n>Vv}xg*2dl_O_iRg z#EE}sk4SuxublV=PK=XAR6@*ZW@p}v$1`JluU4xdpl5G?(60ePKfSRg0?KP(TL-}r z#}Z9&f-yQoJmBFq;-SD_!X)6NhZ@l@h(ZnQqEnU&IrP?=AoD8IE+_&em?Kz)laL1q z;b9VSlEgepN`RGle2$Y1UgG6zlvMZzgg47mEwca_&eWE(%w(6QzoCs+qOIy6Pt(H0ATWP+B!2w>8Uhu4Z(?ayAmi)dtMc zp&yCw-o=v5)wFP-Q8P!hQ5x0|E96I48mfG`L*!v=9zug=8^m{H9>MQ-vHv1Hv%=W) z%|Y7Iy3GofzEfWV)~^;~Y*TZ52Xh!mI|K z7D`E*PJ}&Ql}dfB=`?HVxg*@LSq=8L4?rMX#xd|Va25FS$$wA1=@)hzo32c0&L)CV zSD|#G_*8nmO6lh*lXsr-KvKyDLc+i#wVDkDE#_LdEijE3K|d?I0`_`Pn_g#y7%4Mf z26C}#6QZ3Dyt`{=2MS-#@Sb?=u@HoRL(AUEnB4o~VCn5PLbox{^QN!H8h;g+_`OX2 UaEwpq$}d;#y}~}HkFAD(0Q2Y#wEzGB literal 0 HcmV?d00001 diff --git a/atelier/tools/mesh_close_gaps/data.py b/atelier/tools/mesh_close_gaps/data.py new file mode 100644 index 0000000..aa5b53b --- /dev/null +++ b/atelier/tools/mesh_close_gaps/data.py @@ -0,0 +1,18 @@ + +from bpy.types import PropertyGroup +from bpy.props import EnumProperty, IntProperty, BoolProperty + + +class MeshCloseGapsPG(PropertyGroup): + # CLOSE GAPS PROPS + use : EnumProperty ( + items=( + ('TRIS', "Tris", ""), + ('QUADS', "Quads", "") + ), + default='TRIS', name="Use tris or quads", description="Close gap with tris or quads" + ) + smooth_passes : IntProperty (default = 3, max = 10, min = 0, name = "Smooth Passes", + description = "Number of smooth passes that will be applied after closing the gap" + ) + keep_dyntopo : BoolProperty ( default = True, name="Keep Dyntopo", description="Only works if you are using dyntopo") diff --git a/atelier/tools/mesh_close_gaps/ops.py b/atelier/tools/mesh_close_gaps/ops.py new file mode 100644 index 0000000..4a5250c --- /dev/null +++ b/atelier/tools/mesh_close_gaps/ops.py @@ -0,0 +1,69 @@ +import bpy +from bpy.types import Operator +from bpy.props import EnumProperty, IntProperty, BoolProperty + + +class BAS_OT_Close_Gaps(Operator): + """Destroy those gaps from that broken mesh!""" + bl_idname = "bas.close_gaps" + bl_label = "Close Gaps" + + use : EnumProperty ( + items=( + ('TRIS', "Tris", ""), + ('QUADS', "Quads", "") + ), + default='TRIS', + name="Use tris or quads", + description="Close gap with tris or quads" + ) + + smooth_passes : IntProperty ( + default = 3, + max = 10, + min = 0, + name = "Smooth Passes", + description = "Number of smooth passes that will be applied after closing the gap" + ) + + keep_dyntopo : BoolProperty ( + default = True, + name="Keep Dyntopo", + description="Only works if you are using dyntopo" + ) + + def execute(self, context): + usingDyntopo = bpy.context.sculpt_object.use_dynamic_topology_sculpting + try: + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE', action='TOGGLE') + bpy.ops.mesh.select_non_manifold() + if self.use == 'TRIS': + bpy.ops.mesh.fill() + elif self.use == 'QUADS': + try: + bpy.ops.mesh.fill_grid() + except: + ShowMessageBox("The mesh is not compatible with 'Quads' mode or there's no gaps to close. Will try to close with tris.", "Can't close gaps with quads", 'ERROR') + bpy.ops.mesh.fill() + #bpy.ops.mesh.fill_holes(sides=100) + n = 0 + while n < self.smooth_passes: + bpy.ops.mesh.vertices_smooth() + n+=1 + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.object.mode_set(mode='SCULPT') + + if self.keep_dyntopo and usingDyntopo: + bpy.ops.sculpt.dynamic_topology_toggle() + except: + bpy.ops.object.mode_set(mode='SCULPT') + if self.keep_dyntopo and usingDyntopo: + bpy.ops.sculpt.dynamic_topology_toggle() + return {'FINISHED'} + + +classes = ( + BAS_OT_Close_Gaps, +) diff --git a/atelier/tools/mesh_close_gaps/ui.py b/atelier/tools/mesh_close_gaps/ui.py new file mode 100644 index 0000000..14b5c57 --- /dev/null +++ b/atelier/tools/mesh_close_gaps/ui.py @@ -0,0 +1,21 @@ +from bpy.types import Panel + + +class BAS_PT_Close_Gaps_Options(Panel): + bl_label = "Close gaps options" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = "NONE" + bl_category = 'Sculpt' + bl_description = "Close gaps options" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + props = context.window_manager.bas_closegaps + layout = self.layout + row = layout.row() + row.prop(props, 'use', expand=True, text="Tris") + row = layout.row() + row.prop(props, 'smooth_passes') + row = layout.row() + row.prop(props, 'keep_dyntopo') diff --git a/atelier/tools/mesh_detacher/__init__.py b/atelier/tools/mesh_detacher/__init__.py new file mode 100644 index 0000000..3ab8277 --- /dev/null +++ b/atelier/tools/mesh_detacher/__init__.py @@ -0,0 +1,31 @@ +def register(): + from .ops import classes + from bpy.utils import register_class + + from .ui import BAS_PT_Mesh_Detacher_Options + register_class(BAS_PT_Mesh_Detacher_Options) + + for cls in classes: + register_class(cls) + + from .data import DetacherPG + register_class(DetacherPG) + + from bpy.types import WindowManager as wm + from bpy.props import PointerProperty as Pointer + wm.bas_detacher = Pointer(type=DetacherPG) + +def unregister(): + from bpy.utils import unregister_class + from bpy.types import WindowManager as wm + del wm.bas_detacher + + from .data import DetacherPG + unregister_class(DetacherPG) + + from .ui import BAS_PT_Mesh_Detacher_Options + unregister_class(BAS_PT_Mesh_Detacher_Options) + + from .ops import classes + for cls in reversed(classes): + unregister_class(cls) diff --git a/atelier/tools/mesh_detacher/__pycache__/__init__.cpython-37.pyc b/atelier/tools/mesh_detacher/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3d5b3b69a52f6276cc6d25eca178194790ba47f GIT binary patch literal 1068 zcmY*YOK;RL5VrH)G+SC!UJ?hcxm2i}KnT%?iiFg%P*9~RD5Ps^8(}BG_R_ADaDgj7 z1c_g?S5Ey4oS1Q%0+urNygrX-zU++0F@g2y_0RmHOUN%&hULKFIedBp1S5=oBPWD8 z%!QpY?}()S6g+l?AR(XZ!l%E0V3aw6NZJufn9DrjNVlVKrpyK5==K2y;)K?XsmD+HXTL)O~?`$9ClehT+*Jt@2H^uY}yssKl)|Cc-JlG~L zKpP*I6|2t=imI4#1%?}wx~u?iqUwe#vjCH477b6wOSf*c!Zt2Pr_Ew>Zc3q*hq^N_ zm5&FH6{b+Zbqr=|h3)Kf8bH-5LOnSxbk45oOG686(Rw6z3g8yVIhV`us;VDE;rM=E z>^{lfX|8nkazEQ{nmxESds7#(tY+Dc;1%QQ!G1ljpdNVlV9K^OA3x0WRF#dncCMTZKnhheVGtKkI$W$b)ZPcYl^l-e1R- l*zAO^Qgsi!hpA(mZIyqJET)Bvp30id1%GB|hPOuRe*oB8?aKfF literal 0 HcmV?d00001 diff --git a/atelier/tools/mesh_detacher/__pycache__/data.cpython-37.pyc b/atelier/tools/mesh_detacher/__pycache__/data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..840c8759ba8d241596c1c6e1c2020e605b97fac4 GIT binary patch literal 1289 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�IMW zh}|!vGs%REzTJD9HxVPRcT1Vq>3=Gl=!o%uUhL%`ZNWWM3qCA)^=o zZ!KyTEKQg=F=rALoToCAt%q={)ZSNEFo`#c?wG801uEQ@<7gEE+o((silS$Zx$((4|-86lu$Gn;NL$rJQY zuI`}^QIek`(Ou)4&MAAQhH@4A$XIRPNT?a<>dF5-J)1giyRo1`O0#&xC{+5ROxQ~?y&g+d9VMB^eMXB(&eS5Rf(;NbrCvpS4#ov7b&W@R| zgpE5F&LNB3DSFKEQDQ#Z!B2lhA-J#<%X!N<6Si=amD?@HGZ(IMa<{cOYj|UQL)*oA z!*XkBv-)G!~l9{Rn;5rZGRag$ImtZaC2 z$+3!vFZ`)WwU+u4Rnk{Lo@Tn1kQfJA?r(1$CX;u`eyTqwFD`0Gr*#R*>#EMmLSw*a zX`H-7b$pZ+qCDSEi}X|i8g5L=tU$yGl$C^fjwYv`S27we?6T58ja&>)s`IAgR-ZHZjh|=eBob}^^*Zj z@XvhbN&H?*(DAFic&n;*@!t4tndVt>8gDCE2ni4N%2|O0;mdndyt(oCVXUW+Rke;& zA+Ra&RxMQ~;cz-rRUOx5sdSu6{pq^8I7zZ1tCM7-nlFOB=`<958wKMb53P^~*6?aa zodda+q@tHBNmZcoAE~&odr~$0qaz$7mYaKqSga!!+C1gwL2DyLSVt|i`D&04iB=bP z>rsQmGURhH!ZqYOM)DDff&-xIah$8OqJKe88#5v3NM?E-aWe$mXx>o>Bi!H)i4_tw zQCKB0CP9ybkOZ*+=r`<$g1tJ!AxRN=UG`0KnQ#+H=&(kDrrL>Hcv=jvi=-6u+nP3W n$26Ln9r6_p_t1L~S*F=W`3K2h8o1zTUW%EL&&iVkM?cH9lW8nDW+u!`lhGG1r zjrp)q*~Y)zLjeYup%Iy`8CkAnYTF8Hk?q=1-K|Frw-GhnX4G<9QQK{s#(@Dfuzxnd zX68>dw*&Qo;WS=hB*U@t-kIPD6KZxKc$(ym)8Y4Qq?|_H?Z;^}pUk_voQJnoZ$IQz z-I_~hJctz&x4^<-d^fu|P$GyY^U0Ckcs`J5N$gKlnBkgWxYi{SbZg883u-qetAo8| zpjC$kG;b`lTWB|+4INm3F7$3}#1;!|36|kCSgEkr5o^Ldcmv*qw<_#DVl8+Z9>6>B zF1%OxK158%e;){Za8tu*9~OuOAHkyv`M5$>;gbqkL!`|*;J{<}wD4NrGGGIXyO}Ta zUnoIGii@*@Y?&PI44#wzcNgb7PcKf-$-ZOeO<-f1hAQs_(jN&%6@!bcJ4mq^WDs$# zrj8{Ruq?S9(}?9=si%BdOijuHKKf&>Y)^juMUUT~U<=1%$&_Q~wJ3;D&=*A<(bK^i zyqK;045L#1eo_T^`6O>&OoP!?%%sfQYOV?sy*G}%(0zXULTwu9q!?ShXG{E#bceQYffYU3~)tbaCmZX)X!R{F(U&6v&Tol6{^~U zL>Pz+Q$=_j&QP$@kF1RakClV|_Wse%1$8HuR8B9eu8 z#seOsS9ABMf3nvpryKB54m|xyw5GWs=eeImJpEH(_V_cf86zZu6 zCP7TY0?XT1IPL35S@(=fwb4g!OfK8rWdzAHO;JZAl1GgENW+v#LdSR&{3j*sG8Vq9 z#Ar(636q<7H^F$m_SNwgHMe=q8Dy*FEi@0pK$rXfRpdf9UCr>V&?mbj*se;!;KW(T zTfVQ~9pBG8zK;b>L#=mx|A&-@)lAd(fscG&)OCTDe4oZKS5)UE5!E-8XlbD@VbRp0 zhT^3m*0rw1Kk3u2y-Uf2^q%c|J4v!f74^lePh%+J&LQvf&96T5%AQM) z0^pY3j$&cJMA>Ig;k7G0-7LRi#)XGb=w>n#`h%gfmJd*LOxs*Em(6>{&o(W4(ONO} zuUMzc(Hkj;({TH6x22Ngj_GC7l+lC6OY#In^!8 zBIxV_cJ6rNT$Ai(vyqW=gLz&cZvpZem<8fOcEW)DoNF!Q~Mk%e- zm9m0gwXQa_vet-|V~uz@-bj=ajbu3q8I8s2sYbe-jz+FTSezy9M_9s&-c!pN4yBk5}#0>)Zj; z+Y*@>+Y2+f>AGX?WU~{FQx6NXMSG*s$vswm-8~Cy&gM1CbpXkj5o|? zv%Y0i)=YcVa$Dz$eu1ldYnD!;7Sl~`nY!ob75sXZZtAwRsW&Wlt)(xU?$`?z9=Zk{ zdZ5+eZ|URIo<8ZfCzA8UD>Dm=#d)FKn42n9sB`oe51vWECfxEGwW=}g->t#uj|9JUkYJvkuQ(evYxPZ<_9;(i4#uSF zj?BzWmqcoEd~UKhJ6oI*@zTV5#mPm?NUh;8ixM%{!$8p-1*f`FwfMA%FHGK;y}l@t z(=&533s;L%g*~EYvuZPE6H~&*MB^fd;oO-TA{ERMi}4hOK;$rEs<$n}S*}?XPpHey zEulJ1SHuGjHD?*F<%zUw)gj}jZ&$5Nqi$}Yg@iAOD!wj}>R9NCo=(!AlR~r2h9%Nf z*YGDEE5wBcy(=P7acmF9iC7a0&V>=nHIaZuGEJz^^OFca1Czh>Cf{1R;ac2XdT)9O zQ*R2cU7B~y1{UkmMBTEP#r0`&v*}eGyTe+#IQHg+CAY$>P0w928FOrRY235waOFZ} zqu%tEJS<{&iB`dm$ChBs7@U%@?pT!#&&tv49R-O*lhJrIjfeh~XkN)8myBkkLq1;t zU-I%w>+IK9sAEA3bA_Zx8U}gFFhtrg(D#ixrL%_d@rGFsN)m>_oC?^f<+@R2(z8h< z>*lgm7ipxNCON?6WUlmG7dcBj+L{x1TuLOPn*2{HlD}(%qP2UO1pfo1vq9a!j{Wo& zWhH1`(*DcR4>T*uGVesd%Yv6>qwL)M$h~Me z$Hv(C`w^T47uZYq?PV9SnqL;f&mOanH>#EOZ@Q@-c*dscE9(D%^y#~|RGxvGc@|_9 z5B`q-Lnm>0^@l&8zrVkPU1DVg3$vhMc~or>^NvU`YsK8Cd;BPBVG&D-pl+z`^GhPN zxQ1qJ97iIBEyz#uC@MWZOoZBQA? zolf!~^N%gsM1#ExbGo4U9gI~ytKsrDP+4S{<*#m(^ImYKTiJyToZY(Lje;WO{E8Wu zO8kPwR<6jZo<4^okT&bq8JX>zi2CN5WruCst2#tixYLkFX+hml%BQ6n7IZovZPq1EB-c?)L*L3?cUgI}vK6QLyuZ~Iw`K}7i{4tSfLSIMS{<)3DGU_-hI&GZ#v{|jY`W+0_3Z=pmnoeqZ z+?CH_hmz$if?oE_$({*KPH-H>bUg95VFZ7R2=(ZRCBm;t9{D|-O?-xUI}?Gw zNBLdj4d=yimO2|Ae1+2wlj=Bxd}m&#)RP`%+}xZ%f!hNT!FiJp|6lA~su+5gZUBRL zq<8b!@?@i&YD7cjc`BZQc z;9e&=xTnGE^4=!%*;)=~LyqocLB0p4L~k`3oDO?%KJ?>%AM2IB{Wu@;NYS(NzyC)A z?7#!GvDF`F>1Bg#h_u4apZRuT?&R6ZQB!Svu2*}>raj1_JK!qx29V=*&g&*&MU30LGxfxw$uAa zZHT?Pt~^xv+B56L(4S#8ivB2E;5M(;4zkxUFJ6D3+>KLNJ8m4p@8O4vD*_EiAn}Iu zr!W5|drQhEAg8wzhR%9HN7yLnQS>-r9Ao{U$E6mpZwf@>URQA0(zmqCpZi~78BWZX9>(0C zW42?JBR9mzFL9psIsOh(n4A;IYq(BUIe>)M7J(lG_YQ~aRadtiPp>$QCIT(Gt!!3t zS?q|@y->!fonSYpDX&c;R-B(N&GWrDeMEd_?!)3dg52P|y*e{h6!B~03-2SM#nDJ% zrDF??Fbkm-r)Cy$u`JHY0GWRT4YJ;2y1e4#q@Nuk;#X!%6XUaVLEYiX)YoB5;}4Bp zE0q?nip1n>X`#5VSQrp7c^4BIhgT8o8{Ssa61gDLz?HUoNA&H=`Xapua$TS_Rn#%> zR1q>as+>Dq?AvfHgN}ZOQ(w)xLDxPp_!P@nG3#~Q>cyU2MQ*iaiJ_;Ipg9{MyTzVS zhWZrpqfB#cvnet}4bSAOGS;U|(}AIg`zdYKtE+Y(WIMcJ)j)6? zl@0C!e?kv!cLhQsM|Y+T4gjHPRxFX{R>L8*!8R(UT|t=I%K<8w=sZkasiH=fI$>6z ziQo=J?73E4xJnoo+i2FS6)?rX;{5pB!gOi=8u4Xg2qP?!^d|)-4PDkSB#-I7+4|1S zHH#yv*ulRp>DOhWqdV{y0bcsD1*0v!0v7{I@S&Mj&u*yZF$tX! z;ZMI+x}gKXIkl)4KZ1dii~6`uw(HaLrE8SNeAZ{CrQB4BLTZNhtq7JTRhGechkhD6H_ivrlvmgSAhtGqta%HCXa09C;U4Qqtc-BKi{ioRQEedfWDSAU@` zTrJ%c>iF!ehzYiOt;Hc<`x=Rg}qg8q?H_>EzaJ!H9msC573~{ zXxWSSV(AKkL_`x?q+Tv)iUzUnWW0%jqf|hpuwf6SFN0vbDc+o!z6I#aF??aMXR*TwN?q@dZF^d&30@Fg6E%K z;&e?eT;%=u;rocl=trj%^_3qWLicH4ShU1NFAavVQg<9?tY8>KY7H^4;Uc=_LnJ&* z!WsciA-aaSCwjuU4(#VPCbnxeOuY%a0K|6JT}? z#MR|2!&>t!1NI;ar1;JsEed3Wif|Xe9NLhmnJdE-K0-n=B$a52O!B z{xhW&iX#W2S%uI{(4ck?^z)4B8{1EBP^K@*BcFOPVF2!WF#g9+)n4a-||7{5mBc_Pn+ zK3_p;0gp?Ob~=PU`!Op0$SG`e+=^f42otEw(0?# z320Qpna9|131^-Fm~m3W^s>Jc+?bT;?fsQUZ+f+z22@c>p|(Ov+uJPmwX> zM7|X+$%|A~_Gp??Ux7x-{W3&l6x5(cqB7qUh2yi--U%YRF(bJq{tNsL2siZ1`S1ex zjX*C1K;Hs>lWrxH1^^3=U&w4ukEW1gyARU_xn%`V6c2tzVKwp zyRiVW>&T~kXh~W&SQ`Rhq@xYI#PMzREgp>228|(GL;ZwoX&3+uAXEw6bu{5MPnbVc zJ1k%M^h%hhBN4O}XgESJUsz*!TbH3f$qv^>7<~xPx2tw1u=ce*-F~MhF&0BO8|7by zmhSDcbhvi3)9#MzIK_^Eo=5$RaUra!`!xY`)n%WKOSt9lzM@Ym{}j?=EGa$k>Hk6hsF%;} zUufO#*|~du4cCqU%uWKXC6Jb2EjdrOU+S8(2LV@it%&W5VcEXzmv_=TYxK#UveOvH zSKwcaCdPeV?Nyp7$h{VNI2cEEA*J=i@TUk6C6u(QuvL+u#guXs@J4Vq&$ ztQz!hRK8M4pcK0R%CT#q6nmDx13agt!}8T_*UT@Ro8v8PtTHkzr;GkqD2Pz_0=SDZ zybLg(Xd@eTY_Mb ze-C`LM#sO5iSgny0k8}~mMwZJ1beua?c$vug1opNgcjbEVWm*mu&MEsf1iq_x9Aeb|A@#ZM0Ura{EvzECq#Zg zJ2~LuQUVJ~(+!`ai(B*XRmaEQZ62|Ism6#N1$VgO%n=sR;AmZgl<;NGyA;p310Y$9)4^y~%^%Hw MH}X0jQvpu!zx702pa1{> literal 0 HcmV?d00001 diff --git a/atelier/tools/mesh_extractor/__pycache__/ui.cpython-37.pyc b/atelier/tools/mesh_extractor/__pycache__/ui.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ef123019902736148ce2cf473c5313555d74205 GIT binary patch literal 2839 zcma)8OK;=I5hf{;lBkDYGq&fs+S$Bxh!JdZNwC-*k1fwQ@yO2Dvq1zQ4O-nJQ>I9! zn_7$tB0W=9FWA92Scl@)b#W?cKp5B)Y1ry1Lj^-Tf7x zG#Z|P=O@4XGdo{2jDM0ae{6Ifqp2T&aD$tnF*bXqX>f~|J~evQQ-j;w`PAT!Fn?F- zmAHFow9Eg3wb3@UbKplJ99SgcanT&KEj0BPkeM-pPi|eB7w|W;m^m{?)~GbH)gSZT zMQK(VIov+AUjSbOUjknSUje@bf46y!*Lma8GL2}lkUiey3zsECZy?IW?h%FPhruu!{{3(C ztUuP}CwralcIU-(Z7kHuM&bui%ErDrW#V-z{XsgdKZn%nh+`mwJzuFs4m`|TtcoKBn9J;YK!c(QrO4vrY9S>YJQ`$-zak?SrGB~3}Hn6P)Oat z%I4wV^W}jQek!=O$1xYWfgpoV1dESHVt`<{pZdBICqnuutgGn>Vu*xN+KpemQXI!u!e%%l}NLeJXTx@`@k=PYy z&q=5!u}T?fkQiMfsZ$8$Bu?T_glxOo4gK?Yk|M4+qWzF{%9@kNI8lg^9Jwr${$$AS zW9&<{^|Ss?y-BU|Jl^h}N`&P`lvt7r344;QiLa;I13Zm+`2CuXn8 zE4&II@VJL7QIi!+)aCifD}TJ(`4#)|wzgjGYP-AN?Pxa-Y`S{$47cLqur{|lJDV@} zj@Z`T{$XeP3OgEbOs6-)jz?H1(7+f0H-I4)p~PpU&y5Tl>^G+R&CJG4$I=O>xP57x z1}t1yKszdP2YAwlhsvmW;m(|qSD=Xn9KzksY^tz{IN?Vva=Ar(<`iGaz1QHqPI&M& zedC+@&uBHx&w(aivt8hegks;&FY)EK>R0%!n|gkGW&_!5#IVZOFlJqpvHr%a%o^ZL z@Fw^IIN?|soW_^Hm%&%SGrR*|g}es79{rViZ!Gz+$XbQ3&nozeHM;Z0l>a4uQJqza zY*a^gdFhUE;bmF(fSh-VHO?(4=XXyn%>BoVtnb0PmRZ;Meg2@Z#;P*!^I2s15SA33 zGpob$otu8KD>l2wTUQx)mp`Hm6bSuZS1#em9BJn;4g(Gtbj51`A^Jquj#!O(ptKd~gc?Y6gBl3V;KFp7J3mDX*o7*}&5mAeP1js13yhldMzW`vh zVi^pB$PZg%K$m=n2I>bur5_fsG=v7v7ax6FH7F3jmEEX!hky7zJ^%hlSC6ic);ZkY z+ug?H%YoAiQbJ>@4%0@iYZY)?-HKVRnXViX*0yep7lrOWZ(op~B;omV>BH9M!NK0I zTU*aIyH7iZtq^pXS*O*X*|pQI`y)xxJy0Cvb;}(Hbp)pLJ5{F5M9064qZjn3SI$n6%}(K({@AK zN`$ZEdt^>%dTq@%Lv@tjCFS?XQ3)&7CDc0YC^}>~6j_dCwg{>YS&5MJ1BF@$$R>v> z0WpBcZidRspoG;;)3(4Z(h_1*t6%Gr?853Ml6w}(G>4j3%*y#(b}=@6^sWC18??A? zyQV9QMgCYYrai`xoC&hcbe-Yv!w-v*%5lg-|5$|h83>t5{DENUc_MTZq7*|Mtz7mX z7j;zEsEw6DFcm5p}Wwyqv0 h=Nq&dC8?ribuJrxn@?T&Adm4wlN=K$)Jv7M{{i;L5gPyi literal 0 HcmV?d00001 diff --git a/atelier/tools/mesh_extractor/data.py b/atelier/tools/mesh_extractor/data.py new file mode 100644 index 0000000..d079507 --- /dev/null +++ b/atelier/tools/mesh_extractor/data.py @@ -0,0 +1,22 @@ +from bpy.types import PropertyGroup, Object +from bpy.props import EnumProperty, BoolProperty, FloatProperty, PointerProperty, IntProperty, StringProperty + + +class ExtractorPG(PropertyGroup): + ''' MASK EXTRACTOR ''' + is_created : BoolProperty(default=False) + super_smooth : BoolProperty(default = True, name="Super Smooth") + offset : FloatProperty(min = -10.0, max = 10.0, default = 0.1, name="Offset") + thickness : FloatProperty(min = 0.0, max = 1.0, default = 0.05, name="Thickness") + smooth_passes : IntProperty(min = 0, max = 30, default = 15, name="Smooth Passes") + mode : EnumProperty(name="Extract Mode", + items = (("SOLID","Solid","Solid, two sided"), + ("SINGLE","One Side","Like Solid mode but only one sided (front)"), + ("FLAT","Flat","Just a flat copy of the mask selection"), + ("BLENDER","Blender","New Extract Mask method from Blender by Pablo Dobarro")), + default = "SOLID", description="Mode in how to apply the mask extraction" + ) + edit_new_mesh : BoolProperty(default = True, name="Sculpt New Mesh", description="Sculpt new mesh when extracting it from mask") + keep_mask : BoolProperty(default = False, name="Keep Mask", description="Keep Original Mask") + post_edition : BoolProperty(default = False, name="Post-Edition", description="Be able to edit some values after extracting, the apply changes.") + extracted : PointerProperty(type=Object, name="Mesh Extracted", description="Object extracted by the mask extractor") diff --git a/atelier/tools/mesh_extractor/ops.py b/atelier/tools/mesh_extractor/ops.py new file mode 100644 index 0000000..c04a62e --- /dev/null +++ b/atelier/tools/mesh_extractor/ops.py @@ -0,0 +1,450 @@ +from bpy.types import Operator +import bpy +from bpy.props import StringProperty, IntProperty, FloatProperty, BoolProperty, EnumProperty +from ...utils.others import ShowMessageBox + + +class BAS_OT_mask_extractor_apply_changes(Operator): + """Extracts the masked area to create a new mesh""" + bl_idname = "bas.mask_extractor_apply_changes" + bl_label = "Mask Extractor: Apply Changes" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + props = context.window_manager.bas_extractor + if not props.extracted: + ShowMessageBox("No recent Extracted Mesh data was found", "Can't do this!" 'INFO') + return {'CANCELLED'} + obj = props.extracted + act_obj = context.active_object + try: + bpy.ops.object.mode_set(mode='OBJECT') + #bpy.ops.object.select_all(action='DESELECT') + obj.select_set(state=True) + context.view_layer.objects.active = obj + if obj != None: + for mod in obj.modifiers: + bpy.ops.object.modifier_apply(modifier=mod.name) + except: + ShowMessageBox("No recent Extracted Mesh data was found", "Can't do this!" 'INFO') + pass + + props.extracted = None + props.is_created = False + + act_obj.select_set(state=True) + context.view_layer.objects.active = act_obj + bpy.ops.object.mode_set(mode='SCULPT') + + return {'FINISHED'} + +class BAS_OT_mask_extractor_quick(Operator): + """Extracts the masked area to create a new mesh""" + bl_idname = "bas.mask_extractor_quick" + bl_label = "Quick Mask Extractor" + bl_options = {'REGISTER', 'UNDO'} + + offset : FloatProperty(min = -10.0, max = 10.0, default = 0.1, name="Offset") + thickness : FloatProperty(min = 0.0, max = 10.0, default = 0.5, name="Thickness") + smoothPasses : IntProperty(min = 0, max = 30, default = 12, name="Smooth Passes") + mode : EnumProperty(name="Extract Mode", + items = (("SOLID","Solid",""), + ("SINGLE","One Sided",""), + ("FLAT","Flat","")), + default = "SOLID", description="Mode in how to apply the mesh extraction" + ) + superSmooth : BoolProperty(default = False, name="Super Smooth") + editNewMesh : BoolProperty(default = True, name="Edit New Mesh", description="Edit new mesh when extracting it from mask") + keepMask : BoolProperty(default = False, name="Keep Mask", description="Keep Original Mask") + postEdition : BoolProperty(default = False, name="Post-Edition", description="Be able to edit some values after extracting, the apply changes.") + fails : IntProperty (default=0, name="Number of User Fails xD") + + @classmethod + def poll(cls, context): + return context.active_object is not None and context.active_object.mode == 'SCULPT' + + def draw(self, context): + layout = self.layout + layout.prop(self, "mode", text="Mode") + layout.prop(self, "thickness", text="Thickness") + layout.prop(self, "smoothPasses", text="Smooth Passes") + layout.prop(self, "editNewMesh", text="Edit New Mesh") + layout.prop(self, "keepMask", text="Keep Mask") + layout.prop(self, "postEdition", text="Post-Edition") + + def execute(self, context): + props = context.window_manager.bas_extractor + activeObj = context.active_object # Referencia al objeto activo + try: + if activeObj.modifiers["Multires"]: + ShowMessageBox("The extractor is not compatible with Multires Modifier", "Can't extract mask", 'ERROR') + return {'FINISHED'} + except: + pass + bpy.ops.paint.mask_flood_fill(mode='INVERT') # INVERTIMOS LA MÁSCARA + bpy.ops.paint.hide_show(action='HIDE', area='MASKED') # ESCONDEMOS LA PARTE NO ENMASCARADA (LA AHORA ENMASCARADA) + bpy.ops.object.mode_set(mode='EDIT') # Cambiamos a edit + bpy.ops.mesh.select_all(action='SELECT') # SELECCIONAR TODOS LOS VERTICES VISIBLES (YA ESTAN POR DEFECTO, CASI TODOS, SINO: CAMBIAR TIPO SELECCION A VERT/EDGE) + # Duplicado de la malla + bpy.ops.mesh.duplicate_move(MESH_OT_duplicate={"mode":1}, TRANSFORM_OT_translate={"value":(0, 0, 0), "orient_type":'GLOBAL', "orient_matrix":((0, 0, 0), (0, 0, 0), (0, 0, 0)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), "mirror":False, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False}) + try: + bpy.ops.mesh.separate(type='SELECTED') # Separamos la malla duplicada + except: + if self.fails < 2: + ShowMessageBox("Where is the mask? Please, create a mask before calling this!", "Can't do this!", 'ERROR') + elif self.fails < 5: + ShowMessageBox("As I tell you.... YOU CAN'T EXTRACT A MESH FROM A MASK IF YOU DON'T HAVE A MASK!!!!", "Can't do this!", 'ERROR') + elif self.fails < 10: + ShowMessageBox("I wonder why natural selection has not acted yet. OK. Create a mask or...", "Can't do this!", 'ERROR') + elif self.fails == 10: + ShowMessageBox("...I will format your computer you bad boy/girl!", "Who dare me!", 'ERROR') + else: + ShowMessageBox("Where is the mask? Please, create a mask before calling this!", "Can't do this!", 'ERROR') + self.fails += 1 + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + activeObj.select_set(state=True) + context.view_layer.objects.active = activeObj # context.selected_objects[0] + bpy.ops.object.mode_set(mode='SCULPT') # volvemos a Sculpt + bpy.ops.paint.hide_show(action='SHOW', area='ALL') # mostrar todo + bpy.ops.paint.mask_flood_fill(mode='VALUE', value=0) # borrar mascara si no se quiere mantener + return {'FINISHED'} + if self.fails > 9: + ShowMessageBox("You got it!", "ALELUYA!", 'FUND') + self.fails = 0 + bpy.ops.object.mode_set(mode='OBJECT') # Cambiamos a Object + context.view_layer.objects.active = context.selected_objects[1] # Seleccionamos la malla extraida + + props.is_created = True + + # TRUCO PARA BORRAR GEOMETRIA SUELTA, BORDES ETC + bpy.ops.object.mode_set(mode='EDIT') # trick + bpy.ops.mesh.select_mode(type='VERT', action='TOGGLE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.delete_loose() # borrar vertices sueltos para cada malla + bpy.ops.object.mode_set(mode='OBJECT') # Cambiamos a Object + + extractedMesh = context.active_object # Guardamos referencia a la malla extraida + props.extracted = extractedMesh + bpy.ops.object.select_all(action='DESELECT') # deseleccionar todo # QUITANDO ESTA LINEA PUEDES VER EL OUTLINE DE LA MALLA EXTRAIDA MIENTARS ESCULPES EN LA MALLA BASE, UTIL EN ALGUNOS CASOS + # Solid mode, doble cara + if self.mode == 'SOLID': + bpy.ops.object.mode_set(mode='OBJECT') + obj = context.active_object + if self.smoothPasses > 0: # aplicar smooth inicial solo si los pases son mayores a 0 + smooth = obj.modifiers.new(name="Smooth", type='SMOOTH') + smooth.iterations = self.smoothPasses + if not self.postEdition: + bpy.ops.object.modifier_apply(modifier="Smooth") + solidi = obj.modifiers.new(name="Solid", type='SOLIDIFY') + solidi.thickness = self.thickness + solidi.offset = 1 # add later + solidi.thickness_clamp = 0 + solidi.use_rim = True + solidi.use_rim_only = False + if not self.postEdition: + bpy.ops.object.modifier_apply(modifier="Solid") + if self.superSmooth: # post-smooth para suavizarlo mucho más + co_smooth = obj.modifiers.new(name="Co_Smooth", type='CORRECTIVE_SMOOTH') + co_smooth.iterations = 30 + co_smooth.smooth_type = 'LENGTH_WEIGHTED' + co_smooth.use_only_smooth = True + if not self.postEdition: + bpy.ops.object.modifier_apply(modifier="Co_Smooth") + # Modo Single, sólo una cara + elif self.mode == 'SINGLE': + bpy.ops.object.mode_set(mode='OBJECT') + obj = context.active_object + if self.smoothPasses > 0 and self.superSmooth==False: + smooth = obj.modifiers.new(name="Smooth", type='SMOOTH') + smooth.iterations = self.smoothPasses + if not self.postEdition: + bpy.ops.object.modifier_apply(modifier="Smooth") + solidi = obj.modifiers.new(name="Solid", type='SOLIDIFY') + solidi.thickness = self.thickness + solidi.offset = 1 # add later + solidi.thickness_clamp = 0 + solidi.use_rim = True + solidi.use_rim_only = True # only one sided + if not self.postEdition: + bpy.ops.object.modifier_apply(modifier="Solid") + if self.superSmooth: # post-smooth para suavizarlo mucho más + # Seleccion de borde entre malla extraida y malla original + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT', action='TOGGLE') + bpy.ops.mesh.select_non_manifold() + # Invertir + bpy.ops.mesh.select_all(action='INVERT') + # Añadir al vertex group + bpy.ops.object.vertex_group_add() + bpy.ops.object.vertex_group_assign() + # Aplica smooth + bpy.ops.object.mode_set(mode='OBJECT') + smooth = obj.modifiers.new(name="Co_Smooth", type='SMOOTH') + smooth.factor = 1.5 + smooth.iterations = 30 # valor máximo + smooth.vertex_group = context.object.vertex_groups.active.name # usa vertex group + if not self.postEdition: + bpy.ops.object.modifier_apply(modifier="Co_Smooth") + # Flat mode. Sólo un plano + elif self.mode == 'FLAT': + pass + # Dependiendo si queremos editar la nueva malla o no + if self.editNewMesh: # Si vamos a la malla a editar, primero tenemos que pasar por la malla de origen para mostrarla al completo + context.view_layer.objects.active = activeObj # objeto origen como activo + bpy.ops.object.mode_set(mode='SCULPT') # Cambiamos a Sculpt + bpy.ops.paint.hide_show(action='SHOW', area='ALL') # mostrar todo + if self.keepMask == False: + bpy.ops.paint.mask_flood_fill(mode='VALUE', value=0) # borrar mascara si no se quiere mantener + else: + bpy.ops.paint.mask_flood_fill(mode='INVERT') # invertir máscara para dejarla tal cual estaba en un inicio + bpy.ops.object.mode_set(mode='OBJECT') # volver a object mode + context.view_layer.objects.active = extractedMesh # malla extraida marcada como activa + bpy.ops.object.mode_set(mode='SCULPT') # Cambiamos a Sculpt de nuevo + bpy.ops.paint.mask_flood_fill(mode='VALUE', value=0) # quitar mascara de la malla extraida + else: + context.view_layer.objects.active = activeObj # context.selected_objects[0] + bpy.ops.object.mode_set(mode='SCULPT') # volvemos a Sculpt + bpy.ops.paint.hide_show(action='SHOW', area='ALL') # mostrar todo + if self.keepMask == False: + bpy.ops.paint.mask_flood_fill(mode='VALUE', value=0) # borrar mascara si no se quiere mantener + else: + bpy.ops.paint.mask_flood_fill(mode='INVERT') # invertir máscara para dejarla tal cual estaba en un inicio + + if not props.post_edition: + props.is_created = False + if props.mode == 'FLAT': + props.is_created = False + + return {'FINISHED'} + + +class BAS_OT_mask_extractor(Operator): + """Extracts the masked area to create a new mesh""" + bl_idname = "bas.mask_extractor" + bl_label = "Mask Extractor" + bl_options = {'REGISTER', 'UNDO'} + + offset : FloatProperty(min = -10.0, max = 10.0, default = 0.1, name="Offset") + thickness : FloatProperty(min = 0.0, max = 10.0, default = 0.5, name="Thickness") + smoothPasses : IntProperty(min = 0, max = 30, default = 4, name="Smooth Passes") + mode : EnumProperty(name="Extract Mode", + items = (("SOLID","Solid",""), + ("SINGLE","One Sided",""), + ("FLAT","Flat","")), + default = "SOLID", description="Mode in how to apply the mesh extraction" + ) + superSmooth : BoolProperty(default = False, name="Super Smooth") + + @classmethod + def poll(cls, context): + return context.active_object is not None and context.active_object.mode == 'SCULPT' + + def draw(self, context): + layout = self.layout + layout.prop(self, "mode", text="Mode") + layout.prop(self, "thickness", text="Thickness") + #layout.prop(self, "offset", text="Offset") + layout.prop(self, "smoothPasses", text="Smooth Passes") + + def execute(self, context): + activeObj = context.active_object + + # This is a hackish way to support redo functionality despite sculpt mode having its own undo system. + # The set of conditions here is not something the user can create manually from the UI. + # Unfortunately I haven't found a way to make Undo itself work + if 2>len(bpy.context.selected_objects)>0 and \ + context.selected_objects[0] != activeObj and \ + context.selected_objects[0].name.startswith("Extracted_"): + rem = context.selected_objects[0] + remname = rem.data.name + bpy.data.scenes.get(context.scene.name).objects.unlink(rem) # checkear esto + bpy.data.objects.remove(rem) + # remove mesh to prevent memory being cluttered up with hundreds of high-poly objects + bpy.data.meshes.remove(bpy.data.meshes[remname]) + + # For multires we need to copy the object and apply the modifiers + try: + if activeObj.modifiers["Multires"]: + use_multires = True + objCopy = helper.objDuplicate(activeObj) + context.view_layer.objects.active = objCopy + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.boolean.mod_apply() + except: + use_multires = False + pass + + bpy.ops.object.mode_set(mode='EDIT') + + # Automerge will collapse the mesh so we need it off. + if context.scene.tool_settings.use_mesh_automerge: + automerge = True + bpy.data.scenes[context.scene.name].tool_settings.use_mesh_automerge = False + else: + automerge = False + + # Until python can read sculpt mask data properly we need to rely on the hiding trick + #bpy.ops.mesh.select_all(action='SELECT') + #bpy.ops.mesh.normals_make_consistent() + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.object.mode_set(mode='SCULPT') + bpy.ops.paint.hide_show(action='HIDE', area='MASKED') + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(type="FACE") + bpy.ops.mesh.reveal() + bpy.ops.mesh.duplicate_move(MESH_OT_duplicate=None, TRANSFORM_OT_translate=None) + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action = 'DESELECT') + bpy.ops.object.mode_set(mode='EDIT') + + #print(context.active_object) + #print(context.view_layer.objects.active) + + # For multires we already have a copy, so lets use that instead of separate. + if use_multires == True: + bpy.ops.mesh.select_all(action='INVERT') + bpy.ops.mesh.delete(type='FACE') + context.view_layer.objects.active = objCopy + else: + try: + bpy.ops.mesh.separate(type="SELECTED") + context.view_layer.objects.active = context.selected_objects[0] #bpy.context.window.scene.objects[0] #context.selected_objects[0] + except: + bpy.ops.object.mode_set(mode='SCULPT') + bpy.ops.paint.hide_show(action='SHOW', area='ALL') + return {'FINISHED'} + bpy.ops.object.mode_set(mode='OBJECT') + + # Rename the object for disambiguation + context.view_layer.objects.active.name = "Extracted_" + context.view_layer.objects.active.name + #bpy.ops.object.mode_set(mode='EDIT') + #print(context.active_object) + #print(context.view_layer.objects.active) + + # Solid mode should create a two-sided mesh + if self.mode == 'SOLID': + ''' + if self.superSmooth: + # Seleccion de borde entre malla extraida y malla original + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE', action='TOGGLE') + bpy.ops.mesh.select_non_manifold() + # Aumentar seleccion en 1 + bpy.ops.mesh.select_more() + # Invertir + bpy.ops.mesh.select_all(action='INVERT') + # Añadir al vertex group + bpy.ops.object.vertex_group_add() + bpy.ops.object.vertex_group_assign() + # Guardar referencia para más tarde + ob = context.object + group = ob.vertex_groups.active + ''' + bpy.ops.object.mode_set(mode='OBJECT') + obj = context.active_object + if self.smoothPasses > 0: # aplicar smooth inicial solo si los pases son mayores a 0 + smooth = obj.modifiers.new(name="Smooth", type='SMOOTH') + smooth.iterations = self.smoothPasses + bpy.ops.object.modifier_apply(modifier="Smooth") + solidi = obj.modifiers.new(name="Solid", type='SOLIDIFY') + solidi.thickness = self.thickness + solidi.offset = 1 # add later + solidi.thickness_clamp = 0 + solidi.use_rim = True + solidi.use_rim_only = False + bpy.ops.object.modifier_apply(modifier="Solid") + if self.superSmooth: # post-smooth para suavizarlo mucho más + #bpy.ops.object.vertex_group_select() + #smooth = obj.modifiers.new(name="Smooth", type='SMOOTH') + #smooth.iterations = self.smoothPasses + #smooth.vertex_group = group.name + #bpy.ops.object.modifier_apply(modifier="Smooth") + co_smooth = obj.modifiers.new(name="Co_Smooth", type='CORRECTIVE_SMOOTH') + co_smooth.iterations = 30 + co_smooth.smooth_type = 'LENGTH_WEIGHTED' + co_smooth.use_only_smooth = True + bpy.ops.object.modifier_apply(modifier="Co_Smooth") + + elif self.mode == 'SINGLE': + bpy.ops.object.mode_set(mode='OBJECT') + obj = context.active_object + if self.smoothPasses > 0 and self.superSmooth==False: + smooth = obj.modifiers.new(name="Smooth", type='SMOOTH') + smooth.iterations = self.smoothPasses + bpy.ops.object.modifier_apply(modifier="Smooth") + solidi = obj.modifiers.new(name="Solid", type='SOLIDIFY') + solidi.thickness = self.thickness + solidi.offset = 1 # add later + solidi.thickness_clamp = 0 + solidi.use_rim = True + solidi.use_rim_only = True # only one sided + bpy.ops.object.modifier_apply(modifier="Solid") + if self.superSmooth: # post-smooth para suavizarlo mucho más + # Seleccion de borde entre malla extraida y malla original + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT', action='TOGGLE') + bpy.ops.mesh.select_non_manifold() + # Invertir + bpy.ops.mesh.select_all(action='INVERT') + # Añadir al vertex group + bpy.ops.object.vertex_group_add() + bpy.ops.object.vertex_group_assign() + # Aplica smooth + bpy.ops.object.mode_set(mode='OBJECT') + smooth = obj.modifiers.new(name="Smooth", type='SMOOTH') + smooth.factor = 1.5 + smooth.iterations = 30 # valor máximo + smooth.vertex_group = context.object.vertex_groups.active.name # usa vertex group + bpy.ops.object.modifier_apply(modifier="Smooth") + # trick scale to close up the extracted mesh to original mesh due to smooth # not necessary now + + + elif self.mode == 'FLAT': + pass + ''' OLD + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + n = 0 + while n < self.smoothPasses: + bpy.ops.mesh.vertices_smooth() + n+=1 + #bpy.ops.mesh.solidify(thickness=0) + ''' + + # later will add close mesh bool + + # clear mask on the extracted mesh + bpy.ops.object.mode_set(mode='SCULPT') + bpy.ops.paint.mask_flood_fill(mode='VALUE', value=0) + + bpy.ops.object.mode_set(mode='OBJECT') + + # make sure to recreate the odd selection situation for redo + if use_multires: + bpy.ops.object.select_pattern(pattern=context.active_object.name, case_sensitive=True, extend=False) + + #bpy.ops.object.select_all(action = 'DESELECT') + + #context.view_layer.objects.active = activeObj + + # restore automerge + if automerge: + bpy.data.scenes[context.scene.name].tool_settings.use_mesh_automerge = True + + # restore mode for original object + bpy.ops.object.mode_set(mode='SCULPT') + bpy.ops.paint.hide_show(action='SHOW', area='ALL') + #if(usingDyntopo): + # bpy.ops.sculpt.dynamic_topology_toggle() + return {'FINISHED'} + + +classes = ( + BAS_OT_mask_extractor_apply_changes, + BAS_OT_mask_extractor_quick, + BAS_OT_mask_extractor +) \ No newline at end of file diff --git a/atelier/tools/mesh_extractor/ui.py b/atelier/tools/mesh_extractor/ui.py new file mode 100644 index 0000000..89a3a2e --- /dev/null +++ b/atelier/tools/mesh_extractor/ui.py @@ -0,0 +1,95 @@ +from bpy.types import Panel + + +def draw_mesh_extractor(layout, context): + row = layout.row(align=True) + row.ui_units_x = 6.2 # 5.8 + if not context.window_manager.bas_extractor.created: + props = context.window_manager.bas_extractor + if props.mode == 'BLENDER': + mesh = context.active_object.data + _props = row.operator("mesh.paint_mask_extract", text="Mask Extract", icon='CLIPUV_HLT') + else: + _props = row.operator("bas.mask_extractor_quick", text="Mask Extractor", icon='CLIPUV_HLT') + _props.thickness = props.thickness + _props.offset = props.offset + _props.smoothPasses = props.smooth_passes + _props.mode = props.mode + _props.superSmooth = props.super_smooth + _props.keepMask = props.keep_mask + _props.editNewMesh = props.edit_new_mesh + _props.postEdition = props.post_edition + row.popover(panel="BAS_PT_Mask_Extractor_Options", text="") + else: + row.popover(panel="BAS_PT_Mask_Extractor_Options", text="Mask Extractor", icon='MODIFIER_ON') + #row.operator("bas.mask_extractor_quick", text="Mask Extractor", icon='CLIPUV_HLT') + +class BAS_PT_Mask_Extractor_Options(Panel): + bl_label = "Options" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = "NONE" + bl_category = 'Sculpt' + bl_description = "Mask Extractor options" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + props = context.window_manager.bas_extractor + layout = self.layout + row = layout.row() + if not props.is_created: + row.prop(props, 'mode', expand=True, text="Solid") + _row = layout.column() + if props.mode == 'BLENDER': + mesh = context.active_object.data + props = _row.operator("mesh.paint_mask_extract", text="Mask Extract") + return + + elif props.mode == 'FLAT': + _row.active = False + else: + _row.active = True + row = _row.row() + row.prop(props, 'thickness', slider=True) + row = _row.row() + row.prop(props, 'super_smooth', slider=True) + row = _row.row() + row.prop(props, 'smooth_passes') + row = _row.row() + row.prop(props, 'edit_new_mesh', text="Sculpt new mesh when extracted") + row = _row.row() + row.prop(props, 'keep_mask', text="Keep original mask") + row = _row.row() + row.prop(props, "post_edition", text="Post-Edition") + row = _row.row() + row.scale_y = 1.5 + _props = row.operator("bas.mask_extractor_quick", text="Extract Mask !") + _props.thickness = props.thickness + _props.offset = props.offset + _props.smoothPasses = props.smooth_passes + _props.mode = props.mode + _props.superSmooth = props.super_smooth + _props.keepMask = props.keep_mask + _props.editNewMesh = props.edit_new_mesh + _props.postEdition = props.post_edition + else: + if props.post_edition: + col = layout.column() + col.scale_y = 1.1 + if props.extracted: + obj = props.extracted + row = col.row() + solid = obj.modifiers["Solid"] + row.prop(solid, "thickness", text="Thickness") + row = col.row() + if props.mode == 'SOLID': + smooth = obj.modifiers["Smooth"] + row.prop(smooth, "iterations", text="Smooth Passes") + if props.super_smooth: + row = col.row() + superSmooth = obj.modifiers["Co_Smooth"] + row.prop(superSmooth, "iterations", text="Super Smooth Passes") + row = col.row() + row.alert = True + row.scale_y = 1.3 + row.operator("bas.mask_extractor_apply", text="> APPLY CHANGES <") diff --git a/atelier/tools/mirror_plane/__init__.py b/atelier/tools/mirror_plane/__init__.py new file mode 100644 index 0000000..6acbc24 --- /dev/null +++ b/atelier/tools/mirror_plane/__init__.py @@ -0,0 +1,31 @@ +def register(): + from .ops import classes + from bpy.utils import register_class + + from .ui import BAS_PT_mirror_plane_options + register_class(BAS_PT_mirror_plane_options) + + for cls in classes: + register_class(cls) + + from .data import MirrorPlanePG + register_class(MirrorPlanePG) + + from bpy.types import Scene as scn + from bpy.props import PointerProperty as Pointer + scn.bas_mirrorplane = Pointer(type=MirrorPlanePG) + +def unregister(): + from bpy.utils import unregister_class + from bpy.types import Scene as scn + del scn.bas_mirrorplane + + from .data import MirrorPlanePG + unregister_class(MirrorPlanePG) + + from .ui import BAS_PT_mirror_plane_options + unregister_class(BAS_PT_mirror_plane_options) + + from .ops import classes + for cls in reversed(classes): + unregister_class(cls) diff --git a/atelier/tools/mirror_plane/__pycache__/__init__.cpython-37.pyc b/atelier/tools/mirror_plane/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..31b1c39ae0b8daf977eaf54e9d543c7e6eca63e9 GIT binary patch literal 1065 zcmY*YOK;RL5VrH)ZMF|oUgE+vmkL$AfDob&6$y!IVL?b$P)K8|isU3_oQ1BGaDgj7 z1c_g?S5Ey4oS1RCrC7>%=9}?&JoDvXG8r>md#`_G+GXrF8LQ=>@*F?2jY2TNzq3;& z9O2^5g*Rh~U!cdX5G9^xhxnPVCMtX!}rcg!IsQ0ZNsd2T|2j~wvPmF%>&d*}Ou(I*V zCC4ffz6=YM8zapnnq;qoJTFZvA+rud9vtk?vgzBbDgheEw90FlHM%XE+Ms*#%HmUk zr!NrjXN9aKDjU3%C! zDPdO3m2T6vX_QHCwl2-GvM$>!+tG_i9=bc*rQf3M`}8z$nB56;@I2A~)E;Da1lGs>Z~pi+k-_hm5kU^D)A= ztUE>45qqK&pzCp-oAY}0f)!uvh+trsZF$1&3~-}?Co!zy26suUlc15pCW#3NdK|Y>}Xu4&pXy%kjS=>4^PbZJW7g8%@oQ`8tRD e=sigs+iYvx$YgWcbkWnQ5pyM<*_qL;(biw*L+yJ2 literal 0 HcmV?d00001 diff --git a/atelier/tools/mirror_plane/__pycache__/data.cpython-37.pyc b/atelier/tools/mirror_plane/__pycache__/data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b2cd2de8b297d893b58e00b4e66b598c5919e0c1 GIT binary patch literal 1311 zcmZWp%Wm676y-}KC5y2fyG`Q0*i8YZfKjyBv@q((O6<09Y&UlLAP5X--I2_IQ=y8`)venQvX&9*E3LRY;rq#XnuVy@;s=G^CKHwXp>t_R=0CQI8e{!z^J za`5mFw@?HOFmt0YW3#Yg%T&CT+l3Q5MKA6ZZtSAW1}FDUjeP@p;Jz}zrRGaJ_Q5+b zB7cb}Mr6t0n6rxV`f{JMrjqW_*>gIrBU=u48OyJga(ypnY5fDL7{3v&95X7% zDoj(F_ooW|dfmdqL)_wbB-DsaFk0{hCM4mjH_-=i4om5mawllYxQA9)uB z_^rSY0=RWc8YuI z>J3?1$i{>5;kQr5r{mp&$I|}k=UBQ2j}P}xzd4MCt(O&`V#>3s&RD6PqraL~vR`pJ z%>+uN$Fx>RPc&!sWrZO%Wrlf)Y(aUTVzuYAmbO6Rve^uO7wbCSi!R=7Xe{Zd`>p4o z&73ejP>bM%HGE3L)>ICKW=X+-=8Me<%NY-6xa)KJR?TE@a&&O?G;-#@{i@bqJM%B} z`g=zgqZ9wRvs4d+s|j3$lk>Ek)38Nlv;ToczK)UKRabONHg%dsR!X}_U+@j3(5d&P zDzs=IeL_^D36TS$!ftYP9uo3Pljg04s}STWA!%8%I#o3ngv3GHY0?&i_Y}6P2tIKm zzW0`v=s_Xc=^qBaugnzrS4_T2a0sn;2xP;cNq0$|E#*)Qf(JpjigSh6;}AqB6LmOd>rVfyxezLtC% literal 0 HcmV?d00001 diff --git a/atelier/tools/mirror_plane/__pycache__/ops.cpython-37.pyc b/atelier/tools/mirror_plane/__pycache__/ops.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ada4f6c5206b79c2b068feb1ff3f81849c5b9761 GIT binary patch literal 4521 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 Xyu(Jmy(CY<)f<;xwedUUj;8(}NEbME literal 0 HcmV?d00001 diff --git a/atelier/tools/mirror_plane/__pycache__/prop_fun.cpython-37.pyc b/atelier/tools/mirror_plane/__pycache__/prop_fun.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e182604f88620c5521fc2df4be070e569dfed552 GIT binary patch literal 587 zcmY*XO>Yx15FLAyG?XMjyL6G265vKJ7AiHiD2Q5(1+-*i{S=VV4r%jqO0gvjg#MmARm%C!_IXF#q&9u9r3ULvPCH%52L>8;4oy%GNr!R@=nLREd~klaR~uDAUS|YySsq zSmb^)e8c8WSjRq0*ua=k>Qi>1({--CvlA&47q&Oih2p8twR$MAm;Ki-n9FQ#ykjZn zS~)iGLgvEGvO*fqyw=jOeG<%AZIWf7`be~07=#qbuAvqdZ(sn@ARS^`m>sAS-c{=K zFxhs3KIkNYFQ8fk!ubY@e%+}=rnqoD(^A{A{hxxqT8YfZ)HV^pJ_;cyunUhdE1w-u z+6k9bAfek;AD&R8*T7?+U@eYpU|!L!3VVPM;CZ#}mxCjmwQUom$6jii&qE;aZO+sO{(rDwExNFus+nKcz zTk8|O=E4n5$q|XahT~j0<%qa(;+wT&*{NWxotf`9-~7JceDm;Hy`GX%4+Z!^nxnC-jF@df!bQUEki_-`u!tfULF=X$qtP5%|{OQ-5HDa@<8-JhF1swMKSspW7E`XON`a?UtC!i0QLZ?otns^2D3uyLAnDgWaAYhV9M&WP9s1wzpqnyEDgjUC%r0 zU0}D19M6oIH5KaKWA9JK$rNj{yJL(YI8Yz#9GH_D#!)93-kLi;`#j5{q@!Tm$@pn@ zY0d>08Zxww=7u&rJ7`-(~0?HWT5d6 zlkQMsUz39Sd!#4SGGE$pJlvurIjOrL9S4QHCrExOn!n13KtE?Tx<71!^iP>sR#``Bf1QPj?VX{Gw{J6*~H6fM!6pY`H{2ZEpUao%`94anV z@N_F!P1B7q3xj7O>_LiywU{T2%jTBoCoD8C)fE)nTY2zFpxQD@GZlo4!6O!|W;~9# z9KTS35v+pQ;}rCxm2{vTCc~3D1XoX?LfFFudsYKCa19fyhHI8*nUCNGSe`-g0`_dc z?3z=c)qMr;KT^iG GX8#KTSrF3z literal 0 HcmV?d00001 diff --git a/atelier/tools/mirror_plane/data.py b/atelier/tools/mirror_plane/data.py new file mode 100644 index 0000000..12c2208 --- /dev/null +++ b/atelier/tools/mirror_plane/data.py @@ -0,0 +1,14 @@ +from bpy.types import PropertyGroup, Object +from bpy.props import BoolProperty, FloatVectorProperty, PointerProperty +from . prop_fun import * + + +class MirrorPlanePG(PropertyGroup): + # MIRROR PLANE + use_world_origin : BoolProperty(default = False, name="Use World Origin") + show : BoolProperty(default=False, name="Show mirror plane", update = update_mirror) + created : BoolProperty(default=False, name="Mirror Plane Created") + offset : FloatVectorProperty(name="", description="", default=(1, 1), step=1, precision=2, options={'ANIMATABLE'}, subtype='XYZ', unit='LENGTH', size=2, update=None, get=None, set=None) + mirror_object : PointerProperty(type=Object, name="Mirrow Plane Object") + source_model : PointerProperty(type=Object, name="Source Object") + color : FloatVectorProperty(name="Color for the mirror plane", subtype='COLOR', default=(0.2,0.3,1,0.1), size=4, min=0, max=1, description="Change Color of the mirror plane", update=update_mirror_plane_color) diff --git a/atelier/tools/mirror_plane/ops.py b/atelier/tools/mirror_plane/ops.py new file mode 100644 index 0000000..9329bcc --- /dev/null +++ b/atelier/tools/mirror_plane/ops.py @@ -0,0 +1,247 @@ +# Copyright (C) 2019 Juan Fran Matheu G. +# Contact: jfmatheug@gmail.com + +import bpy +from bpy.types import Operator +from bpy.props import FloatVectorProperty, PointerProperty, BoolProperty, FloatProperty +import numpy as npy +import mathutils +from mathutils import Vector + + +class BAS_OT_mirror_plane_delete(Operator): + bl_idname = "bas.mirror_plane_delete" + bl_label = "Delete Mirror Plane" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + obj = context.active_object + mirror = context.scene.bas_mirrorplane + mirror_object = mirror.mirror_object + if not mirror_object: + mirror.created = False + mirror.show = False + return {'FINISHED'} + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + mirror_object.select_set(state=True) + context.view_layer.objects.active = mirror_object + bpy.data.objects.remove(mirror_object) + mirror.created = False + mirror.show = False + obj.select_set(state=True) + context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='SCULPT') + return {'FINISHED'} + +from ...utils.others import ShowMessageBox +class BAS_OT_mirror_plane(Operator): + bl_idname = "bas.mirror_plane" + bl_label = "Mirror Plane" + bl_options = {'REGISTER', 'UNDO'} + + oldDim : FloatVectorProperty(default=[0 , 0, 0], size=3) + #auto_update : BoolProperty(default = True, name="Auto Update", description="Update mirror plane in real time") + useWorldOrigin : BoolProperty(default = False, name="Use World Origin") + #maxLoops : FloatProperty(default=24) + #loop : FloatProperty(default=0) + #color = FloatVectorProperty(name="Color for the mirror plane", subtype='COLOR', min=0, max=1, description="Change Color of the mirror plane") + #mirror_plane : PointerProperty(type=bpy.types.Object, name="Mirrow Plane Object") + #source_model : PointerProperty(type=bpy.types.Object, name="Mirrow Plane Object") + + @classmethod + def poll(cls, context): + return context.active_object != None and context.mode == 'SCULPT' + + def modal(self, context, event): + mirror = context.scene.bas_mirrorplane + if not mirror.show: + #context.window_manager.event_timer_remove(self._timer) + # OCULTAR MIRROR + try: + mirror.mirror_object.hide_viewport = True + except: + pass + return {'FINISHED'} + if(context.mode == "SCULPT"): + sculpt = context.tool_settings.sculpt + if not sculpt.use_symmetry_x: # or not context.scene.created + mirror.show = False + try: + mirror.mirror_object.hide_viewport = True + except: + pass + ShowMessageBox("You need to turn ON the X symmetry", "Can't do this!", 'ERROR') + return {"CANCELLED"} + + #if self.loop > self.maxLoops: + obj = mirror.source_model + dim = obj.dimensions + + if self.oldDim[1] != dim[1] or self.oldDim[2] != dim[2]: + #print(dim) + #print(self.oldDim) + #print("running") + self.oldDim = dim + offset = mirror.offset + bb = obj.bound_box + + if self.useWorldOrigin: + loc = (0, 0, 0) + else: + loc = obj.location + + bb = obj.bound_box + #mw = obj.matrix_world + A = npy.array(bb[0]) # [x, y, z] + B = npy.array(bb[1]) # [x, y, z] + C = npy.array(bb[2]) # [x, y, z] + D = npy.array(bb[3]) # [x, y, z] + E = npy.array(bb[4]) # [x, y, z] + F = npy.array(bb[5]) # [x, y, z] + G = npy.array(bb[6]) # [x, y, z] + H = npy.array(bb[7]) # [x, y, z] + + v = (A+B+C+D+E+F+G+H)/8 + + mirror.mirror_object.location = (loc[0], v[1], v[2]) + mirror.mirror_object.dimensions = (0, dim[1] + offset[0], dim[2] + offset[1]) + #self.loop = 0 + + if mirror.mirror_object.hide_viewport: + mirror.mirror_object.hide_viewport = False + + #self.loop += 1 + + #mirror.mirror_plane.dimensions = mirror.source_model.dimensions + self.offset + #mirror.mirror_plane.dimensions = (0, dim[1] * 2 + offset[1], dim[2] * 2 + offset[2]) + else: + if not mirror.mirror_object.hide_viewport: + mirror.mirror_object.hide_viewport = True + return {'PASS_THROUGH'} + + def invoke(self, context, event): + mirror = context.scene.bas_mirrorplane + if mirror.mirror_object is None: + self.execute(context) + return {'FINISHED'} + + try: + mirror.mirror_object.hide_viewport = False + except: + pass + # ACTIVAR HANDLER + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + def execute(self, context): + mirror = context.scene.bas_mirrorplane + #self.offset = mathutils.Vector(self.offset) + + try: # Si ya existe + if mirror.mirror_object: + # Sólamente activar y actualizar tamaño + #plane.dimensions = obj.dimensions + self.offset + #if context.scene.show: + # self.mirror_object. + # pass + #else: + # pass + return {'FINISHED'} + except: + pass + + # Crear plano + obj = context.active_object + mirror.source_model = obj + bpy.ops.object.mode_set(mode='OBJECT') + #col = bpy.ops.outliner.collection_new(nested=True) + if mirror.use_world_origin: + loc = (0, 0, 0) + else: + loc = obj.location + + bpy.ops.mesh.primitive_plane_add(size=2, enter_editmode=False, align='WORLD', location=loc, rotation=(0, 1.5708, 0)) + plane = context.active_object + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + plane.scale[0] = 0 + + plane.name = "Mirror_Plane" + plane.color = mirror.color # (0.2,0.3,1,0.1) # + plane.display.show_shadows = False + offset = mirror.offset + + ''' + bb = obj.bound_box + top = npy.array((bb[0][0] , bb[0][1], bb[0][2])) # -1, -1, 0 [x, y, z] (local, escala aplicada) + bottom = npy.array((bb[1][0], bb[1][1], bb[1][2])) # 1, -1, 0 [x, y, z] (local, escala aplicada) + back = npy.array((bb[3][0] , bb[3][1], bb[3][2])) # -1, -1, 0 [x, y, z] (local, escala aplicada) + height = npy.linalg.norm(top-bottom) + depth = npy.linalg.norm(top-back) + plane.dimensions = (0, depth + offset[1], height + offset[2]) + ''' + ''' + mesh = obj.data + verts = mesh.vertices + verts[0].co = top + verts[1].co = bottom + verts[3].co = back + ''' + bb = obj.bound_box + #mw = obj.matrix_world + + A = npy.array(bb[0]) # [x, y, z] + B = npy.array(bb[1]) # [x, y, z] + C = npy.array(bb[2]) # [x, y, z] + D = npy.array(bb[3]) # [x, y, z] + E = npy.array(bb[4]) # [x, y, z] + F = npy.array(bb[5]) # [x, y, z] + G = npy.array(bb[6]) # [x, y, z] + H = npy.array(bb[7]) # [x, y, z] + + v = (A+B+C+D+E+F+G+H)/8 + + plane.location = (loc[0], v[1], v[2]) + plane.dimensions = (0, obj.dimensions[1] + offset[0], obj.dimensions[2] + offset[1]) + self.oldDim = obj.dimensions + + + #plane.parent = obj + plane.hide_select = True + plane.hide_viewport = False + mirror.mirror_object = plane + + context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='SCULPT') + + if mirror.show == False: + mirror.show = True + + context.space_data.shading.color_type = 'OBJECT' + mirror.created = True + + + #bb = obj.bound_box + #top = npy.array((bb[1][0] , bb[1][1], bb[1][2])) # -1, -1, 0 [x, y, z] (local, escala aplicada) + #bottom = npy.array((bb[5][0], bb[5][1], bb[5][2])) # 1, -1, 0 [x, y, z] (local, escala aplicada) + # Primera forma de calcular la distancia + #import numpy as npy + #dist = npy.linalg.norm(top-bottom) + # Segunda forma de calcular la distancia + #import numpy as npy + #a = npy.array((xa ,ya, za)) + #b = npy.array((xb, yb, zb)) + #dist = npy.linalg.norm(a-b) + # Tercera forma de calcular la distancia + #from math import dist + #dist([xa, ya, za], [xb, yb, zb]) + + # Al calcular la distancia aplicar esta sobre las dimensiones del plano + + return {'FINISHED'} + + +classes = ( + BAS_OT_mirror_plane, + BAS_OT_mirror_plane_delete +) diff --git a/atelier/tools/mirror_plane/prop_fun.py b/atelier/tools/mirror_plane/prop_fun.py new file mode 100644 index 0000000..9b8188e --- /dev/null +++ b/atelier/tools/mirror_plane/prop_fun.py @@ -0,0 +1,14 @@ +import bpy + + +def update_mirror(self, context): + if self.show: + bpy.ops.bas.mirror_plane('INVOKE_DEFAULT') + return + +def update_mirror_plane_color(self, context): + try: + self.mirror_object.color = self.color + except: + pass + return diff --git a/atelier/tools/mirror_plane/ui.py b/atelier/tools/mirror_plane/ui.py new file mode 100644 index 0000000..15f7426 --- /dev/null +++ b/atelier/tools/mirror_plane/ui.py @@ -0,0 +1,76 @@ +from bpy.types import Panel + + +class BAS_PT_mirror_plane_options(Panel): + bl_label = "Mirror Plane Options" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = "NONE" + bl_category = 'Sculpt' + bl_options = {'DEFAULT_CLOSED'} + bl_ui_units_x = 16 + + def draw(self, context): + mirror = context.scene.bas_mirrorplane + layout = self.layout + split = layout.split() + + col = split.box().column(align=True) + col.box().label(text="Mirror Plane Settings") + col.separator(factor=2) + col.scale_y = 1.1 + + row = col.row(align=True) + row.prop(mirror, "offset") + + col.separator() + + col.prop(mirror, "color") + + col.separator() + + if mirror.created: + col.operator("bas.mirror_plane_delete", text="Delete Mirror Plane") + else: + col.prop(mirror, "use_world_origin") + col.separator() + row = col.row(align=True) + row.scale_y = 1.2 + _props = row.operator("bas.mirror_plane", text="Create Mirror Plane") + _props.useWorldOrigin = mirror.use_world_origin + + + col = split.box().column() + + sculpt = context.tool_settings.sculpt + + #col.alignment = 'RIGHT' + col.label(text="Lock") + + #col = split.column() + + row = col.row(align=True) + row.prop(sculpt, "lock_x", text="X", toggle=True) + row.prop(sculpt, "lock_y", text="Y", toggle=True) + row.prop(sculpt, "lock_z", text="Z", toggle=True) + + #split = layout.split() + + #col = split.column() + #col.alignment = 'RIGHT' + col.label(text="Tiling") + + #col = split.column() + + row = col.row(align=True) + row.prop(sculpt, "tile_x", text="X", toggle=True) + row.prop(sculpt, "tile_y", text="Y", toggle=True) + row.prop(sculpt, "tile_z", text="Z", toggle=True) + + #layout.use_property_split = True + #layout.use_property_decorate = False + row = col.row(align=True) + row.prop(sculpt, "use_symmetry_feather", text="Feather") + row = col.row(align=True) + row.column().prop(sculpt, "radial_symmetry", text="Radial") + row.column().prop(sculpt, "tile_offset", text="Tile Offset") diff --git a/atelier/tools/reference_system/__init__.py b/atelier/tools/reference_system/__init__.py new file mode 100644 index 0000000..445e1af --- /dev/null +++ b/atelier/tools/reference_system/__init__.py @@ -0,0 +1,48 @@ +def register(): + from bpy.utils import register_class + + from .previews import register_previews + register_previews() + + from .data import ReferenceSystemPG, ReferenceSystemTempPG, ReferenceImagePG + register_class(ReferenceSystemPG) + register_class(ReferenceSystemTempPG) + register_class(ReferenceImagePG) + + from bpy.types import WindowManager as wm, Scene as scn, Image as img + from bpy.props import PointerProperty as Pointer + wm.bas_references = Pointer(type=ReferenceSystemTempPG) + scn.bas_references = Pointer(type=ReferenceSystemPG) + img.ref = Pointer(type=ReferenceImagePG) + + from .ui import classes as UI_CLASSES + for cls in UI_CLASSES: + register_class(cls) + + from .ops import classes as OPS_CLASSES + for cls in OPS_CLASSES: + register_class(cls) + +def unregister(): + from bpy.utils import unregister_class + + from .ops import classes as OPS_CLASSES + for cls in reversed(OPS_CLASSES): + unregister_class(cls) + + from .ui import classes as UI_CLASSES + for cls in reversed(UI_CLASSES): + unregister_class(cls) + + from bpy.types import WindowManager as wm, Scene as scn, Image as img + del wm.bas_references + del scn.bas_references + del img.ref + + from .data import ReferenceSystemPG, ReferenceSystemTempPG, ReferenceImagePG + unregister_class(ReferenceSystemTempPG) + unregister_class(ReferenceSystemPG) + unregister_class(ReferenceImagePG) + + from .previews import unregister_previews + unregister_previews() diff --git a/atelier/tools/reference_system/__pycache__/__init__.cpython-37.pyc b/atelier/tools/reference_system/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..697779da9128f550ed300349da2c4f8a65751035 GIT binary patch literal 1482 zcmZ`(%Z?N`6t(MJ)sLBBh6M89rEF?5LWm7fgcycEh{B|y86lyHil%KwCD~PlUCq!+ z*NSZO57|b_7i5`_$imyK_=T);ue+;ZT1u7b*gn3#*KVKFcL#%nz;pBQANj3-kbiK{ zJq~p4z^C^B7-94m@|-Y-xzJMPjY;ZHVa7gvKo`D4`1IcZj50?MNn1h*bD1X`>9&-T z1%0<~If&@lh~7rT zUl2jy#L3ZnI2=i-87h8K=!UC&DkfULr%QTgP1lOQDfp>QU971@}a9h21JEO+BKL;s|d*$0rj$+ zsiB}&Jsb~TPIRuiLu%tf-#D{E4RF?lxU_HU+G?A1oKtCBJuQt}$P*K;&SR3}hxy?T z2jlU#W8+SRHoYH5w2cAs;C>CO&F_6ogFk>6kLs`Gf_8LQ&mD|>qF(*UR{>wMOid? zzF*Hx)Lk}SzWxe;(7w~BfwM;=e4*_xUtkkEmr!*D;?~Z=-*4@lKVk5H&Ut&G@xVRQ zD%0uQ^7@+9t|xr(mkZj(DDcll)OQd?nJ)tLnvB3paT|blycfyBhc9^10|x}wQ=uHT zk=#PBK$U3a#c}WY&Ero+%&Ej_@hM1%!(T8>?}l72^^v!_XujcK;cSHO4HpF|;Z`+@l`8YpwLDLJ1wEj#Y>q Ze3TdEu59w?8(FcL;J2;p(9Y4t{{Ti0Qa1nq literal 0 HcmV?d00001 diff --git a/atelier/tools/reference_system/__pycache__/data.cpython-37.pyc b/atelier/tools/reference_system/__pycache__/data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0163c66f9fb9cfb266e583946d3e3596ac1eca69 GIT binary patch literal 4275 zcmahM*>c;+ksx@0mq?wv_IhpjHodmvI9?~a>#R;vqD_-pl6JOOtU@5pNWvNfm;q>6 zcuH0Bst-w3z9x_Jv{iZ1FXSoR4S=L%r$Q7P)5mo8%=C0Owo0XZ0)Eeb`lr!bO(gz} zNc>B|U<3Z%p8>E0Q|!b@=_{jTUsVuK+NoZGsVrruN11*GXe!XMy~J*UrCH|h36|l? z-_*Vac$R5^YlP&iQ!M>5!P7j$GG_{4S;Uxzj-Dj}F94opMOI=9gqI1gOz}m+m)J5ZagD8< zrC`n~(bm`(K+8q6FM*b4S9pP4B|Tq}Oo?41t6xXhb#{a4?!*#d&YhT+)t#j19rNs$KFO7-@Qz*KSAjHELS`HkPGe%dH2+V^Y8J|cz-u2 z)Lol#q3@givD9`N9b|vQ#F-+g z*&s#oW!fUW6E7I{0&U0??S9${?>MckUm3X{VCA#j32wjUd^~emCvn|!baUuWOj{qh zj0bOf$Cjs`Sg<(o5ccuZo#?&`(>z~TLmzQ5Q{o}-fa||ZJYQ$RJOPua(m&?&YO5+a zHQN!n{2rV!EXN3q8)ST8x-z{5ffX%pHZ{nH8$gkmt2df83dLBifP0arWf@}#)*$!R z<6$6x2nI~JUAc+Jqa)9UVbbFteVL~yFnkyXl^I>%c5PS4^me=1?!c+0QO`wij!9l% zUL{5~8!i<6A+s{&Spk>o$Z`OfA1NqgWoFM9qK%*sS|x*Ei4;!35om+ouj{f9PNX@q z6h&pM0+$?xuq@z0BmVgcOg{fBfS^3LxGp#bHwK`7=n9=Q>$nBgbhu-;VTQPaU>eS{ z96Eqv2CnR!&8t1OhVLEjdBGKkrEo~P!+4{qQw^~|RjsCFs#&Y|0P;hh?t4GK*=+2# z3?$QLfJ|n)*6P(d@aB%sMJQJcGr@d}VGw zHf`(38G&xt|7o6?Tl)N=nJ5dXEG^Yudmn&14*ki6c7Ql_d#l~+wcp6hmh1cO=z`lJ zD-5X@;7@$Mv3gbJ_u8FCzuoF>Heu`vD{vj(v`Gu(O6^^((`#%4Q+o#`-x@|N@x$D) z>`-Zk)Yp$rFRb-IILCuMe}=M4b33tbG6uIfSc?Xi;zKUY1;IF!ZB@%sGYl*Wh9UC? z-s*{sZR8s^ej zcJqultA36bXHZ$*cP)&YSdmn%(UDxP+pg)qqt<82DKs2^ij+tqM*oozE%1rJe@?c) zAHby(-r!YzusI&@n7%paxF%foqrsNV9ju*ocj7?uhvmhj!LujdJspGv$s54_!O91l zKDR9{y2FV*_6I(!*c(hMf#FeA9)KS6$@o-c!3F$#djRsuq=HWo-tbQ*)uLKaD#;aP zC0S7xlqCf)daf$?RFpM%KrU55=@6=wBx$|S_hG8rKr{OQKJELcUrJH80qrWw60QNB z!LBdD=lVWYrjD!vUB@DH9ZPa?n)Ji213Hc?Q{D zoRg+LMr&Y3YHc?Ac?cOU94MTp(cSE8wVz{V?c)We=)h8fm9F`L&nmj7zkw?g z>92SoHfNaXWvi-*El?u15$qtSA*dt3pb$+2y9lQ78jjjX!vjTGPXmyT!h@(60sv!d z1I6l}Y5b|-R_XXtEJ+xC0;muQA%>p-^oc+`42(33C3ApfAV>#*c;B zTnHeTyU6fkVTG--HENo_0BV+f$*!=g>?;biYd}%i*O52Z3A+(tI$#<|hn~hC2_beH zco}x*lK#6uo%wK&WG?$~AE+vO5NWN_jE4~h9~SHpg#BY#nZ`KvH2b?@#3)h2Fqv4a z-Y-IgPNP?S1rEZdncu#83 zbHcZ-S0&%6dinFPA5CjsO4v literal 0 HcmV?d00001 diff --git a/atelier/tools/reference_system/__pycache__/draw.cpython-37.pyc b/atelier/tools/reference_system/__pycache__/draw.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b79e97c371dcd47802abff5cc5535d99695a7234 GIT binary patch literal 4173 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 literal 0 HcmV?d00001 diff --git a/atelier/tools/reference_system/__pycache__/ops.cpython-37.pyc b/atelier/tools/reference_system/__pycache__/ops.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6dcb3050ad7f4770fbf96f317cd84187f5be4d26 GIT binary patch literal 11630 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 bK(mmM|2e=c{x24e3P0BLf^EQT8UFu2HY-m$ literal 0 HcmV?d00001 diff --git a/atelier/tools/reference_system/__pycache__/previews.cpython-37.pyc b/atelier/tools/reference_system/__pycache__/previews.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa50c2242b530f3251fae7bdd6cedf60f26e3c90 GIT binary patch literal 1471 zcmZuxON$#v5bo~jc}XK#UgHIu9E2eU`LKlGLt>1{+G~ddvLs%DK}<5tPPgRI&dc2` zYj@Lga(+O5f^bGRgGu!W?K&hqbahofs=j*7lfj^eU_AKl_v8;Bp}(xK z8w8jyV9FyP3^81w>2}3a%m{M|QaV!y+dW#)(w(}%yUhC;O+Dtb09K!ctOskr@Clmq z-+?A{{0;-1U~^mW85g|Dc#_pc!84WDm4t(!5&TWgS2DBr?XWK#m``EKF%X5;c!OF% zrBOl{0qSU^D03bG9(VH@U0E{518Z&$rh-eO`HC~_vZiyxfNaz_6_w4Q*wT|6s zsdAWe2eq!I+FejMKZ4agLCRxP(|z<7T+md!SNEveD$uSD0dJ%o&>89wiZoj6>0kzSLI1BWk#@A3bp9mPC-7ww zT;6_GEz563-Eg5U##vevr)hR}EL@0Nmy^r=lPs+&sIV~6{BQ{8x?3X|$^y+X34&+YAX?O2%zOUMWLrX=szG zSH|P#P;t`u1uV8@+@fBA)&MG9u5x=LZ5j|v$iWRRQpF8j)Olr`Iah`jb;^vNXLXh2 zY%(xZ@?vIukQF}%ORLiK?8_vFYL_Ow@({4t-kbLG68uOk8fnO^GB`K1N=t5nSzhoC zCAIpEt6nY3(+`TatJiy-_A94nqAsoH+c0je+Rl&I9^RDSTeXc_bnuLh%TH zgok9046!{O!Ogp*>$v;yZW0Qs#5kwWrev9SaWx@G2TPR~vdd%Fn_Yhh`{o0n-T$RS a8$2p&wk-H#`wNm0kN`S6!jThrf%6aCY={*A literal 0 HcmV?d00001 diff --git a/atelier/tools/reference_system/__pycache__/prop_fun.cpython-37.pyc b/atelier/tools/reference_system/__pycache__/prop_fun.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48b890544bd9a94d6bbc65c7114915dd3d2fd84b GIT binary patch literal 677 zcmb_a%}N6?5Kgk&cKxXodlB*AbwNbLiy~rGdhk&2?@|KGx=D3wnuH_^7Cn0PK?I*@ zubzAWFP@yVY^mVUftgHZl9_M5bT?+6^!aF)UCYF~Wiu z)W|MvW4DWNsyK$|IypyCtl) zl(e+txX8J=$_k+!bxH|KXAlxQwvLaeHcBUhLT&@*^+H2gK8G`@u-80A5g3H)c$KuU zSpxrGnXFj{%_md}l|6VWO$bel?``T}`t2$)2Qen5;SYFl6Aq`w|Chf8swyl){`WvD j5Q&3nOFx|Pfw_wQeaxh6bU0G15d6r09_ukiBx?Ksw$i1e literal 0 HcmV?d00001 diff --git a/atelier/tools/reference_system/__pycache__/ui.cpython-37.pyc b/atelier/tools/reference_system/__pycache__/ui.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d00f411bff39f49a8b1e780341d4455aa82cc521 GIT binary patch literal 5284 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 P4O!6%zf^(>J~RFgRCr@_ literal 0 HcmV?d00001 diff --git a/atelier/tools/reference_system/data.py b/atelier/tools/reference_system/data.py new file mode 100644 index 0000000..f2b3d26 --- /dev/null +++ b/atelier/tools/reference_system/data.py @@ -0,0 +1,98 @@ +from bpy.types import PropertyGroup, Image as img +from bpy.props import * +from . previews import enum_previews_from_directory_items +from . prop_fun import * + + +class ReferenceSystemTempPG(PropertyGroup): + # REFERENCE PREVIEWS + previews_dir : StringProperty( + name="Folder Path", + subtype='DIR_PATH', + default="" + ) + previews : EnumProperty( + items=enum_previews_from_directory_items, + ) + get_references_from : EnumProperty( + items=( + ('SINGLE', "Single Image", ""), + ('FOLDER', "Folder", "") + ), + default='FOLDER' + ) + ### + ui_show_label : BoolProperty(description="Show Label Settings", default=False) + ui_show_outline : BoolProperty(description="Show Outline Settings", default=False) + + hide_all : BoolProperty(name="Hide All references", description="Hide references", default=False, update=update_references_hide_all) + lock_all : BoolProperty(name="Lock All references", description="Lock references", default=False, update=update_references_lock_all) + + image : PointerProperty(name="Image Reference", type=img) + moving_reference : BoolProperty(default=False) + keep_in_actual_mode : BoolProperty(default=False, name="Keep reference image in actual mode", description="This will allow you to restrict your references to be just drawing in the actual mode") # not button prop, this is ALL or mode name if it's true + name : StringProperty(name="Reference Name", default="Reference Name") + image_path : StringProperty(name="Image Path", subtype='FILE_PATH') + use_label : BoolProperty(default=False, name="Use Label") + label_text : StringProperty(name="Label Text", default="Label Text") + label_text_color : FloatVectorProperty(name="Label Text Color", subtype='COLOR', default=(1,1,1,1), size=4, min=0, max=1, description="Label Text Color") + label_color : FloatVectorProperty(name="Label Color", subtype='COLOR', default=(0,0,0,.5), size=4, min=0, max=1, description="Label Color") + outline_color : FloatVectorProperty(name="Label Color", subtype='COLOR', default=(0,0,0,.5), size=4, min=0, max=1, description="Label Color") + use_outline : BoolProperty(default=False, name="Use Outline") + label_text_size : IntProperty(default=20, min=0, max=128, name="Label Text Size", description="Size for label text") + label_thickness : FloatProperty(default=20, min=0, max=100, name="Label Thickness", description="Thickness for image label") + label_text_align : EnumProperty ( + items=( + ('LEFT', "Left", "", 'ALIGN_LEFT', 0), + ('CENTER', "Center", "", 'ALIGN_CENTER', 1), + ('RIGHT', "Right", "", 'ALIGN_RIGHT', 2) + ), + default='CENTER', name="Label Text Alignment", description="Label Text Alignment" + ) + label_align : EnumProperty ( + items=( + ('TOP', "Top", "", 'ALIGN_TOP', 0), + #('MIDDLE', "Middle", "", 'ALIGN_MIDDLE'), + ('BOTTOM', "Bottom", "", 'ALIGN_BOTTOM', 1) + ), + default='TOP', name="Label Text Alignment", description="Label Text Alignment" + ) + label_direction : EnumProperty ( + items=( + ('HORIZONTAL', "Horizontal", ""), + ('VERTICAL', "Vertical", "") + ), + default='HORIZONTAL', name="Label Direction", description="Label Direction" + ) + label_text_padding : FloatProperty(default=10, min=0, max=50, name="Label Text Padding", description="Padding for label text") + + +class ReferenceSystemPG(PropertyGroup): + data_base : StringProperty(name="References Data Base Path", subtype='FILE_PATH') + is_using_references : BoolProperty(default=False, name="Is this project using references?") + num_of_references : IntProperty(default=0, name="Number of references") + save_references_mode : EnumProperty (items=(('INTERNAL', "Internally", ""),('EXTERNAL', "Externally", ""),), default='INTERNAL', name="Save References Mode") + + +# REFERENCE PROPERTIES +class ReferenceImagePG(PropertyGroup): + uuid : StringProperty(default='') # Reference Unic ID + is_reference : BoolProperty(default=False) # Is this image a reference image? + mode : StringProperty(default='ALL') # ALL is visibility for all modes, if not it's 'OBJECT', 'EDIT', etc... + signal : StringProperty(default='') # empty it's ok, visible, H is hidden, R is remove, etc... + is_locked : BoolProperty(default=False) + position : FloatVectorProperty(default=(0,0), size=2) # (X, Y) + size : FloatVectorProperty(default=(0,0), size=2) # Width x Height + use_transparency : BoolProperty(default=False) + use_label : BoolProperty(default=False) + label_color : FloatVectorProperty(default=(1, 1, 1, .5), size=4, subtype='COLOR', min=0, max=1) + label_thickness : FloatProperty(default=20, min=10, max=100) + label_align_to : StringProperty(default='TOP') + label_text : StringProperty(default="Label Tetxt") + label_text_size : IntProperty(default=20) + label_text_align : StringProperty(default='CENTER') + label_text_color : FloatVectorProperty(default=(0, 0, 0, 1), size=4, subtype='COLOR', min=0, max=1) + use_outline : BoolProperty(default=False) + outline_color : FloatVectorProperty(default=(1, 1, 1, .5), size=4, subtype='COLOR', min=0, max=1) + name : StringProperty(default="Reference Name") + in_front : BoolProperty(default=True) diff --git a/atelier/tools/reference_system/draw.py b/atelier/tools/reference_system/draw.py new file mode 100644 index 0000000..4e04025 --- /dev/null +++ b/atelier/tools/reference_system/draw.py @@ -0,0 +1,161 @@ +# Copyright (C) 2019 Juan Fran Matheu G. +# Contact: jfmatheug@gmail.com +from ...utils.draw2d import Draw_Text, Draw_Image_Texture, Draw_Image, Draw_2D_Rectangle, Draw_2D_Line, Draw_2D_Point +from ...utils.geo2dutils import is_inside_2d_rect, Vector +from bpy.props import StringProperty, BoolProperty, FloatProperty, FloatVectorProperty, IntProperty, EnumProperty +from bpy.types import PropertyGroup +import blf + + +def Reference_Events(self, context, event): + # Cursor is inside my reference? + if self.moving: + #print("Moving") + self.ref.position = Vector((self.mousePos[0]-self.ref.size[0]/2, self.mousePos[1]-self.ref.size[1]/2)) - self.offset + if (event.type not in {'LEFTMOUSE'} and event.value not in {'CLICK_DRAG', 'PRESS'}) or event.type == 'MIDDLEMOUSE': + context.window_manager.bas_references.moving_reference = False + self.moving = False + else: + return True + elif is_inside_2d_rect(self.mousePos, *self.ref.position, self.ref.size[0], self.ref.size[1]): + #print("Hovering") + if event.type == 'LEFTMOUSE': + off = self.mousePos - Vector(self.ref.position) + self.offset = Vector((off[0]-self.ref.size[0]/2, off[1]-self.ref.size[1]/2)) + self.moving = True + context.window_manager.bas_references.moving_reference = True + return True + #elif event.type == 'S': + return False + +def interactive_draw_reference_callback_px(self, context): + try: + if self.dragging: + Draw_Text(100, 50, "Ctrl: Scale from center | Shift: Keep aspect ratio", 32, 0) + if self.using_ctrl: + #if self.wider: + height = (self.mousePos[1] - self.midPoint[1]) * 2 + width = height * self.proportion + #else: + #width = (self.mousePos[0] - self.midPoint[0]) * 2 + #height = width * self.proportion + + self.center = Vector((self.midPoint[0] - width/2, self.midPoint[1] - height/2)) + Draw_Image_Texture(self.image, self.center, width, height) + else: + if self.using_shift: + #if self.wider: + height = self.mousePos[1] - self.firstPos[1] + width = height * self.proportion + #else: + #width = self.mousePos[0] - self.firstPos[0] + #height = width * self.proportion + else: + width = self.mousePos[0] - self.firstPos[0] + height = self.mousePos[1] - self.firstPos[1] + Draw_Image_Texture(self.image, self.firstPos, width, height) + + if not self.using_ctrl: + # TEXT + #blf.size(0, self.textSize, 72) + #textsize = blf.dimensions(0, self.text) + #if abs(textsize[0]+20) < abs(width) and abs(textsize[1]+20) < abs(height): + # alpha = self.textColor[3] + #elif abs(textsize[0]) > abs(width) or abs(textsize[1]) > abs(height): + # alpha = 0 + #else: + # alpha = min(clip(abs(width), textsize[0]/abs(width), (20+textsize[0])/abs(width)), clip(abs(height), textsize[1]/abs(height), (20+textsize[1])/abs(height))) + #textY = self.firstPos[1] + (height - textsize[1]) * .5 + #textX = self.firstPos[0] + (width - textsize[0]) * .5 + #Draw_Text(textX, textY, self.text, self.textSize, 0, self.textColor[0], self.textColor[1], self.textColor[2], alpha) + + Draw_2D_Line(Vector((self.firstPos[0], self.firstPos[1])), Vector((width+self.firstPos[0], self.firstPos[1]))) # bot + Draw_2D_Line(Vector((self.firstPos[0], self.firstPos[1])), Vector((self.firstPos[0], self.firstPos[1] + height))) # left + Draw_2D_Line(Vector((self.firstPos[0], self.firstPos[1] + height)), Vector((width+self.firstPos[0], self.firstPos[1] + height))) # top + Draw_2D_Line(Vector((self.firstPos[0] + width, self.firstPos[1] + height)), Vector((width+self.firstPos[0], self.firstPos[1]))) # right + Draw_2D_Point(self.firstPos, (.8, .6, .2, 1)) + Draw_2D_Point(self.mousePos, (.8, .6, .2, 1)) + else: + Draw_2D_Point(self.mousePos, (.8, .6, .2, 1)) + Draw_2D_Point(self.midPoint, (.8, .6, .2, 1)) + + self.width = width + self.height = height + else: + Draw_2D_Point(self.mousePos, (.8, .6, .2, 1)) + except: + return + +def draw_reference_callback_px(self, context): + try: + if context.area != self.area: + return + if self.ref.signal != 'H' and context.window_manager.bas_references.hide_all == False: + # IS THE RIGHT MODE ? + if self.ref.mode == 'ALL' or context.mode == self.ref.mode: + coords = self.ref.position + width = self.ref.size[0] + height = self.ref.size[1] + # DRAW IMAGE + Draw_Image(self.image, coords, width, height, self.ref.use_transparency, self.flipX, self.flipY) + #Draw_Texture(self.image, self.ref.position, width, height) + # DRAW OUTLINE + if self.ref.use_outline: + lineColor = self.ref.outline_color + #Draw_2D_Line_GL((coords[0], coords[1]), (width+coords[0], coords[1]), 2, lineColor) + #Draw_2D_Line_GL((coords[0], coords[1]), (coords[0], coords[1] + height), 2, lineColor) + #Draw_2D_Line_GL((coords[0], coords[1] + height), (width+coords[0], coords[1] + height), 2, lineColor) + #Draw_2D_Line_GL((coords[0] + width, coords[1] + height), (width+coords[0], coords[1]), 2, lineColor) + Draw_2D_Line(Vector((coords[0], coords[1])), Vector((width+coords[0], coords[1])), lineColor) # bot + Draw_2D_Line(Vector((coords[0], coords[1])), Vector((coords[0], coords[1] + height)), lineColor) # left + Draw_2D_Line(Vector((coords[0], coords[1] + height)), Vector((width+coords[0], coords[1] + height)), lineColor) # top + Draw_2D_Line(Vector((coords[0] + width, coords[1] + height)), Vector((width+coords[0], coords[1])), lineColor) # right + # DRAW LABEL + if self.ref.use_label: + if self.ref.label_align_to == 'TOP': + x = self.ref.position[0] + y = self.ref.position[1] + height + else: + x = self.ref.position[0] + y = self.ref.position[1] - self.ref.label_thickness + + # DRAW LABEL TEXT + tColor = self.ref.label_text_color + blf.size(0, self.ref.label_text_size, 72) + textsize = blf.dimensions(0, self.ref.label_text) + textY = y + (self.ref.label_thickness - textsize[1]) * .5 + #textY = y + self.ref.label_text_size/2 + if self.ref.label_text_align == 'LEFT': + textX = coords[0] + context.window_manager.bas_references.label_text_padding + elif self.ref.label_text_align == 'CENTER': + textX = coords[0] + (width - textsize[0]) * .5 + elif self.ref.label_text_align == 'RIGHT': + textX = coords[0] + width - textsize[0] - context.window_manager.bas_references.label_text_padding + + Draw_2D_Rectangle(x, y, width, self.ref.label_thickness, self.ref.label_color) + + Draw_Text(textX, textY, str(self.ref.label_text), self.ref.label_text_size, 0, + self.ref.label_text_color[0], self.ref.label_text_color[1], self.ref.label_text_color[2], self.ref.label_text_color[3]) + except: + pass + + + +# CONSTRUCTOR DE REFERENCIAS +class Create_Reference: + image_path : StringProperty(name="Image Path", subtype='FILE_PATH') + label_text : StringProperty(name="Label Text") + + def __init__(self): + wm = bpy.context.window_manager.bas_references + self.image_path = wm.image_path + self.label_text = wm.label_text + # saved external or internal + # text + # use label + # label size + # label position + # etc + + #def __repr__(self): + # return "Create_Reference[%s, %i[], %i[], %i[]]" % (self.stage_Name, self.relative_Values, self.constant_Values, self.brush_Values) diff --git a/atelier/tools/reference_system/io.py b/atelier/tools/reference_system/io.py new file mode 100644 index 0000000..a24f104 --- /dev/null +++ b/atelier/tools/reference_system/io.py @@ -0,0 +1,29 @@ +# Copyright (C) 2019 Juan Fran Matheu G. +# Contact: jfmatheug@gmail.com +from os import path, sep +data_dir = path.join(path.dirname(__file__), "data") +references_path = path.join(data_dir, "references") + +import bpy + +def Create_Ref_Data(): + try: + name = bpy.path.basename(bpy.context.blend_data.filepath).split(".blend")[0] + path_to_refs = references_path + sep + name + ".json" + bpy.context.scene.bas_references.data_base = path_to_refs + f = open(path_to_refs, "w") # write, create if it doesn't exist + return True + except: + return False + +def Save_Ref_Data(_buttonData): + # TODO Button data must be serialized before + refs_data = bpy.context.scene.bas_references.data_base + with open(refs_data) as data_file: + data_loaded = json.load(data_file) + with open(refs_data, 'w+', encoding='utf-8') as json_file: # Después de salir del bloque del "with" el archivo es cerrado automáticamente + if data_loaded != {}: + json.dump(MergeDictionaries(data_loaded, _buttonData), json_file, ensure_ascii=False, indent=4) + else: + json.dump(_buttonData, json_file, ensure_ascii=False, indent=4) + # TODO algunos matices que ajustar acorde a las settings diff --git a/atelier/tools/reference_system/ops.py b/atelier/tools/reference_system/ops.py new file mode 100644 index 0000000..cdb96f5 --- /dev/null +++ b/atelier/tools/reference_system/ops.py @@ -0,0 +1,418 @@ +# Copyright (C) 2019 Juan Fran Matheu G. +# Contact: jfmatheug@gmail.com +import bpy +from bpy.types import Operator +from .io import Create_Ref_Data +from ...utils.others import ShowMessageBox, Generate_UUID +from .draw import Reference_Events, draw_reference_callback_px, interactive_draw_reference_callback_px, Create_Reference +from .ui import references +import blf +from bpy.props import FloatVectorProperty, BoolProperty, IntProperty, StringProperty +from mathutils import Vector + +# OVERLAP N REFERENCE +class BAS_OT_Flip_Reference(Operator): + bl_idname = "bas.flip_reference" + bl_label = "" + bl_description = "Flip Reference" + index : IntProperty() + axis : StringProperty() + def execute(self, context): + references[self.index].signal = self.axis + return {'FINISHED'} + +# OVERLAP N REFERENCE +class BAS_OT_Overlap_Reference(Operator): + bl_idname = "bas.overlap_reference" + bl_label = "" + bl_description = "Overlap Reference" + index : IntProperty() + def execute(self, context): + r = references[self.index] + r.in_front = False if r.in_front else True + return {'FINISHED'} + +# OVERLAP N REFERENCE +class BAS_OT_Transparent_Reference(Operator): + bl_idname = "bas.transparent_reference" + bl_label = "" + bl_description = "Overlap Reference" + index : IntProperty() + def execute(self, context): + r = references[self.index] + r.use_transparency = False if r.use_transparency else True + return {'FINISHED'} + +# HIDE ALL REFERENCES +class BAS_OT_Hide_References(Operator): + bl_idname = "bas.hide_all_references" + bl_label = "" + bl_description = "Hide All References" + hide : BoolProperty() + def execute(self, context): + signal = 'H' if self.hide else '' + for r in references: + r.signal = signal + #bpy.context.window_manager.references_hide_all = self.hide + return {'FINISHED'} + +# HIDE N REFERENCE +class BAS_OT_Hide_Reference(Operator): + bl_idname = "bas.hide_reference" + bl_label = "" + bl_description = "Hide Reference" + index : IntProperty() + def execute(self, context): + r = references[self.index] + r.signal = 'H' if r.signal == '' else '' + return {'FINISHED'} + +# LOCK ALL REFERENCES +class BAS_OT_Lock_References(Operator): + bl_idname = "bas.lock_all_references" + bl_label = "" + bl_description = "Lock All References" + state : BoolProperty() + def execute(self, context): + for r in references: + r.is_locked = self.state + #bpy.context.window_manager.references_lock_all = self.state + return {'FINISHED'} + +# Lock n REFERENCES +class BAS_OT_Lock_Reference(Operator): + bl_idname = "bas.lock_reference" + bl_label = "" + bl_description = "Lock Reference" + index : IntProperty() + def execute(self, context): + r = references[self.index] + r.is_locked = False if r.is_locked else True + return {'FINISHED'} + + +# Remove n REFERENCES +class BAS_OT_Remove_Reference(Operator): + bl_idname = "bas.remove_reference" + bl_label = "" + bl_description = "Remove Reference" + index : IntProperty() + permanent : BoolProperty(default=False) + + @classmethod + def description(cls, context, properties): + if properties.permanent: + return "Remove Completelly that image from the project" + else: + return "Remove this image from the references but keep it in the project" + + def execute(self, context): + r = references.pop(self.index) + #r = references[self.index] + r.signal = 'R' # remove signal + r.is_reference = False + # QUITAR SI CRASHEA + ''' + if self.permanent: + for img in bpy.data.images: + if img.ref.uuid == r.uuid: + bpy.data.images.remove(img) + break + ''' + return {'FINISHED'} + +# INTERACTIVE DRAWING IMAGE REFERENCE MAKER +# FLOATING IMAGE REFERENCE SYSTEM +class BAS_OT_Reference_Maker(Operator): + bl_idname = "bas.reference_maker" + bl_label = "" + bl_description = "Reference Maker" + + @classmethod + def description(cls, context, properties): + return "Drag over the viewport to draw your reference" + + def modal(self, context, event): + self.mousePos = Vector((event.mouse_region_x, event.mouse_region_y)) + if event.type in {'ESC'} or self.finished: + self.finish(context) + return {'FINISHED'} + elif self.dragging: + if event.type in {'LEFTMOUSE'} and event.value in {'RELEASE', 'CLICK'}: + self.lastPos = self.mousePos + self.create_button(context) + return {'FINISHED'} + if event.shift: + self.using_shift = True + self.using_ctrl = False + elif event.ctrl: + if not self.using_ctrl: + self.midPoint = (self.mousePos + self.firstPos) / 2 + self.using_ctrl = True + self.using_shift = False + else: + self.midPoint = Vector((0, 0)) + self.using_shift = False + self.using_ctrl = False + else: + if event.type in {'LEFTMOUSE'} and event.value in {'PRESS'}: + self.firstPos = self.mousePos + self.dragging = True + return {'RUNNING_MODAL'} + context.area.tag_redraw() + return {'RUNNING_MODAL'} + + def execute(self, context): + scn = context.scene.bas_references + wm = context.window_manager.bas_references + # LOAD IMAGE + if wm.get_references_from == 'SINGLE': + if wm.image_path == None or wm.image_path == "": + ShowMessageBox("Image path is empty", "Can't load any image", 'ERROR') + return {'FINISHED'} + else: + if wm.previews_dir == "" or wm.previews == None: + ShowMessageBox("Directory is empty or image in null", "Can't load any image", 'ERROR') + return {'FINISHED'} + try: + # TODO check existing to true and make a previous pass to determine if image can be imported, return error msg if not + if wm.get_references_from == 'SINGLE': + self.image = bpy.data.images.load(wm.image_path, check_existing=False) + else: + self.image = bpy.data.images.load(wm.previews_dir + wm.previews, check_existing=False) + except: + ShowMessageBox("Image path or image is not valid", "Can't load any image", 'ERROR') + return {'FINISHED'} + wm.image = self.image # this is for passing image data to real draw method + size = self.image.size + self.proportion = size[0] / size[1] + self.wider = False if self.proportion > 1 else True + + # TEMP TOOL + self.mode = context.mode + self.oldTool = context.workspace.tools.from_space_view3d_mode(self.mode, create=False).idname + bpy.ops.wm.tool_set_by_id(name="builtin.annotate") + + # VARS + self.mousePos = Vector((0, 0)) + self.firstPos = Vector((0, 0)) + self.lastPos = Vector((0, 0)) + self.midPoint = Vector((0, 0)) + self.finished = False + self.dragging = False + self.width = 0 + self.height = 0 + self.using_shift = False + self.using_ctrl = False + + # SAFE TEXT + if wm.label_text_size > wm.label_thickness: + wm.label_text_size = wm.label_thickness - 2 + + # HANDLERS + context.window_manager.modal_handler_add(self) + if not hasattr(self, '_handle'): + args = (self, context) + self._handle = bpy.types.SpaceView3D.draw_handler_add(interactive_draw_reference_callback_px, args, 'WINDOW', 'POST_PIXEL') + return {'RUNNING_MODAL'} + + def create_button(self, context): + #wm = context.window_manager + if self.using_ctrl: + coords = self.center + elif self.lastPos[1] < self.firstPos[1] and self.lastPos[0] < self.firstPos[0]: + coords = Vector((self.firstPos[0] - abs(self.width), self.firstPos[1] - abs(self.height))) + elif self.lastPos[1] < self.firstPos[1]: + coords = Vector((self.firstPos[0], self.firstPos[1] - abs(self.height))) + elif self.lastPos[0] < self.firstPos[0]: + coords = Vector((self.firstPos[0] - abs(self.width), self.firstPos[1])) + else: + coords = Vector((self.firstPos[0], self.firstPos[1])) + bpy.ops.bas.create_reference(size=(self.width, self.height), position=coords) + self.finish(context) + + def finish(self, context): + if self.finished == False: + bpy.ops.wm.tool_set_by_id(name=self.oldTool) + try: + self.finished = True + if self._handle: + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + except: + pass + return {'FINISHED'} + +# FLOATING IMAGE REFERENCE SYSTEM +class BAS_OT_Create_Reference(Operator): + bl_idname = "bas.create_reference" + bl_label = "" + bl_description = "Create Reference" + + size : FloatVectorProperty(default=(0,0), size=2, min=64, max=2048) + position : FloatVectorProperty(default=(0,0), size=2, min=0) + + def modal(self, context, event): + if self.area != context.area: # just for safe + print("hey") + print(self.area) + print(context.area) + print("ERROR: Reference will be removed...") + self.remove() + return {'FINISHED'} + self.refresh(context) # refresh + + # SIGNAL CHANGE ! + if self.prev_signal != self.ref.signal: + # REMOVING SIGNAL + if self.ref.signal == 'R': + self.remove() # force remove ref + return {'FINISHED'} + elif self.ref.signal == 'X': + self.flipX = False if self.flipX else True # invert flip x state + self.flipY = False if self.flipX else self.flipY # deactivate flip in other axist to not interf + self.ref.signal = self.prev_signal # Reset Signal to prev state + elif self.ref.signal == 'Y': + self.flipY = False if self.flipY else True + self.flipX = False if self.flipY else self.flipX + self.ref.signal = self.prev_signal # Reset Signal to prev state + else: + self.prev_signal = self.ref.signal # update prev Signal + # HIDDING SIGNAL? + elif self.ref.signal != 'H': + # Image is locked or global lock state is active ? + # Some image is being moved but is not this one ? + if (self.ref.is_locked or context.window_manager.bas_references.lock_all) or (context.window_manager.bas_references.moving_reference and not self.moving): + pass + # IS THE RIGHT MODE ? + elif self.ref.mode == 'ALL' or context.mode == self.ref.mode: + self.mousePos = Vector((event.mouse_region_x, event.mouse_region_y)) + if Reference_Events(self, context, event): + return {'RUNNING_MODAL'} + #print("EXCEPTION: Reference will be removed...") + #self.remove() + #return {'FINISHED'} + return {'PASS_THROUGH'} + + def refresh(self, context): + if context.area: + context.area.tag_redraw() + + def remove(self): + if self.cancelled == False: + self.cancelled = True + try: + if hasattr(self, '_handle'): + bpy.types.SpaceView3D.draw_handler_remove(self._handle, "WINDOW") + del self._handle + except: + pass + scn = bpy.context.scene.bas_references + scn.num_of_references -= 1 + if scn.num_of_references < 1: + scn.is_using_references = False + scn.num_of_references = 0 + + def execute(self, context): + wm = context.window_manager.bas_references + if not wm.image: + print("WARN: No reference image data was found!") + return {'FINISHED'} + scn = context.scene.bas_references + self.cancelled = False # cancelled condition to block over-iter + self.moving = False + self.flipX = False + self.flipY = False + self.image = bpy.data.images[wm.image.name] # load image data + wm.image = None # free" pointer + # This way we can know if this project has references to load + if not scn.is_using_references: + scn.is_using_references = True + scn.num_of_references += 1 + self.image.use_fake_user = True # We ensure to keep image in project + ref = self.image.ref # Access to custom reference data in our image + ref.is_reference = True # Now this image is set as a reference + ref.uuid = Generate_UUID() # Generate a unic ID for this image reference + # If this is true, we are going to block this reference to be drawing only in the actual mode + if wm.keep_in_actual_mode: + ref.mode = context.mode + else: + ref.mode = 'ALL' + ref.size = self.size # this is passed by the interactive drawing method + ref.position = self.position # this is passed by the interactive drawing method + ref.name = wm.name + ref.use_label = wm.use_label + ref.label_text = wm.label_text + ref.label_text_size = wm.label_text_size + ref.label_text_align = wm.label_text_align + ref.label_text_color = wm.label_text_color + ref.label_color = wm.label_color + ref.label_thickness = wm.label_thickness + ref.label_align_to = wm.label_align + #ref.opacity = wm.reference_image_opacity + ref.use_outline = wm.use_outline + ref.outline_color = wm.outline_color + ref.is_locked = False # By default is always false + ref.signal = '' # by default is always '' empty == visible + ref.in_front = True + ref.use_transparency = False #wm.reference_use_transparency + + self.prev_signal = '' + self.ref = ref # we'll use self.image for image and bindcode atributes, self.ref for reference properties + # Add it to our list of references + references.append(self.ref) + + # ADD A MODAL HANDLER + context.window_manager.modal_handler_add(self) + # ADD A DRAWING HANDLER + if not hasattr(self, '_handle'): + args = (self, context) + self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_reference_callback_px, args, 'WINDOW', 'POST_PIXEL') + self.area = context.area + return {'RUNNING_MODAL'} + +# OPTIMAL FLOATING IMAGE REFERENCE SYSTEM +class BAS_OT_Create_Opti_Reference(Operator): + bl_idname = "bas.create_opti_reference" + bl_label = "" + bl_description = "Create Reference" + + def execute(self, context): + scn = context.scene.bas_references + wm = context.window_manager.bas_references + # CHECK IF IT'S FIRST REFERENCE + if not scn.is_using_references: + # CREATE REFERENCE DATABASE FOR THIS PROJECT + if Create_Ref_Data(): # If succeed, continue + scn.is_using_references = True # Save into project file so next time you start ur project it will notice it + scn.num_of_references += 1 # Increment in 1 the number of references created + else: + ShowMessageBox("A database problem has ocurred! Please report it!", "Ops! There is a problem!", 'ERROR') + return {'FINISHED'} + # Get Image path and import image + self.image = bpy.data.images.load(wm.image_path, check_existing=True) + # Save Internally? + if scn.save_references_mode == 'INTERNAL': + self.image.use_fake_user = True # Force to keep in project so it's not deleted when closing + else: # EXTERNAL SAVE + self.image.use_fake_user = False # No need to save in project + # Save it in reference folder + # Fills reference object data and save 'reference' to that inst + self.reference = Create_Reference() + + # TODO + + +classes = ( + BAS_OT_Create_Opti_Reference, + BAS_OT_Create_Reference, + BAS_OT_Flip_Reference, + BAS_OT_Hide_Reference, + BAS_OT_Hide_References, + BAS_OT_Lock_Reference, + BAS_OT_Lock_References, + BAS_OT_Overlap_Reference, + BAS_OT_Reference_Maker, + BAS_OT_Remove_Reference, + BAS_OT_Transparent_Reference +) diff --git a/atelier/tools/reference_system/previews.py b/atelier/tools/reference_system/previews.py new file mode 100644 index 0000000..18685e3 --- /dev/null +++ b/atelier/tools/reference_system/previews.py @@ -0,0 +1,62 @@ +# Copyright (C) 2019 Juan Fran Matheu G. +# Contact: jfmatheug@gmail.com +import os +import bpy +from .ui import reference_collections +from bpy.utils import previews + + +def register_previews(): + p_pcoll = previews.new() + p_pcoll.bas_reference_previews_dir = "" + p_pcoll.bas_reference_previews = () + reference_collections["previews"] = p_pcoll + +def unregister_previews(): + previews.remove(reference_collections["previews"]) + +def enum_previews_from_directory_items(self, context): + """EnumProperty callback""" + if context is None: + return [] + + #scn = context.scene.bas_references + wm = context.window_manager.bas_references + + if wm.previews_dir == "": + return [] + enum_items = [] + directory = wm.previews_dir + + # Get the preview collection (defined in register func). + pcoll = reference_collections["previews"] + + if directory == pcoll.bas_reference_previews_dir: + return pcoll.bas_reference_previews + + print("Scanning directory: %s" % directory) + + if directory and os.path.exists(directory): + # Scan the directory for png files + image_paths = [] + for fn in os.listdir(directory): + if fn.lower().endswith(".png") or fn.lower().endswith(".jpg"): + image_paths.append(fn) + + for i, name in enumerate(image_paths): + # generates a thumbnail preview for a file. + filepath = os.path.join(directory, name) + icon = pcoll.get(name) + if not icon: + thumb = pcoll.load(name, filepath, 'IMAGE') + else: + thumb = pcoll[name] + enum_items.append((name, name, "", thumb.icon_id, i)) + + pcoll.bas_reference_previews = enum_items + pcoll.bas_reference_previews_dir = directory + return pcoll.bas_reference_previews + +# We can store multiple preview collections here, +# however in this example we only store "main" +#preview_collections = {} diff --git a/atelier/tools/reference_system/prop_fun.py b/atelier/tools/reference_system/prop_fun.py new file mode 100644 index 0000000..ea6c73c --- /dev/null +++ b/atelier/tools/reference_system/prop_fun.py @@ -0,0 +1,17 @@ +# Copyright (C) 2019 Juan Fran Matheu G. +# Contact: jfmatheug@gmail.com +import bpy + +def update_references_hide_all(self, context): + if self.hide_all: + bpy.ops.bas.hide_all_references(hide=True) + else: + bpy.ops.bas.hide_all_references(hide=False) + return None + +def update_references_lock_all(self, context): + if self.lock_all: + bpy.ops.bas.lock_all_references(state=True) + else: + bpy.ops.bas.lock_all_references(state=False) + return None diff --git a/atelier/tools/reference_system/ui.py b/atelier/tools/reference_system/ui.py new file mode 100644 index 0000000..f2f828f --- /dev/null +++ b/atelier/tools/reference_system/ui.py @@ -0,0 +1,209 @@ +# Copyright (C) 2019 Juan Fran Matheu G. +# Contact: jfmatheug@gmail.com +import bpy +from bpy.types import Panel + + +references = [] +reference_collections = {} +class BAS_PT_Reference_System(Panel): + bl_label = "Import References" + bl_category = 'Sculpt' + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = ".paint_common" + bl_options = {'DEFAULT_CLOSED'} + #bl_ui_units_x = 14 + bl_order = 10 + + can_create = False + + def draw_header(self, context): + self.layout.label(text="", icon='RENDERLAYERS') # FILE_IMAGE OUTLINER_OB_IMAGE RENDERLAYERS + self.layout.separator() + + def draw(self, context): + wm = context.window_manager.bas_references + scn = context.scene.bas_references + + layout = self.layout + col = layout.column(align=True) + + content = col.box() + + row = content.row() + row.scale_y = 1.3 + row.prop(wm, "get_references_from", text="Single Image", expand=True) + + if wm.get_references_from == 'SINGLE': + content.label(text="Image Path", icon='FILE_IMAGE') + content.prop(wm, "image_path", text="") + if wm.image_path == "": + content.alert = True + content.label(text="Please, select your reference image") + content.alert = False + self.can_create = False + else: + self.can_create = True + + elif wm.get_references_from == 'FOLDER': + content.label(text="Folder Directory", icon='FILE_FOLDER') + content.prop(wm, "previews_dir", text="") + if wm.previews == "": + content.alert = True + content.label(text="Please, select your references folder") + content.alert = False + self.can_create = False + else: + content.template_icon_view(wm, "previews") + content.prop(wm, "previews", text="") + self.can_create = True + + content.separator(factor=.1) + + if self.can_create: + content = col.box() + content.separator(factor=.1) + text = content.row(align=True).split(factor=.25) + text.label(text="Name :") + text.prop(wm, "name", text="") + content.prop(wm, "keep_in_actual_mode", text="Keep In Actual Mode", toggle=False) + #content.prop(wm, "reference_image_opacity", text="Image Opacity") + #content.prop(wm, "reference_use_transparency", text="Use Transparency", toggle=False) + content.separator(factor=.1) + # LABEL SECTION + section = col.box().column(align=True) + header = section.box().row() + arrow_icon = 'TRIA_DOWN' if wm.ui_show_label else 'TRIA_RIGHT' + #header_text = " (ON)" if wm.reference_use_label else " (OFF)" + header.prop(wm, "use_label", text="", toggle=False) + header.prop(wm, "ui_show_label", text="Label ", toggle=True, icon='BOOKMARKS', emboss=False) + header.label(text="", icon=arrow_icon) + if wm.ui_show_label: + label = section.box() + label.prop(wm, "label_color", text="Label Color") + label.prop(wm, "label_thickness", text="Label Thickness") + label.row().prop(wm, "label_align", text="Label Align", expand=True) + text = label.row(align=True).split(factor=.35) + text.label(text="Label Text") + text.prop(wm, "label_text", text="") + label.prop(wm, "label_text_size", text="Text Size") + label.prop(wm, "label_text_color", text="Text Color") + label.row().prop(wm, "label_text_align", text="Text Align", expand=True) + label.prop(wm, "label_text_padding", text="Global Label Text Padding") + label.separator(factor=1) + # OUTLINE SECTION + #section = content.column(align=True) # This is to split our label and outline properties boxes + header = section.box().row() + arrow_icon = 'TRIA_DOWN' if wm.ui_show_outline else 'TRIA_RIGHT' + #header_text = " (ON)" if wm.reference_use_outline else " (OFF)" + header.prop(wm, "use_outline", text="") + header.prop(wm, "ui_show_outline", text="Outline", toggle=True, icon='MATPLANE', emboss=False) + header.label(text="", icon=arrow_icon) + if wm.ui_show_outline: + outline = section.box() + outline.prop(wm, "outline_color", text="Outline Color") + outline.separator(factor=.5) + # OTHER PROPERTIES + #if scn.is_using_references: + # other = col.box() + # other.prop(wm, "reference_label_text_padding", text="Global Label Text Padding") + + # DRAW OPERATOR + op = col.box() + op.scale_y = 1.5 + op.operator('bas.reference_maker', text="Draw Reference") + +class BAS_PT_Reference_Manager(Panel): + bl_label = "References" + bl_category = 'Sculpt' + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = ".paint_common" + bl_options = {'DEFAULT_CLOSED'} + #bl_ui_units_x = 14 + bl_order = 11 + + def draw_header(self, context): + wm = context.window_manager.bas_references + row = self.layout.row(align=True) + hide_icon = 'HIDE_ON' if wm.hide_all else 'HIDE_OFF' + row.prop(wm, "hide_all", text="", icon=hide_icon, toggle=True) + lock_icon = 'DECORATE_LOCKED' if wm.lock_all else 'DECORATE_UNLOCKED' + row.prop(wm, "lock_all", text="", icon=lock_icon, toggle=True) + row.separator() + row.label(text="", icon='RENDERLAYERS') # FILE_IMAGE OUTLINER_OB_IMAGE + + + def draw(self, context): + wm = context.window_manager.bas_references + scn = context.scene.bas_references + + # AVOID TO DRAW IF NOT REFERENCES ARE AVAILABLE + if scn.is_using_references: + layout = self.layout + col = layout.column(align=True) + + content = col.box().column(align=True) + header = content.box() + header.label(text="Image References", icon='RENDER_RESULT') + refs = content.box() + n = 0 + m = 0 + for ref in references: + if ref.mode == 'ALL' or ref.mode == context.mode: + row = refs.row(align=True) + # HIDE + hide = row.row(align=True) + hide.enabled = not wm.hide_all + isHidden = True if ref.signal == 'H' else False + hide_icon = 'HIDE_ON' if isHidden else 'HIDE_OFF' + hide.operator("bas.hide_reference", text="", icon=hide_icon, depress=isHidden).index = n + # LOCK + look = row.row(align=True) + look.enabled = not wm.lock_all + lock_icon = 'DECORATE_LOCKED' if ref.is_locked else 'DECORATE_UNLOCKED' + look.operator("bas.lock_reference", text="", icon=lock_icon, depress=ref.is_locked).index = n + # OVERLAP dui + #overlap = row.row() + #overlap.enabled = not wm.references_lock_all + row.operator("bas.overlap_reference", text="", icon='OVERLAY', depress=ref.in_front).index = n + # TRANSPARENCY + row.operator("bas.transparent_reference", text="", icon='NODE_TEXTURE', depress=ref.use_transparency).index = n + # NAME + row.label(text=ref.name) + # OPTIONS + #options_n = n + #options = row.popover("DUI_PT_Reference_Options", text="", icon='THREE_DOTS') + # FLIP X + flip = row.operator("bas.flip_reference", text="", icon='EVENT_X') + flip.index = n + flip.axis = 'X' + # FLIP Y + flip = row.operator("bas.flip_reference", text="", icon='EVENT_Y') + flip.index = n + flip.axis = 'Y' + # REMOVE + remove = row.operator("bas.remove_reference", text="", icon='TRASH') + remove.index = n + remove.permanent = False + #perma_remove = row.operator("bas.remove_reference", text="", icon='LIBRARY_DATA_BROKEN') + #perma_remove.index = n + #perma_remove.permanent = True + m += 1 + n += 1 + if n == 0: + refs.alert = True + refs.label(text="There are no references yet", icon='INFO') + elif m == 0: + refs.alert = True + refs.label(text="No references in this mode", icon='INFO') + else: + self.layout.alert = True + self.layout.label(text="There are no references yet", icon='INFO') + + +classes = ( + BAS_PT_Reference_System, + BAS_PT_Reference_Manager +) diff --git a/atelier/tools/remesh/__init__.py b/atelier/tools/remesh/__init__.py new file mode 100644 index 0000000..ad5b02a --- /dev/null +++ b/atelier/tools/remesh/__init__.py @@ -0,0 +1,39 @@ +def register(): + from .utils import utils_classes + from .ops import classes + from bpy.utils import register_class + + from .ui import BAS_PT_remesh_options + register_class(BAS_PT_remesh_options) + + for cls in utils_classes: + register_class(cls) + + for cls in classes: + register_class(cls) + + from .data import RemeshPG + register_class(RemeshPG) + + from bpy.types import WindowManager as wm + from bpy.props import PointerProperty as Pointer + wm.bas_remesh = Pointer(type=RemeshPG) + +def unregister(): + from bpy.utils import unregister_class + from bpy.types import WindowManager as wm + del wm.bas_remesh + + from .data import RemeshPG + unregister_class(RemeshPG) + + from .ui import BAS_PT_remesh_options + unregister_class(BAS_PT_remesh_options) + + from .ops import classes + for cls in reversed(classes): + unregister_class(cls) + + from .utils import utils_classes + for cls in reversed(utils_classes): + unregister_class(cls) diff --git a/atelier/tools/remesh/__pycache__/__init__.cpython-37.pyc b/atelier/tools/remesh/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8698d0ae101ea0ae554260ba1938db156b0e337a GIT binary patch literal 1190 zcmZ`&OKTKC5bmD$&OQ<}(fBxcol6k$qDU|gA_#^xi3GyPIN5I2fu2XIXN?OxAn4Vb zKftTtFWRf8`~^=|_3TDb?4hfxzj{nf)i*o+e#me=eEB_DcNqIYm*uihd5T-Fp%6^) zPwbQlOW1gF;T*BZ9ihdn0Huw47q|Y3LU3Uzmh*;j(rsnscEeG3gsYs~Z7hR(%FF%6 z=4@uo?2l-Fzcyog>~*hk5OD}O-#Q_b|Aii(P+ngJd8ctryi4AgKTu)bZ9JoQR4?y0 zKHB}6E5grw<|PBcu=ei3CC54=KJQQJOzC8#Qmv&%z+b4BXeWmO$Z@7?39Szz>pR;= z$?!MSC&;DSJ& z^7FuulC`8nKVNn7uyK%}8+Dh6Y3S%;l%d`EDbkUsM|4B#887c2uMg-&46Crc z?{J<{K$kF+%y0A1pWX(V7;=Z}ODDsJR@_8v9{w(!n4)i^Xb0sihC>4ym{AQl+(GL> cWSMc9%OCkD9!%2UNnVPHl3Ql>R9mqA3+#Ub-v9sr literal 0 HcmV?d00001 diff --git a/atelier/tools/remesh/__pycache__/data.cpython-37.pyc b/atelier/tools/remesh/__pycache__/data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f379df4ad4c69f57db84149dae36ec11cce46320 GIT binary patch literal 2376 zcmZWrTXWM!6qe+>6DLjrB+vlc3oR;;Qkqhrp~Z=v)E&n*wv&R!GaALaNd&sFD?zN+ z_BB7GGyO&T*gj?Y5Bk)zE7=JkkIwFX`<-+4oIShpW~EYA;8}nEmw7UyDF2ATPG?3cMe{WQK;wnY{QG($t0iM^nGVKs%$&H6;6lq zUElUXLZ`q?(>tG?>xb0#jwX{M;dt_tpz{n~c12Ybq8KVx3~fLVRMR3%xQ*-%K1 zUdK1U;?2_xAV}K#6n}<4$6uuOZb{ac!1+r0ye&C8{u(iz>0Vc ze0&4%3cioOO?^C&+TY>zsgLg^@(}+pMIK3ukMWZWUVq$B@CIzvQ(o*5m$2jRj-J&s zJh$I@(P$aDegA|w@tkZ>!XOmp%g^^}^3_xmz1FjAEUPY}X1&ygKP!&~kljAKI`S zUFA7DBrfAcDuW<2UP@{Vg+6)(g@fMN;W1QVHXH|pXFba$eBrGK&J@$8FDr68UT$DJ zL`k@PJ0_@02_qo`)dD7mR^)_q32KQiC&~oqCIg}wq{IwFkZpI`4UoP^;+t(hL=hv1 zl7RZ6?rq;gx8Fqe=rv)X2sqzw?si)ZBj2~(z#;K+$0LYAIXmxb32MEwx7lhy>qiGb zy`Hom7-z5*!Zuxor5dxn3!dYJiz5U%Bzl%sfk!V3A%mO2gpH^jh^6wyB+`q^AH+AB z-bm!ydoNjan0zclp;->3>B!^x?b_C$(>uf9>A&ezA{^2}N%$CnD(3r^rIb03lJe7l?X+7g%&4_E|>6Gp_A{uugah_Gn}? zFfYTTtHS9bPU_M;_Z81=`*cK5f9$#>q+_n=nRAWAYIUNa<$5ZodIN-*K#f!FftnJ+s&D?`_tb`^|bIp562A z^`ir9zqRp{k?*!@?OKoLyN=~qGzno{hA@Ei$1CZf6$LKx> z8tp-Cr*U*|YxUp1A71-8F5N=@AsSGKY`sfhKfKU7A_9Y}-V%p{t)UiSEG>zr2oQEY z$>*j{s0B5}XTTQdMUJ>oR+252#go_*xTyC_Q?+xPKmy+^r`=h>^14uiL{~5Y(;E8uok( zPS4SB(;*)0JKFXm4@;91X!v05@%>?PTd<*pvG1{AEhG+H%>7a11mQ4*uR0qhH}ep* zt+ilG#s3ho5cUK_S=F@JVpXj|t7SBGPOIiMwG8c|s==?SRRI<41Woa*wA0pSc5+CRWXs-s){oI1NL;w Av;Y7A literal 0 HcmV?d00001 diff --git a/atelier/tools/remesh/__pycache__/ops.cpython-37.pyc b/atelier/tools/remesh/__pycache__/ops.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1844ac13672ba8a043f436c3b7460fdfd1030ecd GIT binary patch literal 7163 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 literal 0 HcmV?d00001 diff --git a/atelier/tools/remesh/__pycache__/ui.cpython-37.pyc b/atelier/tools/remesh/__pycache__/ui.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdea6cf064b24934caa95eb1a2f8ba937bf77ac2 GIT binary patch literal 3573 zcmZ`6OKjW7l|NBm%d%oSwv)|zH%V)?Ti4la_SYniY{`LB$8!8h8bQS@tr^RdDN;M6 z980B6NiPKqBxNgZN?}ItH}5xZ-jGWp zBPj`XpCvk z)oP6DTT&r@qQa7bEYgIujrxJnV1%3i32Ctn!)8_;vB9GNa1Vz2DNIbN%2cW$98+! z%>bXRgt&-5UM)W%H%mg8&*8JosPE5)DrZ~F+jpqQ zmdY!|owaSUyjIyNmrl~)bsb~CXADLH#xe|d5_0IvO{ppO75G=0s;5m$O{K5;QZv@q zeC?I~TI$FA*sD1F>%P)VQ29XN4}86u#5`kmk5yTEsoH-GxH@ou@9VEpzCz=#VK*W; z5yDL!vvMp4I#DJa&|`soCX!FTmSCM0+N2}CPBMYLEFGn}W6%lKs)4n!1LdX6_rM1F z83)=3%^zs|uQch$NtRydC+rj*58#P@!jCsc{REu^pV6~;VD(~THHZ{sVhI`mKLc8lBnvTq0B{oF$iol)Ob{oW z0YCW)A~;(gt_id#T%%WQgjA@pYwBm3?jJcUH0DdbM%wv%5xdk-uk67Euk-D>ZS7aw@=P?3~Mw+ zKaF-D;m?NP&xhbIhTt!U;GaZrSOH&0|NNNR-OUs}GCYWs#+#A6C|XT7D`dk*8hTbJ`9B={i;iJGICSXahG+;<$L?JUMVhXhm?S zmk)e7)rE~;#2#0ER$dchp=adKIbs%Z%^qvq44!{33bj}&fy?EGmB(dqF_3VHY1eUy z?HMTEuuo(#fASxAdQ)iTOwpNWPn}=5)%fji@OiU{*p0?7#(#JJFTEFwJ#Bsgm?7$g z=#9p|-~8jx4|g{fBeR52(+j*KZO8@l-0EayXM1gRy}U_E#qA=wzd@h?%9{lxNG;ET zR|QohIh07tXgXZP>yW~h?TQRLWHuRK`5}iR-Mxu(fz@rKl+_q(UBGoZT$2L8e9Em(` z2I>H@E(!S)Z_%I`IG~z|5d)G9+dS9}YIuf-%pLLTfw@D&>M%ZsaI(QoO0d~+e8&{3 z>v0j|PRFJ~-F1#cGU!lvCiyKC&_n(FGekzBf{2U-H3hxGXqk0_y~T1G;BHVfrrjuv z3C(5J9-bB(J7zWR1yQ*ohRJsM9Z<^e27W*`g@y+yIBfu8jy3Eq)l1}^ubBj;s|jq# zTi8@J>>zvzwZ)^uSB*jF4u2!@uYmlogXJ%4J1*mH?ZHZ|*lw2~SG7$C+_xLGC5zdV z@!1v31$J?JK&pK_|HVSht#h;OxwWt|YDJG(Cgb62Y|V2V%dLf#Q0tiU?XJ)$H=gCe ztosL;Bsn2xWgX^(nv%z1WClM4Xu!#<8Td~Dq$|3tfi&WJH0Oq-Kmzpu2O*TwJPC4FKyyl-3-Dq4s3Q@qos=_?r3~OWQgTk|&Af95<}t)|_zp-A z0!08{93n+X%b^{pbs{o^z};#^oW!n0!h#m4Lrq(D@}4 q#=Do_hDjuM+ueEe*5&w(7Kjb0{t9WRUnFja;orrZ%tcwqv;JS?1~vWw literal 0 HcmV?d00001 diff --git a/atelier/tools/remesh/__pycache__/utils.cpython-37.pyc b/atelier/tools/remesh/__pycache__/utils.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36c05b7c792fdbf90bc83c357cdb3203da49f863 GIT binary patch literal 1811 zcmcIl&5zqe6rZu3jgw8f38b)43ZXsa5+Q_yKvmoQ>;^%WZn`~KmPU?enhz$nnQ>OT zQE%M?7ybbF50JQWKpgl#=7dx(J#s^wc+XDugAL1xiRPF0X5Nh7$M4yj)oPi*_~`ME z>=%cSm#8cT=;R?xy$!$#r;<#lM=8pdv?jJ^Q}T##n>(k3JA%HjJqPpxUjcn3(+k28 zg@NU*@M4d&+*fcDX;I_uCqe~jtU#{r$~Z{BR52{2Cm~ua#tH}zVd^~qK|IQdXB~nk zp3N=K!6);=(Dn*qg~JoL%S*icts{zj^&Sx}ukh+Ac|pArzroi|iC5+|@S<*RZEp0~ z{u6c-9}CI!XeQVw3YD0MD4nhM1KnP_)95UWnosd$vj-=Qd$9AU(|fXWV4TD5_P%jD zT~N9kpAWwO4#&?A2H-u6-+o;P4`z+?3z}!Qwj5LB;#n}2sj(-c2p~8%P83XpDI5iI zDncv2ir_!aEtvW>fai2f;u_+Za{848aavvDe6iEpYgwimgz4x=u(&@KVQQT8BoVM| zhfUMHsoFtaAf^lTQ zS+nLf)oG*gr7YNRGRwl-^DK<1L&nx~GPXa46fuj_A9N3S(bS13BL*^(7netoAO5GUy9w${@uFrd=xx+^jJcL{Xe(rOTyhP6&%<J^{WXBhe0$Dvz!0u z!ufK2E8D)%e+olT=%6YBoHyMc!23Is|c8j zOZ@upE>l%F)3P#3skK~2UMwR{R+4vMb3yjo|CWKPOuy_28Dr1WvBm5&hO3+SbSDcU zcjpcC<4s(()H=#b$eb~x37c~wSLm`9%nQ&yXR*SDQ@0Q>AaxtzU4-`#ke;fE@IJ!z zRJ;Qw==)VF;!d|!G^KuW(#F@8R+xuePvE1GH$p~Pg~q0uy;)e+2VlK~`NqAMH{E@t QhQ{JoXvMD8YITeL4XvKGBme*a literal 0 HcmV?d00001 diff --git a/atelier/tools/remesh/data.py b/atelier/tools/remesh/data.py new file mode 100644 index 0000000..da8daa0 --- /dev/null +++ b/atelier/tools/remesh/data.py @@ -0,0 +1,62 @@ +from bpy.types import PropertyGroup, Object +from bpy.props import EnumProperty, BoolProperty, FloatProperty, PointerProperty, IntProperty, StringProperty + + +class RemeshPG(PropertyGroup): + remesher : EnumProperty ( + items = ( + ('VOXEL', "Voxel", "Voxel Remesher"), + ('QUADRIFLOW', "Quadriflow", "Quad Remesher"), + ('DYNTOPO', "Dyntopo", "Dyntopo Remesher (flood fill)"), + ('DECIMATE', "Decimate", "Decimate Remesher (modifier)") + ) + ) + + ''' VOXEL REMESH PROPS ''' + voxel_join_object : PointerProperty(type=Object, name="Object to join with") + voxel_edit_size_presets : BoolProperty(default=False, name="Edit Voxel Size Presets") + voxel_reprojection : EnumProperty ( + items = ( + ('NONE', "None", "Not use reprojection (quickest)"), + ('SIMPLE', "Simple", "One single reprojection"), + ('DOUBLE', "Double", "Double reprojection (slowest)") + ), default = 'NONE', + name="Reprojection", description="Reprojection mode" + ) + voxels_incremental_sign : BoolProperty(default=True, name="Increment Voxel Size Direction") + + ''' QUADRIFLOW REMESH PROPS ''' + + + ''' DYNTOPO REMESH PROPS ''' + dyntopo_resolution : FloatProperty ( + subtype='FACTOR', default=100, min=1, max=300, precision=2, + name="Resolution", description="Mesh resolution. Higher value for a high mesh resolution" + ) + dyntopo_symmetry : BoolProperty(name="Force Symmetry", description="", default=False) + dyntopo_symmetry_axis : EnumProperty( + items=(('POSITIVE_X', "X", ""), ('POSITIVE_Y', "Y", ""), ('POSITIVE_Z', "Z", "")), + default='POSITIVE_X', name="Axis", description="Axis where apply symmetry" + ) + dyntopo_only_masked : BoolProperty(name="Remesh masked", default=False) + + ''' DECIMATE REMESH PROPS ''' + decimate_type : EnumProperty ( + items=(('COLLAPSE', "Collapse", ""), ('UNSUBDIVIDE', "Un-Subdivide", ""), ('PLANAR', "Planar", "")), + default='COLLAPSE', name="Type", description="Decimation Type to apply" + ) + decimate_ratio : FloatProperty ( + subtype='PERCENTAGE', default=100, min=0.0001, max=100, precision=2, + name="% of Tris", description="Percentage of triangles. Less value = less triangles" + ) + decimate_triangulate : BoolProperty(name="Triangulate", description="Force mesh triangulation", default=False) + decimate_symmetry : BoolProperty(name="Use Symmetry", description="Symmetrize mesh", default=False) + decimate_symmetry_axis : EnumProperty ( + items=(('X', "X", ""), ('Y', "Y", ""), ('Z', "Z", "")), + default='X', name="Axis", description="Axis where apply symmetry" + ) + + + ''' MESH TOOLS ''' + #show_remesher : BoolProperty(name='', default=True) + #show_mesh_tools : BoolProperty(name='', description="Show Mesh Tools", default=False) diff --git a/atelier/tools/remesh/ops.py b/atelier/tools/remesh/ops.py new file mode 100644 index 0000000..0409b46 --- /dev/null +++ b/atelier/tools/remesh/ops.py @@ -0,0 +1,189 @@ +import bpy +from bpy.types import Operator +from bpy.props import BoolProperty, EnumProperty, FloatProperty + +from ...utils.others import ShowMessageBox + + +class BAS_OT_voxel_remesh_reproject(Operator): + """Remesh by using OpenVDB Voxel Remesher""" + bl_idname = "bas.voxel_remesh_reproject" + bl_label = "Voxel Remesh with Reprojection" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + def execute(self, context): + wm = context.window_manager + obj = context.active_object + remesh = wm.bas_remesh + try: + if context.sculpt_object.use_dynamic_topology_sculpting: + bpy.ops.sculpt.dynamic_topology_toggle() + use_dyntopo = True + else: + use_dyntopo = False + except: + use_dyntopo = False + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(0, 0, 0), "orient_type":'GLOBAL', "orient_matrix":((0, 0, 0), (0, 0, 0), (0, 0, 0)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), "mirror":False, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False}) + obj_copy = context.active_object + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(state=True) + context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='SCULPT') + bpy.ops.object.voxel_remesh() + bpy.ops.object.mode_set(mode='OBJECT') + + if remesh.voxel_reprojection == 'DOUBLE': + sw = obj.modifiers.new(name="SW1", type='SHRINKWRAP') + sw.wrap_method = 'TARGET_PROJECT' # PROJECT # NEAREST_VERTEX # TARGET_PROJECT # NEAREST_SURFACEPOINT + sw.wrap_mode = 'ABOVE_SURFACE' # ABOVE_SURFACE # ON_SURFACE + sw.target = obj_copy + sw.use_positive_direction = True + sw.use_negative_direction = True + sw.cull_face = 'FRONT' + bpy.ops.object.modifier_apply(modifier="SW1") + sw = obj.modifiers.new(name="SW2", type='SHRINKWRAP') + sw.wrap_method = 'TARGET_PROJECT' # PROJECT # NEAREST_VERTEX # TARGET_PROJECT # NEAREST_SURFACEPOINT + sw.wrap_mode = 'ABOVE_SURFACE' # ABOVE_SURFACE # ON_SURFACE + sw.target = obj_copy + sw.use_positive_direction = True + sw.use_negative_direction = True + sw.cull_face = 'BACK' + bpy.ops.object.modifier_apply(modifier="SW2") + else: + sw = obj.modifiers.new(name="SW", type='SHRINKWRAP') + sw.wrap_method = 'NEAREST_SURFACEPOINT' # PROJECT # NEAREST_VERTEX # TARGET_PROJECT # NEAREST_SURFACEPOINT + sw.wrap_mode = 'ABOVE_SURFACE' # ABOVE_SURFACE # ON_SURFACE + sw.target = obj_copy + bpy.ops.object.modifier_apply(modifier="SW") + + bpy.data.objects.remove(obj_copy) + + bpy.ops.object.mode_set(mode='SCULPT') + + if use_dyntopo: + bpy.ops.sculpt.dynamic_topology_toggle() + + return {'FINISHED'} + +class BAS_OT_voxel_remesh_join(Operator): + """Remesh by using OpenVDB Voxel Remesher""" + bl_idname = "bas.voxel_remesh_join" + bl_label = "Voxel Remesh Join Objects" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + @classmethod + def poll(cls, context): + return context.active_object + + def execute(self, context): + bpy.ops.object.mode_set(mode='OBJECT') # Cambiamos a Object + #activeObj = context.active_object + #activeObj.select_set(state=True) # AÑADIMOS EL OBJETO INICIAL A LA SELECCION + context.window_manager.bas_remesh.voxel_join_object.select_set(state=True) + bpy.ops.object.join() # JOIN PARA AMBOS OBJ + bpy.ops.object.mode_set(mode='SCULPT') # Cambiamos a Object + bpy.ops.object.voxel_remesh() + context.window_manager.bas_remesh.voxel_join_object = None + return {'FINISHED'} + +class BAS_OT_voxel_remesh(Operator): + """Remesh by using OpenVDB Voxel Remesher""" + bl_idname = "bas.voxel_remesh" + bl_label = "Voxel Remesh (Dyntopo Support)" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + @classmethod + def poll(cls, context): + return context.mode == 'SCULPT' + + def execute(self, context): + if context.sculpt_object.use_dynamic_topology_sculpting: + bpy.ops.sculpt.dynamic_topology_toggle() + bpy.ops.object.voxel_remesh() + bpy.ops.sculpt.dynamic_topology_toggle() + else: + bpy.ops.object.voxel_remesh() + + return {'FINISHED'} + +class BAS_OT_dyntopo_remesh(Operator): + """Remesh by using Dyntopo Flood Fill""" + bl_idname = "bas.dyntopo_remesh" + bl_label = "Dyntopo Remesh" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + resolution : FloatProperty(name="Resolution", subtype='FACTOR', default=100, min=1, max=300, precision=2, description="Mesh resolution. Higher value for a high mesh resolution") + force_symmetry : BoolProperty(name="Force Symmetry", description="", default=False) + symmetry_axis : EnumProperty(items=(('POSITIVE_X', "X", ""), ('POSITIVE_Y', "Y", ""), ('POSITIVE_Z', "Z", "")), default='POSITIVE_X', name="Axis", description="Axis where apply symmetry") + only_masked : BoolProperty(name="Remesh masked", default=False) + + @classmethod + def poll(cls, context): + return context.active_object + + def execute(self, context): + if context.sculpt_object.use_dynamic_topology_sculpting: + if self.only_masked: + bpy.ops.paint.mask_flood_fill(mode='INVERT') + tool_settings = context.tool_settings + sculpt = tool_settings.sculpt + detail_method = sculpt.detail_type_method + sculpt.detail_type_method = 'CONSTANT' + resolution = sculpt.constant_detail_resolution + sculpt.constant_detail_resolution = self.resolution + #bpy.ops.sculpt.set_detail_size() + bpy.ops.sculpt.detail_flood_fill() + if self.force_symmetry: + #symmetry_dir = sculpt.symmetrize_direction + sculpt.symmetrize_direction = self.symmetry_axis + bpy.ops.sculpt.symmetrize() + sculpt.constant_detail_resolution = resolution + #sculpt.symmetrize_direction = symmetry_dir + sculpt.detail_type_method = detail_method + else: + # Shows a message box with an error message when dyntopo is disabled + ShowMessageBox("This remesher only works if Dyntopo is enabled", "Can't apply remesher", 'ERROR') + self.report({'ERROR'}, "Dyntopo should be enabled") + return {'FINISHED'} + +class BAS_OT_decimate_remesh(Operator): + """Remesh by using Decimate Modifier""" + bl_idname = "bas.decimate_remesh" + bl_label = "Decimation Remesh" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + type : EnumProperty( + items=(('COLLAPSE', "Collapse", ""), ('UNSUBDIVIDE', "Un-Subdivide", ""), ('PLANAR', "Planar", "")), + default='COLLAPSE', description="Decimation Type to apply" + ) + ratio : FloatProperty(name="% of Triangles", subtype='PERCENTAGE', default=100, min=0.0001, max=100, precision=2, description="Percentage of triangles. Less value = less triangles") + triangulate : BoolProperty(name="Triangulate", description="", default=False) + symmetry : BoolProperty(name="Symmetry", description="", default=False) + symmetry_axis : EnumProperty( + items=(('X', "X", ""), ('Y', "Y", ""), ('Z', "Z", "")), + default='X', description="Axis where apply symmetry", name="Axis" + ) + + @classmethod + def poll(cls, context): + return context.active_object + + def execute(self, context): + obj = context.active_object + decimation = obj.modifiers.new(name="Remesh", type='DECIMATE') + decimation.ratio = self.ratio / 100 # % to factor range(0, 1) + decimation.use_collapse_triangulate = self.triangulate + decimation.use_symmetry = self.symmetry + decimation.symmetry_axis = self.symmetry_axis + bpy.ops.object.modifier_apply(modifier="Remesh") + return {'FINISHED'} + + +classes = ( + BAS_OT_voxel_remesh, + BAS_OT_voxel_remesh_reproject, + BAS_OT_voxel_remesh_join, + BAS_OT_dyntopo_remesh, + BAS_OT_decimate_remesh +) diff --git a/atelier/tools/remesh/ui.py b/atelier/tools/remesh/ui.py new file mode 100644 index 0000000..167c3a8 --- /dev/null +++ b/atelier/tools/remesh/ui.py @@ -0,0 +1,131 @@ +from ... import __package__ as main_package +from bpy.types import Panel + + +# --------------------------------------------- # +# REMESH OPTIONS +# --------------------------------------------- # +class BAS_PT_remesh_options(Panel): + bl_label = "Remesh Options" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = "NONE" + bl_category = 'Sculpt' + bl_description = "Remesh options for each remesh method" + bl_options = {'DEFAULT_CLOSED'} + bl_ui_units_x = 11 + + def draw(self, context): + t_props = context.window_manager.bas_remesh + prefs = context.preferences.addons[main_package].preferences + layout = self.layout + row = layout.row() + col = row.column() + + # QUADRIFLOW + if t_props.remesher == 'QUADRIFLOW': + col.operator('object.quadriflow_remesh', icon='PLAY', text="Quadriflow Remesh") + + # DECIMATION + elif t_props.remesher == 'DECIMATE': + col.prop(t_props, 'decimate_ratio') + col.prop(t_props, 'decimate_triangulate') + row = col.row(align=True) + row.prop(t_props, 'decimate_symmetry') + row.prop(t_props, 'decimate_symmetry_axis') + + col.separator() + col = layout.column() + col.scale_y = 2 + col.operator('bas.decimate_remesh', text="Remesh") + + # DYNTOPO REMESH + elif t_props.remesher == 'DYNTOPO': + _col = col.row() + if t_props.dyntopo_resolution > 150: + _col.alert=True + icon = 'ERROR' + else: + _col.alert=False + icon = 'MONKEY' + _col.label(icon=icon, text="") + _col.prop(t_props, 'dyntopo_resolution') + row = col.row() + row.alert=False + row.prop(t_props, 'dyntopo_symmetry') + _row = row.split() + _row.ui_units_x = 7 + _row.prop(t_props, 'dyntopo_symmetry_axis', text="Axis") + + col.separator() + col = layout.column() + col.scale_y = 2 + col.operator('bas.dyntopo_remesh', text="Remesh") + + # VOXELS + elif t_props.remesher == 'VOXEL': + col.use_property_split = True + col.use_property_decorate = False + mesh = context.active_object.data + __col = col.row() + __col.prop(mesh, "remesh_voxel_size") + __col.scale_y = 1.2 + __col.scale_x = 2 + _row = col.row(align=True) + if t_props.voxels_incremental_sign: + iconSign = 'ADD' + _row.alert = False + else: + iconSign = 'REMOVE' + _row.alert = True + _row.prop(t_props, "voxels_incremental_sign", icon=iconSign, text="") + _row = _row.split().row(align=True) + _row.alert = False + sign = 1 if t_props.voxels_incremental_sign else -1 + prop = _row.operator("bas.voxel_size_increment", text=".05") + prop.value = 0.05 * sign + prop = _row.operator("bas.voxel_size_increment", text=".01") + prop.value = 0.01 * sign + prop = _row.operator("bas.voxel_size_increment", text=".005") + prop.value = 0.005 * sign + prop = _row.operator("bas.voxel_size_increment", text=".001") + prop.value = 0.001 * sign + + col.separator() + row = layout.row(align=True) + row.split().prop(t_props, "voxel_edit_size_presets", text="", icon='OUTLINER_DATA_GP_LAYER') + if t_props.voxel_edit_size_presets: + layout.grid_flow(row_major=True, columns=1, even_columns=True).prop(prefs, "voxel_size_presets", text="") + else: + presets = prefs.voxel_size_presets + for valor in presets: # Creamos boton por valor + row.operator('bas.voxel_size_change', text=str(round(valor, 5))[1:6]).value = valor # AQUI LLAMAMOS A NUESTRO OPERADOR + + col.separator() + col.prop(mesh, "remesh_voxel_adaptivity") + col.prop(mesh, "use_remesh_fix_poles") + col.prop(mesh, "use_remesh_smooth_normals") + col.prop(mesh, "use_remesh_preserve_volume") + col.prop(mesh, "use_remesh_preserve_paint_mask") + col.prop(mesh, "use_remesh_preserve_sculpt_face_sets") + + col.separator() + col.prop(t_props, "voxel_join_object") + col.separator() + _row = col.box() + row = _row.column().row(align=True) + row.alignment = 'LEFT' + row.active = True if t_props.voxel_join_object == None else False + row.prop(t_props, 'voxel_reprojection') + + col.separator() + col = layout.column() + col.scale_y = 2 + if t_props.voxel_join_object != None: + col.operator('bas.voxel_remesh_join', text="Remesh (Join)") + elif t_props.voxel_reprojection != 'NONE': + col.operator('bas.voxel_remesh_reproject', text="Remesh (Reproject)") + elif context.sculpt_object.use_dynamic_topology_sculpting: + col.operator('bas.voxel_remesh', text="Remesh (Dyn)") + else: + col.operator('object.voxel_remesh', text="Remesh") diff --git a/atelier/tools/remesh/utils.py b/atelier/tools/remesh/utils.py new file mode 100644 index 0000000..5b0982b --- /dev/null +++ b/atelier/tools/remesh/utils.py @@ -0,0 +1,40 @@ +from bpy.types import Operator +from bpy.props import FloatProperty + + +class BAS_OT_voxel_size_increment(Operator): + bl_idname = "bas.voxel_size_increment" + bl_label = "Increment Voxel Size" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + value : FloatProperty(default=0.1, min=-.5, max=.5, name="Voxel Size Increment") + + @classmethod + def poll(cls, context): + return context.active_object and context.active_object.type == 'MESH' + + def execute(self, context): + context.active_object.data.remesh_voxel_size += self.value + return {'FINISHED'} + + +class BAS_OT_voxel_size_change(Operator): + bl_idname = "bas.voxel_size_change" + bl_label = "Change Voxel Size" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + value : FloatProperty(default=0.1, min=.00001, max=1, name="Voxel Size Value") + + @classmethod + def poll(cls, context): + return context.active_object and context.active_object.type == 'MESH' + + def execute(self, context): + context.active_object.data.remesh_voxel_size = self.value + return {'FINISHED'} + + +utils_classes = [ + BAS_OT_voxel_size_increment, + BAS_OT_voxel_size_change +] diff --git a/atelier/tools/rmb/__init__.py b/atelier/tools/rmb/__init__.py new file mode 100644 index 0000000..acc9f29 --- /dev/null +++ b/atelier/tools/rmb/__init__.py @@ -0,0 +1,53 @@ +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# ***** END GPL LICENSE BLOCK ***** + +# INFORMATION # +# THIS CODE IS BASED ON THE ADDON BRUSH QUICKSET BY Jean Ayer +# EXTENDED, IMPROVED AND SOME BUG_ FIXES BY jfranmatheu + + +def register(): + from .ops import classes + from bpy.utils import register_class + + for cls in classes: + register_class(cls) + + from .data import RMBPG + register_class(RMBPG) + + from bpy.types import Scene as scn + from bpy.props import PointerProperty as Pointer + scn.bas_rmb = Pointer(type=RMBPG) + + from . km import register_keymap + register_keymap() + +def unregister(): + from . km import unregister_keymap + unregister_keymap() + + from bpy.utils import unregister_class + from bpy.types import Scene as scn + del scn.bas_rmb + + from .ops import classes + for cls in reversed(classes): + unregister_class(cls) + + from .data import RMBPG + unregister_class(RMBPG) diff --git a/atelier/tools/rmb/__pycache__/__init__.cpython-37.pyc b/atelier/tools/rmb/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e69f65f9b6a8d2fd2020f57ca893b50595e8b04a GIT binary patch literal 1038 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 IBW->0zZtFga{vGU literal 0 HcmV?d00001 diff --git a/atelier/tools/rmb/__pycache__/km.cpython-37.pyc b/atelier/tools/rmb/__pycache__/km.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6f4099de305875cefd305559a35d74915ec7130 GIT binary patch literal 1012 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 newval > -0.1: + unify_settings.strength = newval + self.strmod_total += modrate + + + else: + if self.increments: + modrate = self.strmod * 0.0001 + newval = self.brush.strength + modrate + else: + modrate = self.strmod * 0.0025 + newval = self.brush.strength + modrate + if 10.0 > newval > -0.1: + self.brush.strength = newval + self.strmod_total += modrate + + + if self.doingrad: + if self.uni_size: + newval = unify_settings.size + self.radmod + if 2000 > newval > 0: + unify_settings.size = newval + self.radmod_total += self.radmod + else: + newval = self.brush.size + self.radmod + if 2000 > newval > 0: + self.brush.size = newval + self.radmod_total += self.radmod + + + +def revertChanges(self): + unify_settings = bpy.context.tool_settings.unified_paint_settings + + if self.doingstr: + if self.uni_str: + unify_settings.strength -= self.strmod_total + else: + self.brush.strength -= self.strmod_total + + if self.doingrad: + if self.uni_size: + unify_settings.size -= self.radmod_total + else: + self.brush.size -= self.radmod_total + +class SCULPT_OT_brush_rmb(bpy.types.Operator): + bl_idname = "sculpt.brush_rmb" + bl_label = "Brush RMB Quick Tweak" + + axisaffect : bpy.props.EnumProperty( + name = "Axis Order", + description = "Which axis affects which brush property", + items = [('YSTR', 'X: Radius, Y: Strength', ''), + ('YRAD', 'Y: Radius, X: Strength', '')], + default = 'YRAD') + + textSize : bpy.props.EnumProperty( + name = "Size Value", + description = "Text display; only shows when strength adjusted", + items = [('NONE', 'None', ''), + ('LARGE', 'Large', ''), + ('MEDIUM', 'Medium', ''), + ('SMALL', 'Small', '')], + default = 'MEDIUM') + + keyaction : bpy.props.EnumProperty( + name = "Key Action", + description = "Hotkey second press or initial release behaviour", + items = [('IGNORE', 'Key Ignored', ''), + ('CANCEL', 'Key Cancels', ''), + ('FINISH', 'Key Applies', '')], + default = 'FINISH') + + text : bpy.props.EnumProperty( + name = "Numeric", + description = "Text display; only shows when strength adjusted", + items = [('NONE', 'None', ''), + ('LARGE', 'Large', ''), + ('MEDIUM', 'Medium', ''), + ('SMALL', 'Small', '')], + default = 'LARGE') + + slider : bpy.props.EnumProperty( + name = "Slider", + description = "Slider display for strength visualization", + items = [('NONE', 'None', ''), + ('LARGE', 'Large', ''), + ('MEDIUM', 'Medium', ''), + ('SMALL', 'Small', '')], + default = 'NONE') + + deadzone : bpy.props.IntProperty( + name = "Deadzone", + description = "Screen distance after which movement has effect", + default = 4, + min = 0) + + sens : bpy.props.FloatProperty( + name = "Sens", + description = "Multiplier to affect brush settings by", + default = 1.0, + min = 0.1, + max = 2.0) + + graphic : bpy.props.BoolProperty( + name = "Graphic", + description = "Transparent circle to visually represent strength", + default = True) + + lock : bpy.props.BoolProperty( + name = "Lock Axis", + description = "When adjusting one value, lock the other", + default = True) + + + @classmethod + def poll(cls, context): + return (context.area.type == 'VIEW_3D' + and context.mode in {'SCULPT', 'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE'}) + + def changeValues(self, context): + rmb = context.scene.bas_rmb + self.deadzone = rmb.deadzone_prop + self.sens = rmb.sens_prop + #self.slider = scn.textDisplaySize + self.text = rmb.textDisplaySize + self.textSize = rmb.textDisplaySize + if rmb.invertAxis: + self.axisaffect = 'YSTR' + else: + self.axisaffect = 'YRAD' + + def modal(self, context, event): + self.changeValues(context) + + self.cur = (event.mouse_region_x, event.mouse_region_y) + diff = (self.cur[0] - self.prev[0], self.cur[1] - self.prev[1]) + #sens = (10) if event.ctrl else (self.sens) + deadzone = self.deadzone + sens = (self.sens * 0.5) if event.shift else (self.sens) + if event.ctrl: + self.increments = True + else: + self.increments = False + + if self.axisaffect == 'YRAD': + # Y corresponds to radius + s = (-1) if (self.cur[1] < self.prev[1]) else (0) if (self.cur[1] == self.prev[1]) else (1) + if not self.doingrad: + if self.lock: + if not self.doingstr and abs(self.cur[1] - self.start[1]) > deadzone: + self.doingrad = True + self.radmod = (self.strmod+10*s) if self.increments else (diff[1] * sens) + elif abs(self.cur[1] - self.start[1]) > deadzone: + self.doingrad = True + self.radmod = (self.strmod+10*s) if self.increments else (diff[1] * sens) + else: + self.radmod = (self.strmod+10*s) if self.increments else (diff[1] * sens) + #x = (-1) if (self.cur[0] < self.prev[0]) else (0) if (self.cur[0] == self.prev[0]) else (1) + if not self.doingstr: + if self.lock: + if not self.doingrad and abs(self.cur[0] - self.start[0]) > deadzone: + self.doingstr = True + self.strmod = diff[0] * sens#(self.strmod+.1*x) if self.increments else (diff[0] * sens) + elif abs(self.cur[0] - self.start[0]) > deadzone: + self.doingstr = True + self.strmod = diff[0] * sens#(self.strmod+.1*x) if self.increments else (diff[0] * sens) + else: + self.strmod = diff[0] * sens#(self.strmod+.1*x) if self.increments else (diff[0] * sens) + else: + # Y corresponds to strength + s = (-1) if (self.cur[0] < self.prev[0]) else (0) if (self.cur[0] == self.prev[0]) else (1) + if not self.doingrad: + if self.lock: + if not self.doingstr and abs(self.cur[0] - self.start[0]) > deadzone: + self.doingrad = True + self.radmod = (self.strmod+10*s) if self.increments else (diff[1] * sens) + elif abs(self.cur[0] - self.start[0]) > deadzone: + self.doingrad = True + self.radmod = (self.strmod+10*s) if self.increments else (diff[1] * sens) + else: + self.radmod = (self.strmod+10*s) if self.increments else (diff[1] * sens) + #x = (-1) if (self.cur[1] < self.prev[1]) else (0) if (self.cur[1] == self.prev[1]) else (1) + if not self.doingstr: + if self.lock: + if not self.doingrad and abs(self.cur[1] - self.start[1]) > deadzone: + self.doingstr = True + self.strmod = diff[0] * sens#(self.strmod+.1*x) if self.increments else (diff[0] * sens) + elif abs(self.cur[1] - self.start[1]) > deadzone: + self.doingstr = True + self.strmod = diff[0] * sens#(self.strmod+.1*x) if self.increments else (diff[0] * sens) + else: + self.strmod = diff[0] * sens#(self.strmod+.1*x) if self.increments else (diff[0] * sens) + + context.area.tag_redraw() + if event.type in {'LEFTMOUSE'} or self.action == 1: + # apply changes, finished + if hasattr(self, '_handle'): + context.space_data.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + applyChanges(self) + return {'FINISHED'} + elif event.type in {'ESC'} or self.action == -1: + # do nothing, return to previous settings + if hasattr(self, '_handle'): + context.space_data.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + revertChanges(self) + return {'CANCELLED'} + elif self.keyaction != 'IGNORE' and event.type in {self.hotkey} and event.value == 'RELEASE': + # if key action enabled, prepare to exit + if self.keyaction == 'FINISH': + if hasattr(self, '_handle'): + context.space_data.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + self.action = 1 + elif self.keyaction == 'CANCEL': + if hasattr(self, '_handle'): + context.space_data.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + self.action = -1 + return {'RUNNING_MODAL'} + else: + # continuation + applyChanges(self) + self.prev = self.cur + return {'RUNNING_MODAL'} + return {'CANCELLED'} + + + def invoke(self, context, event): + if bpy.context.mode == 'SCULPT': + self.brush = context.tool_settings.sculpt.brush + elif bpy.context.mode == 'PAINT_TEXTURE': + self.brush = context.tool_settings.image_paint.brush + elif bpy.context.mode == 'PAINT_VERTEX': + self.brush = context.tool_settings.vertex_paint.brush + elif bpy.context.mode == 'PAINT_WEIGHT': + self.brush = context.tool_settings.weight_paint.brush + else: + self.report({'WARNING'}, "Mode invalid - only paint or sculpt") + return {'CANCELLED'} + + self.hotkey = event.type + if self.hotkey == 'NONE': + self.keyaction = 'IGNORE' + self.action = 0 + unify_settings = context.tool_settings.unified_paint_settings + self.uni_size = unify_settings.use_unified_size + self.uni_str = unify_settings.use_unified_strength + self.cd = 10 + self.timer = 0 + self.doingrad = False + self.doingstr = False + self.start = (event.mouse_region_x, event.mouse_region_y) + self.prev = self.start + self.radmod_total = 0.0 + self.strmod_total = 0.0 + self.radmod = 0.0 + self.strmod = 0.0 + self.increments = False + + # self._handle = context.space_data.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL') + + if self.graphic: + if not hasattr(self, '_handle'): + self._handle = context.space_data.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL') + + self.brushcolor = self.brush.cursor_color_add + if self.brush.sculpt_capabilities.has_secondary_color and self.brush.direction in {'SUBTRACT','DEEPEN','MAGNIFY','PEAKS','CONTRAST','DEFLATE'}: + self.brushcolor = self.brush.cursor_color_subtract + + if self.text != 'NONE': + if not hasattr(self, '_handle'): + self._handle = context.space_data.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL') + + self.offset = (30, -37) + + self.backcolor = Color((1.0, 1.0, 1.0)) - context.preferences.themes['Default'].view_3d.space.text_hi + + if self.slider != 'NONE': + if not hasattr(self, '_handle'): + self._handle = context.space_data.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL') + + if self.slider == 'LARGE': + self.sliderheight = 16 + self.sliderwidth = 180 + elif self.slider == 'MEDIUM': + self.sliderheight = 8 + self.sliderwidth = 80 + else: + self.sliderheight = 3 + self.sliderwidth = 60 + + if not hasattr(self, 'offset'): + self.offset = (30, -37) + + if not hasattr(self, 'backcolor'): + self.backcolor = Color((1.0, 1.0, 1.0)) - context.preferences.themes['Default'].view_3d.space.text_hi + + self.frontcolor = context.preferences.themes['Default'].view_3d.space.text_hi + + # enter modal operation + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + +def applyChanges_2(self, context): + brush = context.tool_settings.sculpt.brush + + if self.doingsmooth: + if self.uni_smooth: + modrate = self.smoothmod * 0.0025 + newval = brush.auto_smooth_factor + modrate + if 10.0 > newval > -0.1: + brush.auto_smooth_factor = newval + self.smoothmod_total += modrate + else: + modrate = self.smoothmod * 0.0025 + newval = self.brush.auto_smooth_factor + modrate + if 10.0 > newval > -0.1: + self.brush.auto_smooth_factor = newval + self.smoothmod_total += modrate + +def revertChanges_2(self, context): + brush = context.tool_settings.sculpt.brush + if self.doingsmooth: + if self.uni_smooth: + brush.auto_smooth_factor -= self.smoothmod_total + else: + self.brush.auto_smooth_factor -= self.smoothmod_total + +class SCULPT_OT_brush_rmb_alt(bpy.types.Operator): + bl_idname = "sculpt.brush_rmb_alt" + bl_label = "Brush RMB Quick Tweak" + + axisaffect : bpy.props.EnumProperty( + name = "Axis Order", + description = "Which axis affects which brush property", + items = [('YSTR', 'X: Radius, Y: Strength', ''), + ('YRAD', 'Y: Radius, X: Strength', '')], + default = 'YRAD') + + textSmooth : bpy.props.EnumProperty( + name = "Smooth Value", + description = "Text display; only shows when strength adjusted", + items = [('NONE', 'None', ''), + ('LARGE', 'Large', ''), + ('MEDIUM', 'Medium', ''), + ('SMALL', 'Small', '')], + default = 'LARGE') + + keyaction : bpy.props.EnumProperty( + name = "Key Action", + description = "Hotkey second press or initial release behaviour", + items = [('IGNORE', 'Key Ignored', ''), + ('CANCEL', 'Key Cancels', ''), + ('FINISH', 'Key Applies', '')], + default = 'FINISH') + + slider : bpy.props.EnumProperty( + name = "Slider", + description = "Slider display for strength visualization", + items = [('NONE', 'None', ''), + ('LARGE', 'Large', ''), + ('MEDIUM', 'Medium', ''), + ('SMALL', 'Small', '')], + default = 'LARGE') + + deadzone : bpy.props.IntProperty( + name = "Deadzone", + description = "Screen distance after which movement has effect", + default = 4, + min = 0) + + sens : bpy.props.FloatProperty( + name = "Sens", + description = "Multiplier to affect brush settings by", + default = 1.0, + min = 0.1, + max = 2.0) + + graphic : bpy.props.BoolProperty( + name = "Graphic", + description = "Transparent circle to visually represent strength", + default = True) + + lock : bpy.props.BoolProperty( + name = "Lock Axis", + description = "When adjusting one value, lock the other", + default = True) + + + @classmethod + def poll(cls, context): + return (context.area.type == 'VIEW_3D' + and context.mode in {'SCULPT', 'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE'}) + + + def changeValues(self, context): + rmb = context.scene.bas_rmb + self.deadzone = rmb.deadzone_prop + self.sens = rmb.sens_prop + self.textSmooth = rmb.textDisplaySize + self.slider = rmb.textDisplaySize + if rmb.invertAxis: + self.axisaffect = 'YSTR' + else: + self.axisaffect = 'YRAD' + + def modal(self, context, event): + self.changeValues(context) + sens = (self.sens * 0.5) if event.shift else (self.sens) + self.cur = (event.mouse_region_x, event.mouse_region_y) + diff = (self.cur[0] - self.prev[0], self.cur[1] - self.prev[1]) + X = True + if self.axisaffect == 'XRAD': + # Y corresponds to Smooth + if not self.doingspac: + if self.lock: + if not self.doingsmooth and abs(self.cur[1] - self.start[1]) > self.deadzone: + self.doingspac = True + self.spacemod = diff[1] * sens + elif abs(self.cur[1] - self.start[1]) > self.deadzone: + self.doingspac = True + self.spacemod = diff[1] * sens + else: + self.spacemod = diff[1] * sens + if not self.doingsmooth: + if self.lock: + if not self.doingspac and abs(self.cur[0] - self.start[0]) > self.deadzone: + self.doingsmooth = True + self.smoothmod = diff[0] * sens + elif abs(self.cur[0] - self.start[0]) > self.deadzone: + self.doingsmooth = True + self.smoothmod = diff[0] * sens + else: + self.smoothmod = diff[0] * sens + else: + # Y corresponds to Spacing + if not self.doingspac: + if self.lock: + if not self.doingsmooth and abs(self.cur[0] - self.start[0]) > self.deadzone: + self.doingspac = True + self.spacemod = diff[0] * sens + elif abs(self.cur[0] - self.start[0]) > self.deadzone: + self.doingspac = True + self.spacemod = diff[0] * sens + else: + self.spacemod = diff[0] * sens + if not self.doingsmooth: + if self.lock: + if not self.doingspac and abs(self.cur[1] - self.start[1]) > self.deadzone: + self.doingsmooth = True + self.smoothmod = diff[1] * sens + elif abs(self.cur[1] - self.start[1]) > self.deadzone: + self.doingsmooth = True + self.smoothmod = diff[1] * sens + else: + self.smoothmod = diff[1] * sens + + context.area.tag_redraw() + if event.type in {'LEFTMOUSE'} or self.action == 1: + # apply changes, finished + if hasattr(self, '_handle'): + context.space_data.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + applyChanges_2(self, context) + return {'FINISHED'} + elif event.type in {'ESC'} or self.action == -1: + # do nothing, return to previous settings + if hasattr(self, '_handle'): + context.space_data.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + revertChanges(self) + return {'CANCELLED'} + elif self.keyaction != 'IGNORE' and event.type in {self.hotkey} and event.value == 'RELEASE': + # if key action enabled, prepare to exit + if self.keyaction == 'FINISH': + if hasattr(self, '_handle'): + context.space_data.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + self.action = 1 + elif self.keyaction == 'CANCEL': + if hasattr(self, '_handle'): + context.space_data.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + self.action = -1 + return {'RUNNING_MODAL'} + else: + # continuation + applyChanges_2(self, context) + self.prev = self.cur + return {'RUNNING_MODAL'} + return {'CANCELLED'} + + + def invoke(self, context, event): + if bpy.context.mode == 'SCULPT': + self.brush = context.tool_settings.sculpt.brush + elif bpy.context.mode == 'PAINT_TEXTURE': + self.brush = context.tool_settings.image_paint.brush + elif bpy.context.mode == 'PAINT_VERTEX': + self.brush = context.tool_settings.vertex_paint.brush + elif bpy.context.mode == 'PAINT_WEIGHT': + self.brush = context.tool_settings.weight_paint.brush + else: + self.report({'WARNING'}, "Mode invalid - only paint or sculpt") + return {'CANCELLED'} + + self.hotkey = event.type + if self.hotkey == 'NONE': + self.keyaction = 'IGNORE' + self.action = 0 + unify_settings = context.tool_settings.unified_paint_settings + self.uni_size = unify_settings.use_unified_size + self.uni_smooth = self.brush.auto_smooth_factor + #self.uni_str = unify_settings.use_unified_strength + self.smooth = self.brush.auto_smooth_factor + + self.doingsmooth = False + self.doingspac = False + self.start = (event.mouse_region_x, event.mouse_region_y) + self.prev = self.start + self.smoothmod_total = 0.0 + self.spacemod_total = 0.0 + #self.strmod_total = 0.0 + self.smoothmod = 0.0 + self.spacemod = 0.0 + #self.strmod = 0.0 + + # self._handle = context.space_data.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_PIXEL') + + if self.graphic: + if not hasattr(self, '_handle'): + self._handle = context.space_data.draw_handler_add(draw_callback_px_2, (self, context), 'WINDOW', 'POST_PIXEL') + + self.brushcolor = self.brush.cursor_color_add + if self.brush.sculpt_capabilities.has_secondary_color and self.brush.direction in {'SUBTRACT','DEEPEN','MAGNIFY','PEAKS','CONTRAST','DEFLATE'}: + self.brushcolor = self.brush.cursor_color_subtract + + if self.textSmooth != 'NONE': + if not hasattr(self, '_handle'): + self._handle = context.space_data.draw_handler_add(draw_callback_px_2, (self, context), 'WINDOW', 'POST_PIXEL') + + self.offset = (30, -37) + + self.backcolor = Color((1.0, 1.0, 1.0)) - context.preferences.themes['Default'].view_3d.space.text_hi + + if self.slider != 'NONE': + if not hasattr(self, '_handle'): + self._handle = context.space_data.draw_handler_add(draw_callback_px_2, (self, context), 'WINDOW', 'POST_PIXEL') + + if self.slider == 'LARGE': + self.sliderheight = 16 + self.sliderwidth = 180 + elif self.slider == 'MEDIUM': + self.sliderheight = 8 + self.sliderwidth = 80 + else: + self.sliderheight = 3 + self.sliderwidth = 60 + + if not hasattr(self, 'offset'): + self.offset = (30, -37) + + if not hasattr(self, 'backcolor'): + self.backcolor = Color((1.0, 1.0, 1.0)) - context.preferences.themes['Default'].view_3d.space.text_hi + + self.frontcolor = context.preferences.themes['Default'].view_3d.space.text_hi + + # enter modal operation + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + +classes = ( + SCULPT_OT_brush_rmb, + SCULPT_OT_brush_rmb_alt +) diff --git a/atelier/tools/sculpt_notes/__init__.py b/atelier/tools/sculpt_notes/__init__.py new file mode 100644 index 0000000..328aece --- /dev/null +++ b/atelier/tools/sculpt_notes/__init__.py @@ -0,0 +1,47 @@ +def register(): + from .ops import classes + from bpy.utils import register_class + + from .ui import BAS_PT_sculpt_notes + register_class(BAS_PT_sculpt_notes) + + from .utils import utils_classes + for cls in utils_classes: + register_class(cls) + + from .ops_curves import curve_classes + for cls in curve_classes: + register_class(cls) + + for cls in classes: + register_class(cls) + + from .data import SculptNotesPG + register_class(SculptNotesPG) + + from bpy.types import WindowManager as wm + from bpy.props import PointerProperty as Pointer + wm.bas_sculptnotes = Pointer(type=SculptNotesPG) + +def unregister(): + from bpy.utils import unregister_class + from bpy.types import WindowManager as wm + del wm.bas_sculptnotes + + from .data import SculptNotesPG + unregister_class(SculptNotesPG) + + from .ui import BAS_PT_sculpt_notes + unregister_class(BAS_PT_sculpt_notes) + + from .ops import classes + for cls in reversed(classes): + unregister_class(cls) + + from .ops_curves import curve_classes + for cls in reversed(curve_classes): + unregister_class(cls) + + from .utils import utils_classes + for cls in reversed(utils_classes): + unregister_class(cls) diff --git a/atelier/tools/sculpt_notes/__pycache__/__init__.cpython-37.pyc b/atelier/tools/sculpt_notes/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5fb74dd8c92405566c0556880212c792cbe96f93 GIT binary patch literal 1349 zcmZXUOKTKC5P-Yqu`|2bS2RkZ2*HD}mmuOrkzgJ~5L{NH1j5KX* z=+(a=2>t?({*1kP%3ttgRn2Y^<3Lw;RoA1t>)YLKwHgAy$FF~c4_qOBld||MTs+5N zR!}IRN~&mG|oe|3~LjvXB47YZenr9~o3A%(J)qpid)C9b7%wUfA| zCB;-utP4YBjEAtGaF`t(f|=M>l_cw{C3sl>RgzsaV6jq_DEN` ztE|>pGV|u=|1Mw^GyOT+c^bSk5t!iRUa*ztyLg7+O*Ty8^d#8UQK}+X-^<1+9xl$( zg(-Nn@#JA(MiA$P35H5#sR_1uL%CG;g7T?6a+HIlyGe~n7jx1bRnuPL z6X{biwV6i;*{&yz(m}RYizT9-Np{i0EdM;JTn7O(a)XiNp3Pugd0x84^GtdUKd(8!CewJNDx6l{~EAN zf}Rc9BnU}pl3;4FBc}*Lq@YVS$_X73oYt>0-X;U5;AjejTO`<>JejLXtf4w<_)IJN&Q$<$a!o6HcM#b5gi6@w|b literal 0 HcmV?d00001 diff --git a/atelier/tools/sculpt_notes/__pycache__/data.cpython-37.pyc b/atelier/tools/sculpt_notes/__pycache__/data.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e3c17a7ed90f43abc3b45dd4d1497b873ca037c7 GIT binary patch literal 5456 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 OrlLK}rKi$U+5ZC7DhE9P literal 0 HcmV?d00001 diff --git a/atelier/tools/sculpt_notes/__pycache__/ops.cpython-37.pyc b/atelier/tools/sculpt_notes/__pycache__/ops.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e60fb975478c8e9b04f9f16e807488d7d89fa0c GIT binary patch literal 8338 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%0DiFT>u*ln4rCML)64c@k@wxnQ%x{UlM03ETvt< zw-r6unP5ae$;AgFiQHbJFMRjcf6g87B4!>gm){RvE@R;s2HhbzCF*8D95lH(;Fbp$ zEKMDho{H@_-68pX%18{~{NTOsdDzm8ef87QhHiPfI@WFz#D^uXqsC#HG%m4*b-t6%4LqB1ES#G0a4QZP!tQUWBL*T)!r+UqSm<+l2)w zgwxur6fZ2leee^1^pUrJc`Ud+Nj2bbz>_P!4gtr$$PP>fw-`u&a>CoJJl(B=VT9aw zLBX*>)fXD`qC|v}Lf%(z*}GSp+#|f-aNnpdc1fOE!Fe|z68OTvTE;!)I%=B83>bF94l+Z^j#AlNwD zbh&}!j2();5P0DN;~X@xgYb17jGW*!qja4Hry2!c3D(+-;oHACv`IU2ZiYhccAg@v zw9)7kjV}|K4{E?$N(AR1-P0Ced)>ZmnCZc{>ANv;9!S95H~}s&3(hqOIB?B@GjtMc zeM(ZpOnE8!UTehNXXfbmV~-g7F&a3G+K24n?Gke+Ov6x^#slYVaKU;r2ch?HGwwu9M5+? zG}srVMmDU$H*4_Ss?qHpvmf1l%qx1`VzBRlJ3--g9JE7!px+E|K;PiwbD`0_0}~kN zbr|$g+sE;RCfqE@^?E6L6jDTi^9->*Wt_n1H5ga%{i3KbFc(O{iMf@MnHn}?;VM1R zGUChpKFPVWPlD&~4a>X1Pw%ecQ=stuq1leB`V>sUp?P2GKMj){zWC}`^}V~`42<}J zI{+M^Aw%>U&$Kbw7n&jC_OqaRe=N8>hi~!t_2WUgQ=Y{dJ`ZENj`FWyP5bHtl2ei# zA2L=SWP^N@^zm8y1+Q;d_9j!tiwL<}o;A**%}X%XUqWicW)Fw)MOx6qIS(v{eGUd` z-g@W6B+d7j*)8b{;rn9!Jkp%$-MOXf)&YBWZoBc7o6H^hyM+EeE+cK7m~PPNW3y8o z%8b8Dvhj-upe5O4yhGt4<}dX(kXz0eA6c)Ma>^gFe+5#nrv4@>wwa_*SF<1*5+t?> z`#yuuGWZn)p2o6fA{W!9V}24b+u(WW3g6QUGtNK3(dQq3nNCOjz#zua2)OLxa+=$ z4D}-h;|%21`yVBDp2mysE~%58!;-{iyBn-azo~Do3fmLN?l~Z#mzNT<}(#2 z_t_fXa1n|gsHF%8F@#X!Lt#a;0#*BjLl{(I@vaX38MLL+p9=DaTmr)z8G~(!6>Tn` z`X%m0PNPG1iuW=U(xg7Pm#HBV%FQ^w)e7RM1MQNLa6`91e!V3nQ9G+!Ox3wl}FEUtWGELHm-i^9QZhxO@yU(N&t5( cn#ysX!@*7~{#1njrPPb5Sn7GCmUQj^0hg*I8UO$Q literal 0 HcmV?d00001 diff --git a/atelier/tools/sculpt_notes/__pycache__/prop_fun.cpython-37.pyc b/atelier/tools/sculpt_notes/__pycache__/prop_fun.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2d772cf582a9b167b6eea58c7ef7004c41b826e GIT binary patch literal 4569 zcmd^D&u<&Y72eq;mrIJGXjzs-TWQm@Fw7PR+#p5syOn9umMc>VN!zhNbW3s85?9nN zF}sqjf+|p?*8)KgJ-0wFEpp7Er~C>11LoLXqHEAgd+PVzk~D2oZVdER5&O;0H}mGr zyl>v<#{7KFz~9>+fA0S7yN2-(DvbY3WPXY-Y5}AnMc>#lq$#c67&}6yWE!a{ZJ9x8 zNk?XpresdeAx+D?oJVTQf?PnFkwtj{sUsKVMWk6Nnnro)3?m!$F3m)Ll-l?z_@Wqa zvO6?<%za>~U&JCdealZ#jh`+VzAXhX^Qn;LsxdTNM+#sT?Z_RN=St+BN<1)vyfEnZ z&PlelGlKG*&UKH}vDe)13_NZ1y+B*LgA<*Sow%cIZi{p(=p1@F6Ak*k zz>D^+J9Y^Wj>`uF3?d+mFXr=s?qT6=vkxCuSljj(gr3-;PKpr7>A>$UJGfP%d+7+1C5 zdh^}u?Wn7IgE(q;qzr?oy&ijg=-KQZ^#^e~4#R$gE>v)XF!rMMK!pSM(NXXwR{Yx0 z07H6i(xFc6?$b4-pj)&EFoY>`;tgQ~uL5nMsCu%;77CL+ejA08@6nE)nnM%&Y^o*N z1JsD2>6^5}uD~wkl3k)U-XpWF)6KBolVwYrdjp+~_j}y~*gn$P!%+4fVOdexQUy$) z7694`cYU^C0vD+6jTuXdXjFGH9LYvB3a!f)j!2n{w5CosS@C28m!IG zV50hU2v4iAcCH$Kf*+AR*e5@dCVUhg3p5!m#uQ_k(Pqp@i+uVs5hNUt!r2&d%UC*% z_OUeO(~{6;%t(7A&8JIdApKWv5sW6I#WKE7m&;*?+!*Y>s6=XW6*} z^E08|dIjk`O9w1H&(ec)rC(fifu*mXD?zIp+bLP;0!i>0Es{@dZEoCbDLTwLRoh%| z)y|UqgU0&y8TqbGSMD|LSI@}db!vOPb^9z$S-aV4Y-~5r=x8#3ma}^9(2Ms&c+&~E zv(W7XcR+~EScL~5zy*}=dTP%bSL9LA7`p~|uM^bSU8rAAsZi-GvdzOVjQ2U)@D9Cb zU)#wnFFVSm8Amyh`VrKPeg!bh96J;-R`Sb%!N&RXYMdAPI3oX?)QlSQ*>S-qKZFLe z-T3=xq0^vVPxu6RGrAh%CiK&Pu>Xj=D@Z&hVH&I6s+BYPDpRw26nPc8zV3DgwNN$r ze%j_+snLX(Xl+6o5|F2PQ;^V;-f;dZ%G zawoWlox#gC3NV`bG0GD&QRp(cO4e{rhk`DWSoj<$ND#$u zg&^alPpzTF*UJ}Zwfzk096yWjAvbA}CrcAa&)5EmCdTPI@&uM0u@k=t#L~;%J!l@J3NEc2)asDNlP|WS2!`fdSrrp)x zm|F@gBZt&)4l7IAcDQYU+kVe&SE%hYJIvAuz!YP#WIVEXv}-JJfhE428f`S=sv(P4 z4C*;q=i+FcHr29y#x^1QOl&o_&o|q*^2NHgw{G02R$67DQwS0~a8~8s=5|7N%vscw zj$)kknqJsyY~0$YySJ)acdM<&Ly}@5vn$nltJ=`^jq1(@(sXU3(QN6|<~m`0>t$j?Uel!w@v$@qMuj%jdn;qH}Yadl&hFi zGt?o?PNE#j=XbqhuRk7Sjye)4t1JQehMEWH9KuM=XpKM=PCx8&l&LBG6VVg@RJEo2L0&+!sd2tz~HM0oXNqUdRQJ-Lj*(CHgigo@! zNJ#jX{ADJdm2-(_m1qj_&)N&bQzBA1NTKZ=j#Wy*5^74IO#^kC)QY&I#B@=bNig{l z!K_SZV}6P@R{m$SQK+kTu-|2&-bJF`BcPCI%opl?6l&@NAo`FWsGkrJ1*n@;dqq_# zPj@GKxvEj_7QwULt!`5t`L^01IM1ck9cuj?53%edq|qzW@7X~o-X9U_%c7TW z&9K6h=)Xu0+D2MB#gM9vJj~BWfL_D=d{p#wQ}l4+=#OI?<`zY(W0@)Q^G5Ki)%gHi deAM?oqJL2Eo7=IRIcLpD<5zLA&WFzX{{bBx{Nw-t literal 0 HcmV?d00001 diff --git a/atelier/tools/sculpt_notes/__pycache__/ui.cpython-37.pyc b/atelier/tools/sculpt_notes/__pycache__/ui.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fed2b906e90dd13188af0b62f287b3f8ac63820c GIT binary patch literal 6476 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 literal 0 HcmV?d00001 diff --git a/atelier/tools/sculpt_notes/data.py b/atelier/tools/sculpt_notes/data.py new file mode 100644 index 0000000..3e8a943 --- /dev/null +++ b/atelier/tools/sculpt_notes/data.py @@ -0,0 +1,82 @@ +from bpy.types import PropertyGroup +from bpy.props import * +from . prop_fun import * + + +class SculptNotesPG(PropertyGroup): + # SCULPT NOTES + curve_postEdit : BoolProperty(default=False, name="Post-Edition of curves in Sculpt Mode", description="You'll be able to edit result curve in Sculpt Mode. Ctrl to Move. Shift for extrude. S for Scale. D for Rotate. Ctrl+Shift for snap. Alt to show handles.") + live : BoolProperty(default=False, name="Live sculpting", description="Live sculpting. ONLY with annotations") + autoClear : BoolProperty(default=True, name="Auto-Clear after convert", description="Clear all made notes automatically after converting them") + use : EnumProperty ( + items=( + ('NOTES', "Annotations", "Use annotations to sculpt"), + ('GP', "Grease Pencil", "Use GP to sculpt") + ), + default='NOTES', name="Annotations or Grease Pencil?", description="", + #update=update_sculptNotes_use + ) + notes_symmetry_x : BoolProperty(default=False, name="Use X Symmetry for annotations?") + thickness : FloatProperty(name="Thickness", min=0.001, max=1, default=.1, subtype='DISTANCE', description="Change thickness of sculpted notes", update=update_sculptNotes_solid_thickness) + #sculptNotes_radius : FloatProperty(name="Radius", max=10, min=0.001, default=.05, subtype='DISTANCE', description="Change Radious for the path of sculpted notes", update=update_sculptNotes_curve_radius) + gp : PointerProperty(type=bpy.types.Object, name="Pointer to GP Object") + curve : PointerProperty(type=bpy.types.Object, name="Pointer to GP Curve") + path_object : PointerProperty(type=bpy.types.Object, name="Object To Follow The Path", update=update_sculptNotes_path_object) + path_object_makeCopy : BoolProperty(default=True, name="Make a Copy", description="Make a copy from the original object. Do it if you're aware of loosing that object") + path_object_pivotToCenter : BoolProperty(default=False, name="Obj pivot to its center", description="Force pivot to be in the center of the object") + curve_curveMap_isCreated : BoolProperty(default=False, name="CurveMap is created?") + mergeDistThreshold : FloatProperty(name="Distance Threshold", default=.06, min=0.0001, max=0.5, subtype='DISTANCE', description="Minimum distance between vertices. This will reduce greatly the ammount of vertices / nodes") + isCreated : BoolProperty(default=False, name="A note is created?") + applyModifiersDirectly : BoolProperty(default=True, name="Apply modifiers directly") + joinStrokes : BoolProperty(default=True, name="Join strokes", description="Join meshes extracted from different strokes or keep them separated in different objects") + mergeStrokes : BoolProperty(default=False, name="Merge strokes into One?") + ngon : BoolProperty(default=False, name="N-gons?", description="Use n-gon to fill the face of the mesh?", update=update_sculptNotes_ngon) + reproject : BoolProperty(default=False, name="Re-project?", description="You may get better results, specially for curved surfaces, a little slower", update=update_sculptNotes_reproject) + smooth : BoolProperty(default=False, name="Post-Smooth") + smoothPasses : IntProperty(min=1, max=10, default=2, name="Smooth Passes", description="Number of iterations for smooth") + remeshIt : BoolProperty(default=True, name="Post-Remesh") + mirror : BoolProperty(default=False, name="Post-Mirror") + strips_thicknessForSize : BoolProperty(default=False, name="Use Notes Thickness For Quad Size", description="Instead of the distance threshold") + strips_makeSolid : BoolProperty(default=True, name="Make Solid") + strips_makeBevel : BoolProperty(default=True, name="Make Bevel") + curveShape : PointerProperty(type=bpy.types.Object, name="Curve Shape for Wrapper", update=update_sculptNotes_curveShape) + curve_isCyclic : BoolProperty(default=False, name="Is Cyclic?", description="Make a cyclic curve") + curve_simplify : BoolProperty(default=True, name="Simplify?", description="Simplify curve to make it with less points") + curve_useCurveMapForSplinePointsRadius : BoolProperty(default=False, name="Use CurveMap for Radius", description="Use Curve Map to 'map' the radius of the stroke over it's length", update=update_sculptNotes_curve_curveMap) + #sculptNotes_radiusMultiplier : FloatProperty(name="Radius Multiplier", max=5, min=0.1, default=1, subtype='FACTOR', description="Factor to multiply radius made with curve map", update=update_sculptNotes_curve_radius) + method_type : EnumProperty ( + items=( + ('SOLID', "Solid", "Make a solid block from the draw"), + ('WRAP', "Wrap", "The firsts strokes are for the path, the last stroke in for the shape to wrap the path!"), + ('FLAT', "Flat", "Just flat mesh. Take care is n-gon!"), + ('CURVE', "Curve", "Make a curve with a stroke or various strokes! Use the bevel curve you want and tweak it in Post-Edition without exiting Sculpt-Mode!"), + ('STRIPS', "Strips", "Make perfect quad strips with size based on the threshold or the thickness of the annotation tool!"), + ('PATH', "Path", "Draw shapes and save them as curves to use them later! Make your own shape library!") + ), + default='SOLID', name="Which mode do you want to use?", description="", + update=update_sculptNotes_method + ) + curveShape_pivot_mode : EnumProperty ( + items=( + ('CENTER', "Center", "Pivot at the center of the curve"), + ('FISRT', "Start", "Pivot at the start point of the curve"), + ('LAST', "End", "Pivot at the end point of the curve"), + ('AVERAGE', "Average", "Curve at average point between start and end points"), + ('NODE', "Per Node", "Pivot at specific curve point") + ), + default='CENTER', name="Pivot location", description="Where to align the pivot of the curve?", + update=update_sculptNotes_curveShape_pivot_mode + ) + canJoinStrokes : BoolProperty(default=True) + canMergeStrokes : BoolProperty(default=True) + canReproject : BoolProperty(default=True) + canNgon : BoolProperty(default=True) + canMirror : BoolProperty(default=True) + canSmooth : BoolProperty(default=True) + canRemesh : BoolProperty(default=True) + method : IntProperty(default=1) + #points : bpy.props.FloatVectorProperty(size=5, default=[0,0,0,0,0]) + + showCurveMapEditor : BoolProperty(default=False, name="Show Curve Map Editor") + curveShape_numNodes : IntProperty(min=1, name="Nodes of the Shape", description="Number of Nodes of the Curve Shape") + curveShape_pivot_index : IntProperty(min=-1, name="Pivot Index", description="Index of the node used as pivot point", update=update_sculptNotes_curveShape_pivot_index) diff --git a/atelier/tools/sculpt_notes/draw.py b/atelier/tools/sculpt_notes/draw.py new file mode 100644 index 0000000..d011288 --- /dev/null +++ b/atelier/tools/sculpt_notes/draw.py @@ -0,0 +1,171 @@ + + + +def draw_callback_px(self, context): + #bgl.glEnable(bgl.GL_BLEND) + + if self.start: + value = str(self.solid.thickness)[0:4] + start = "Thickness : " + end = " m" + else: + value = str(self.bevel.width)[0:4] + start = "Bevel Width : " + end = " m" + + text = start + value + end + + header_height = context.area.regions[0].height # 26px + npanel_width = context.area.regions[1].width + transorm_panel_width = context.area.regions[3].width + + width = context.area.width - npanel_width - transorm_panel_width + height = context.area.height + header_height + + # draw text + font_id = 0 # XXX, need to find out how best to get this. + #bgl.glColor(*(1.0, 1.0, 1.0, 1)) + blf.size(font_id, 32, 72) + blf.color(font_id, 1, 1, 1, 1) + dim_x = blf.dimensions(font_id, text)[0] + dim_y = blf.dimensions(font_id, text)[1] + #blf.position(font_id, width/2-dim_x, height/2-dim_y, 0) + blf.position(font_id, width/4+dim_x, height/6-dim_y, 0) + blf.draw(font_id, text) + + text = "Use CTRL + MouseWheel" + blf.size(font_id, 20, 72) + blf.color(font_id, .8, .7, .4, 1) + blf.position(font_id, width/4+dim_x, height/6+dim_y, 0) + blf.draw(font_id, text) + + if self.start and context.scene.sculptNotes_strips_makeBevel: + text = "LMB -> Confirm and Edit Bevel / RMB -> Confirm and Finish" + else: + text = "LMB/RMB -> Confirm and Finish" + blf.size(font_id, 16, 72) + blf.color(font_id, .5, .9, .6, 1) + blf.position(font_id, width/4+dim_x, height/6-dim_y*3, 0) + blf.draw(font_id, text) + # restore opengl defaults + #bgl.glLineWidth(1) + #bgl.glDisable(bgl.GL_BLEND) + #bgl.glColor(0.0, 0.0, 0.0, 1.0) + +distThreshold = 55 +prev = Vector((0, 0, 0)) +def editableCurve_draw_callback_px(self, context): + try: + if not bpy.context.space_data.overlay.show_overlays or not self.spline: + return + except: + return + #fun.Draw_Button_2D_Rectangle(context, event, None, Vector((200, 200)), 500, 500, "Hola Mundo", 24) + #fun.Draw_2D_Button(self.button_1) + #fun.Draw_2D_Button(self.button_2) + n = 0 + lastPointCo = None + penultPointCo = None + minDist = 2000 + numPoints = len(self.spline.bezier_points) + #self.nearPoint = -1 + #self.nearCoord = Vector((0, 0)) + #if self.spline != None: + if numPoints > 0: + self.snapping = False + #k = numPoints - 1 + for point in self.spline.bezier_points: + # OBTENEMOS LAS COORDENADAS EN PANTALLA + v = fun.Convert_3D_spaceCoords_to_2D_screenCoords(context, point.co) + if v != None: + if not self.scaling and not self.tilting: + # CALC DIST FROM MOUSE TO EVERY SINGLE POINT + dist = fun.DistanceBetween(v, self.mousePos) # ya es abs ! + if (dist < minDist): + # SAVE SNAP POINT + if self.canSnap: # Si puede snapear a otros puntos + if self.nearBPoint != point: # Comprobar si no es el punto ya activo + if abs(dist) < self.snapThreshold: # Si está dentro del rango + self.snapping = True + self.nearestSnapPoint = point.co # guardar referencia coordenadas + #else: + # self.snapping = False + minDist = dist + # NOT IF IT'S DRAGGING BUT YES FOR SNAP + if not self.dragging: # Keep active point if you are dragging + # UPDATE NEAREST POINT + self.nearPoint = n + self.nearCoord = Vector((v[0], v[1])) + self.nearBPoint = point + + # DIBUJAMOS CADA LINEA + if n != 0: + fun.Draw_2D_Line(prev, v, (.7, .1, .2, .5)) + #if n == k: + # lastPointCo = v + + # DIBUJAMOS CADA PUNTO + fun.Draw_Text(v[0]-6, v[1]-8, "•", 24, 0, .6, .8, 1, .8) + prev = v + n+=1 + + # DIBUJAMOS EXTRUDE POINT + if self.extruding: # OBVIAR # realizar extrude y cerrar + #if lastPointCo != None: + lastPointCo = fun.Convert_3D_spaceCoords_to_2D_screenCoords(context, self.spline.bezier_points[numPoints-1].co) + #CALCULAR PUNTO # YA NO HACE FALTA EL CALCULO QUE HACIA, CAMBIO DE FORMA + fun.Draw_2D_Line(lastPointCo, self.mousePos, (.4, .8, .5, .5)) + #fun.Draw_2D_Point(self.extrudePoint, (.2, .75, .35, .6)) + fun.Draw_Text(self.mousePos[0]-20, self.mousePos[1]-24, "•", 70, 0, .2, .75, .35, .8) + fun.Draw_Text(self.mousePos[0]-9, self.mousePos[1]-6, "+", 24, 0, 1, 1, 1, .8) + + fun.Draw_Text(lastPointCo[0]-9, lastPointCo[1]-13, "•", 36, 0, 1, .6, .8, .8) + fun.Draw_2D_Circle(lastPointCo, 10, 16, (1, 1, 0, .8)) + self.active = True + self.nearPoint = numPoints - 1 + + fun.Draw_Text(200, 150, "Left Click to confirm extrude", 18, 0, 1, 0.75, 0.5, 1) + # MARCAMOS PUNTO ACTIVO + elif self.scaling or self.tilting: + # DIBUJAMOS PUNTO + fun.Draw_Text(self.nearCoord[0]-9, self.nearCoord[1]-13, "•", 36, 0, 1, .6, .8, .8) + fun.Draw_2D_Circle(self.nearCoord, 10, 8, (1, 1, 0, .8)) + # SCALING + if self.scaling: + rad = str(self.nearBPoint.radius)[0:4] + fun.Draw_Text(self.nearCoord[0]+20, self.nearCoord[1]-8, rad, 24, 0, 1, 1, 1, .8) + # TILTING + if self.tilting: + til = str(math.degrees(self.nearBPoint.tilt))[0:4] + fun.Draw_Text(self.nearCoord[0]+20, self.nearCoord[1]-8, til, 24, 0, 1, 1, 1, .8) + fun.Draw_Text(200, 150, "Left Click to confirm extrude", 18, 0, 1, 0.75, 0.5, 1) + elif (self.nearPoint != -1): + if (minDist < distThreshold): + self.active = True + if self.showHandlers: + self.hL = fun.Convert_3D_spaceCoords_to_2D_screenCoords(context, self.nearBPoint.handle_left) + self.hR = fun.Convert_3D_spaceCoords_to_2D_screenCoords(context, self.nearBPoint.handle_right) + # DIBUJAMOS LINEAS A HANDLERS + fun.Draw_2D_Line(self.hL, self.nearCoord, (.8, .8, .8, .5)) + fun.Draw_2D_Line(self.hR, self.nearCoord, (.8, .8, .8, .5)) + # DIBUJAMOS PUNTOS HANDLERS + fun.Draw_Text(self.hL[0]-6, self.hL[1]-8, "•", 24, 0, 1, 1, 0, .8) + fun.Draw_Text(self.hR[0]-6, self.hR[1]-8, "•", 24, 0, 1, 1, 0, .8) + # DIBUJAMOS PUNTO + if self.dragging: + if self.snapping: + snapPoint2D = fun.Convert_3D_spaceCoords_to_2D_screenCoords(context, self.nearestSnapPoint) + fun.Draw_Text(snapPoint2D[0]-9, snapPoint2D[1]-13, "•", 36, 0, 1, .6, .8, .8) + fun.Draw_2D_Circle(snapPoint2D, 10, 16, (1, 1, 0, .8)) + else: + fun.Draw_Text(self.mousePos[0]-9, self.mousePos[1]-13, "•", 36, 0, 1, .6, .8, .8) + fun.Draw_2D_Circle(self.mousePos, 10, 16, (1, 1, 0, .8)) + else: + fun.Draw_Text(self.nearCoord[0]-9, self.nearCoord[1]-13, "•", 36, 0, 1, .6, .8, .8) + fun.Draw_2D_Circle(self.nearCoord, 10, 16, (1, 1, 0, .8)) + else: + self.active = False + + # DRAW3 INFO + fun.Draw_Text(200, 100, "Ctrl: Move Point | Shift: Extrude | Alt: Show handlers", 18) + fun.Draw_Text(200, 50, "Ctrl + Shift: Snap | S: Scale | D: Rotate", 18) diff --git a/atelier/tools/sculpt_notes/ops.py b/atelier/tools/sculpt_notes/ops.py new file mode 100644 index 0000000..a20b6f8 --- /dev/null +++ b/atelier/tools/sculpt_notes/ops.py @@ -0,0 +1,453 @@ +import bpy +from bpy.types import Operator +from ...utils.others import ShowMessageBox +import random +from mathutils import Vector +import numpy as npy +import bmesh + + +class BAS_OT_sculpt_notes_curve_to_mesh(Operator): + bl_idname = "bas.sculpt_notes_curve_to_mesh" + bl_label = "Sculpt Notes: Curve to Mesh" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + def execute(self, context): + act_obj = context.active_object + act_obj_name = act_obj.name + SN = context.window_manager.bas_sculptnotes + obj = SN.curve + if not obj or obj.name not in context.view_layer.objects: + ShowMessageBox("Can convert note to mesh! Curve not found!", "Ops! Something happened!") + SN.isCreated = False + SN.curve = None + SN.curveShape = None + SN.path_object = None + return {'CANCELLED'} + bpy.ops.object.mode_set(mode='OBJECT') + + ########## + + if SN.path_object != None: + objPath = SN.path_object + objPath.select_set(state=True) + context.view_layer.objects.active = objPath + bpy.ops.object.convert(target='MESH') + for mod in objPath.modifiers: + bpy.ops.object.modifier_apply(modifier=mod.name) + + # selecciono la curve path para convertirla (siempre después de aplicar los modificadores dependientes de esta) + objPath.select_set(state=False) + obj.select_set(state=True) + context.view_layer.objects.active = obj + bpy.ops.object.convert(target='MESH') + + # volvemos a seleccionar el obj path + objPath.select_set(state=True) + + bpy.ops.object.join() # join, y el master será el objeto de curve path + + if SN.mirror: + mirror = obj.modifiers.new(name="_Mirror", type='MIRROR') + mirror.mirror_object = act_obj + mirror.use_mirror_vertex_groups = False + mirror.use_clip = True + if SN.remeshIt: + if SN.mirror: + bpy.ops.object.modifier_apply(modifier="_Mirror") + bpy.ops.object.mode_set(mode='SCULPT') + if SN.reproject: + bpy.ops.bas.voxel_remesh_reproject() + else: + bpy.ops.object.voxel_remesh() + + bpy.ops.object.mode_set(mode='OBJECT') + + else: + #bpy.ops.object.select_all(action='DESELECT') + if bpy.data.curves[obj.name].bevel_object == None: + closeGaps = True + else: + closeGaps = False + obj.select_set(state=True) + context.view_layer.objects.active = obj + bpy.ops.object.convert(target='MESH') + + if closeGaps: + bpy.ops.object.mode_set(mode='SCULPT') + bpy.ops.bas.close_gaps() + bpy.ops.object.mode_set(mode='OBJECT') + if SN.mirror: + mirror = obj.modifiers.new(name="_Mirror", type='MIRROR') + mirror.mirror_object = act_obj + mirror.use_mirror_vertex_groups = False + mirror.use_clip = True + if SN.remeshIt: + if SN.mirror: + bpy.ops.object.modifier_apply(modifier="_Mirror") + bpy.ops.object.mode_set(mode='SCULPT') + if SN.reproject: + bpy.ops.bas.voxel_remesh_reproject() + else: + bpy.ops.object.voxel_remesh() + bpy.ops.object.mode_set(mode='OBJECT') + + obj.select_set(state=False) + + if not act_obj: + act_obj = bpy.data.objects.get(act_obj_name, None) + if not act_obj: + SN.isCreated = False + SN.curve = None + SN.curveShape = None + SN.path_object = None + return {'FINISHED'} + act_obj.select_set(state=True) + context.view_layer.objects.active = act_obj + bpy.ops.object.mode_set(mode='SCULPT') + + SN.isCreated = False + + #obj.mode = 'SCULPT' # TEST + + SN.curve = None + SN.curveShape = None + SN.path_object = None + return {'FINISHED'} + +class BAS_OT_sculpt_notes_Convert(Operator): + bl_idname = "bas.sculpt_notes_convert_to_3d" + bl_label = "Convert Notes to 3D" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + obj = context.active_object + SN = context.window_manager.bas_sculptnotes + # USING ANNOTATIONS # CONVERTING ANNOTATIONS TO GP IS NOT WORKING PROPERLY NOW / NO SUPPORT, PROBABLY IN A FUTURE? + if SN.use == 'NOTES': + #notes = bpy.data.grease_pencils['Annotations'] + # GET NOTES DATA + noteData = context.scene.grease_pencil + # CHECK IF THERE ARE STROKES + try: + if len(noteData.layers.active.active_frame.strokes) == 0: # if len(noteData.layers.active.active_frame.strokes) == 0: + ShowMessageBox("No annotation strokes! Make some strokes before!", "Can't do this", 'ERROR') + return {'FINISHED'} + except: + ShowMessageBox("No annotation strokes! Make some strokes before!", "Can't do this", 'ERROR') + return {'FINISHED'} + # QUICK CHANGE TO OBJECT + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + # GET NOTES LAYER # access with name '' or with index + noteLayer = noteData.layers.active # noteData.layers[0] + # GET NOTES FRAME # access with index # opacity, thickness, select... # add/remove frame + noteFrame = noteLayer.active_frame # noteLayer.frames[0] + # GET NOTES STROKES # acess with index # display-mode, line-width, material index... # add/remove stroke + noteStrokes = noteFrame.strokes + numStrokes = len(noteStrokes) + # GET NOTES POINTS # access with index # location, pressure, strength, uv-rotation... ('foreach_get()') # add/remove point + # notePoints[numStrokes][] + noteObjs = [] + note_id = str(random.randint(0,500) + random.randint(0,500)) + + if SN.mergeStrokes: + # Default + vertices = [] + edges = [] + faces = [] + face = () + oldPoint = None + + meshName = "SculptNotes_" + note_id + "_MergedStroke" + mesh = bpy.data.meshes.new(meshName) # create a new mesh + ob = bpy.data.objects.new(meshName, mesh) # create an object with that mesh + ob.location = Vector((0,0,0)) #by.context.scene.cursor_location # position object at 3d-cursor + # Link light object to the active collection of current view layer, + # so that it'll appear in the current scene. + context.view_layer.active_layer_collection.collection.objects.link(ob) + p = 0 + for n in range(0, numStrokes): + # FOR EACH POINT IN THE ACTUAL STROKE + strPoints = noteStrokes[n].points + #numPoints = len(strPoints) + #x = 0 + for point in strPoints: + # first stroke and first point + if n == 0 and p == 0: + vertices.append(point.co) + face = face + (p,) + primVert = p + primPoint = point + oldPoint = point + p += 1 # Increment for next point + else: + a = npy.array((point.co[0] ,point.co[1], point.co[2])) + b = npy.array((oldPoint.co[0], oldPoint.co[1], oldPoint.co[2])) + d = npy.linalg.norm(a-b) + if d > SN.mergeDistThreshold: + vertices.append(point.co) + face = face + (p,) + edges.append((p-1, p)) + p += 1 # Increment for next point + #x += 1 + oldPoint = point + # UPDATE AND VALIDATE NEW MESH + #face = face + (primVert,) + #edges.append((p-1, 0)) + edges.append((p-1, 0)) + faces.append(face) + mesh.from_pydata(vertices,edges,faces) # [] (empty) # Fill the mesh with verts, edges, faces + mesh.validate(verbose=True) # clean_customdata=True + noteObjs.append(ob) + mesh.remesh_voxel_size = 0.01 + # BMESH # no es estrictamente necesario + ''' + bm = bmesh.new() + bm.from_mesh(mesh) + # bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001) # ya no con los pases de comp con numpy + bmesh.ops.recalc_face_normals(bm, faces=bm.faces) + bm.to_mesh(mesh) + mesh.update() # Update mesh with new data # calc_edges=True + ''' + # NO MERGE + else: + # FOR EACH STROKE + for n in range(0, numStrokes): + # Default + vertices = [] + edges = [] + faces = [] + face = () + oldPoint = None + # Create next mesh for next stroke + meshName = "SculptNotes_" + note_id + "_Stroke_" + str(n) + mesh = bpy.data.meshes.new(meshName) # create a new mesh + ob = bpy.data.objects.new(meshName, mesh) # create an object with that mesh + ob.location = Vector((0,0,0)) #by.context.scene.cursor_location # position object at 3d-cursor + # Link light object to the active collection of current view layer, + # so that it'll appear in the current scene. + context.view_layer.active_layer_collection.collection.objects.link(ob) + # FOR EACH POINT IN THE ACTUAL STROKE + strPoints = noteStrokes[n].points + numPoints = len(strPoints) + p = 0 # RESET FOR NEXT STROKE + for point in strPoints: + # notePoints[numStrokes][p] = point + if p == 0 or p == numPoints: + if p == 0: + vertices.append(point.co) + face = face + (p,) + primVert = p + oldPoint = point + else: + # para cerrar el loop si el primer y ultimo están muy cerca + a = npy.array((point.co[0] ,point.co[1], point.co[2])) + b = npy.array((primVert.co[0], primVert.co[1], primVert.co[2])) + d = npy.linalg.norm(a-b) + if d > SN.mergeDistThreshold: + vertices.append(point.co) + edges.append((p-1, 0)) # movido de abajo, sólo se crea con el ultimo si estan cerca + face = face + (p) + else: + edges.append((p-2, 0)) # sino se obvia el ultimo vertice y se une con el anterior + p += 1 # Increment for next point + else: + a = npy.array((point.co[0] ,point.co[1], point.co[2])) + b = npy.array((oldPoint.co[0], oldPoint.co[1], oldPoint.co[2])) + d = npy.linalg.norm(a-b) + if d > SN.mergeDistThreshold: + vertices.append(point.co) + face = face + (p,) + edges.append((p-1, p)) + p += 1 # Increment for next point + oldPoint = point + # UPDATE AND VALIDATE NEW MESH + #face = face + (primVert,) + #edges.append((p-1, 0)) + faces.append(face) + mesh.from_pydata(vertices,edges,faces) # [] (empty) # Fill the mesh with verts, edges, faces + mesh.validate(verbose=False) # clean_customdata=True + noteObjs.append(ob) + mesh.remesh_voxel_size = 0.01 + # BMESH # no es estrictamente necesario + + bm = bmesh.new() + bm.from_mesh(mesh) + # bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001) # ya no con los pases de comp con numpy + bmesh.ops.recalc_face_normals(bm, faces=bm.faces) + bm.to_mesh(mesh) + mesh.update() # Update mesh with new data # calc_edges=True + + SN.isCreated = True + + if SN.joinStrokes or SN.mergeStrokes: + if len(noteObjs) > 1: + for o in noteObjs: + o.select_set(state=True) + context.view_layer.objects.active = noteObjs[0] + bpy.ops.object.join() + noteObjs = [] + noteObjs.append(context.active_object) + if SN.method_type == 'FLAT': + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(state=True) + context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='SCULPT') + SN.isCreated = False + return {'FINISHED'} + else: + SN.applyModifiersDirectly = True + + SN.gp = noteObjs[0] + print("NOTE OBJECTS", noteObjs) + + obj.select_set(state=False) + for o in noteObjs: + o.select_set(state=True) + context.view_layer.objects.active = o + if not SN.ngon: + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') + if SN.reproject: + bpy.ops.mesh.subdivide(number_cuts=2, ngon=False) + #bpy.ops.mesh.tris_convert_to_quads() + bpy.ops.object.mode_set(mode='OBJECT') + sw = o.modifiers.new(name="SW", type='SHRINKWRAP') + sw.wrap_method = 'NEAREST_SURFACEPOINT' # PROJECT # NEAREST_VERTEX # TARGET_PROJECT # NEAREST_SURFACEPOINT + sw.wrap_mode = 'ABOVE_SURFACE' # ABOVE_SURFACE # ON_SURFACE + sw.target = obj + bpy.ops.object.modifier_apply(modifier="SW") + else: + bpy.ops.mesh.tris_convert_to_quads() + bpy.ops.object.mode_set(mode='OBJECT') + + # *1 MOVED + if SN.reproject and not SN.ngon: + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.tris_convert_to_quads() + bpy.ops.mesh.decimate(ratio=0.25) + bpy.ops.mesh.tris_convert_to_quads() + if SN.mergeDistThreshold < 0.09 and SN.mergeDistThreshold > 0.001: + # ESTO NO HACE FALTA YA, SÓLO SI ERA DESPUÉS DEL SOLIDIFY HACIA MEJOR RESULTADO CON DIFERENCIA + #bpy.ops.mesh.dissolve_limited(angle_limit=0.122173, use_dissolve_boundaries=True, delimit={'NORMAL'}) + bpy.ops.mesh.decimate(ratio=0.1+SN.mergeDistThreshold*10) + bpy.ops.mesh.tris_convert_to_quads() + bpy.ops.object.mode_set(mode='OBJECT') + + if SN.method_type == 'SOLID': + solidi = o.modifiers.new(name="Solid", type='SOLIDIFY') + solidi.thickness = SN.thickness + solidi.offset = 1 # add later # 1 si activase la parte de bmesh que arregla las normales + solidi.thickness_clamp = 0 + solidi.use_rim = True + solidi.use_rim_only = False + if SN.applyModifiersDirectly: + bpy.ops.object.modifier_apply(modifier="Solid") + + # *1 # ANTES AQUÍ PERO LOS BLOQUES FLOTABAN SI ERAN PEQUES + + if SN.smooth: + co_smooth = o.modifiers.new(name="Co_Smooth", type='CORRECTIVE_SMOOTH') + co_smooth.iterations = SN.smoothPasses + co_smooth.smooth_type = 'LENGTH_WEIGHTED' + co_smooth.use_only_smooth = True + if SN.applyModifiersDirectly: + bpy.ops.object.modifier_apply(modifier="Co_Smooth") + o.data.remesh_smooth_normals = True + + if SN.mirror: + mirror = o.modifiers.new(name="_Mirror", type='MIRROR') + mirror.mirror_object = obj + mirror.use_mirror_vertex_groups = False + mirror.use_clip = True + if SN.applyModifiersDirectly: + bpy.ops.object.modifier_apply(modifier="_Mirror") + + if SN.applyModifiersDirectly: + SN.isCreated = False # resetear, ya se aplicó todo + if SN.remeshIt: + bpy.ops.object.mode_set(mode='SCULPT') + if SN.reproject: + bpy.ops.bas.voxel_remesh_reproject() + else: + bpy.ops.object.voxel_remesh() + + # USING GP + elif SN.use == 'GP': + #actLayer = bpy.context.object.data.layers.active_note + #gp_obj = bpy.context.object + + + # SIMPLIFY DRAW + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.gpencil.stroke_simplify(factor=0.1) + + # CONVERT TO CURVE + bpy.ops.gpencil.convert(type='POLY', use_normalize_weights=True, radius_multiplier=1.0, use_link_strokes=False) + + # CONVERT TO MESH + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(state=True) + context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='SCULPT') + + if SN.autoClear: + bpy.ops.bas.clear_note() + + return {'FINISHED'} + +class BAS_OT_sculpt_notes_apply_modifiers(Operator): + bl_idname = "bas.sculpt_notes_apply_modifiers" + bl_label = "Sculpt Notes: Apply Modifiers" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + act_obj = context.active_object + SN = context.window_manager.bas_sculptnotes + obj = SN.gp + + if not obj: + ShowMessageBox("No recent SculptNote mesh was found", "Can't do this!" 'INFO') + SN.isCreated = False + SN.curve = None + SN.curveShape = None + SN.path_object = None + return {'CANCELLED'} + + try: + bpy.ops.object.mode_set(mode='OBJECT') + #bpy.ops.object.select_all(action='DESELECT') + obj.select_set(state=True) + context.view_layer.objects.active = obj + if obj != None: + for mod in obj.modifiers: + bpy.ops.object.modifier_apply(modifier=mod.name) + except: + ShowMessageBox("No recent SculptNote mesh was found", "Can't do this!" 'INFO') + pass + if SN.remeshIt: + bpy.ops.object.mode_set(mode='SCULPT') + if SN.reproject: + bpy.ops.bas.voxel_remesh_reproject() + + else: + bpy.ops.object.voxel_remesh() + bpy.ops.object.mode_set(mode='OBJECT') + obj.select_set(state=False) + act_obj.select_set(state=True) + context.view_layer.objects.active = act_obj + bpy.ops.object.mode_set(mode='SCULPT') + + SN.isCreated = False + SN.gp = None + return {'FINISHED'} + +classes = ( + BAS_OT_sculpt_notes_curve_to_mesh, + BAS_OT_sculpt_notes_Convert, + BAS_OT_sculpt_notes_apply_modifiers +) \ No newline at end of file diff --git a/atelier/tools/sculpt_notes/ops_curves.py b/atelier/tools/sculpt_notes/ops_curves.py new file mode 100644 index 0000000..cf827a9 --- /dev/null +++ b/atelier/tools/sculpt_notes/ops_curves.py @@ -0,0 +1,965 @@ +from .ops import ( + npy, Vector, Operator, bpy, random, ShowMessageBox, bmesh +) +import blf + + +########## CURVES #################### + + +#curvePointsX = [] +#curvePointsY = [] +# SAVE POINT COORDS TO LIST +#curvePointsX.append(v[0]) +#curvePointsY.append(v[1]) + +distThreshold = 55 +prev = Vector((0, 0, 0)) +def editableCurve_draw_callback_px(self, context): + try: + if not bpy.context.space_data.overlay.show_overlays or not self.spline: + return + except: + return + #fun.Draw_Button_2D_Rectangle(context, event, None, Vector((200, 200)), 500, 500, "Hola Mundo", 24) + #fun.Draw_2D_Button(self.button_1) + #fun.Draw_2D_Button(self.button_2) + n = 0 + lastPointCo = None + penultPointCo = None + minDist = 2000 + numPoints = len(self.spline.bezier_points) + #self.nearPoint = -1 + #self.nearCoord = Vector((0, 0)) + #if self.spline != None: + if numPoints > 0: + self.snapping = False + #k = numPoints - 1 + for point in self.spline.bezier_points: + # OBTENEMOS LAS COORDENADAS EN PANTALLA + v = fun.Convert_3D_spaceCoords_to_2D_screenCoords(context, point.co) + if v != None: + if not self.scaling and not self.tilting: + # CALC DIST FROM MOUSE TO EVERY SINGLE POINT + dist = fun.DistanceBetween(v, self.mousePos) # ya es abs ! + if (dist < minDist): + # SAVE SNAP POINT + if self.canSnap: # Si puede snapear a otros puntos + if self.nearBPoint != point: # Comprobar si no es el punto ya activo + if abs(dist) < self.snapThreshold: # Si está dentro del rango + self.snapping = True + self.nearestSnapPoint = point.co # guardar referencia coordenadas + #else: + # self.snapping = False + minDist = dist + # NOT IF IT'S DRAGGING BUT YES FOR SNAP + if not self.dragging: # Keep active point if you are dragging + # UPDATE NEAREST POINT + self.nearPoint = n + self.nearCoord = Vector((v[0], v[1])) + self.nearBPoint = point + + # DIBUJAMOS CADA LINEA + if n != 0: + fun.Draw_2D_Line(prev, v, (.7, .1, .2, .5)) + #if n == k: + # lastPointCo = v + + # DIBUJAMOS CADA PUNTO + fun.Draw_Text(v[0]-6, v[1]-8, "•", 24, 0, .6, .8, 1, .8) + prev = v + n+=1 + + # DIBUJAMOS EXTRUDE POINT + if self.extruding: # OBVIAR # realizar extrude y cerrar + #if lastPointCo != None: + lastPointCo = fun.Convert_3D_spaceCoords_to_2D_screenCoords(context, self.spline.bezier_points[numPoints-1].co) + #CALCULAR PUNTO # YA NO HACE FALTA EL CALCULO QUE HACIA, CAMBIO DE FORMA + fun.Draw_2D_Line(lastPointCo, self.mousePos, (.4, .8, .5, .5)) + #fun.Draw_2D_Point(self.extrudePoint, (.2, .75, .35, .6)) + fun.Draw_Text(self.mousePos[0]-20, self.mousePos[1]-24, "•", 70, 0, .2, .75, .35, .8) + fun.Draw_Text(self.mousePos[0]-9, self.mousePos[1]-6, "+", 24, 0, 1, 1, 1, .8) + + fun.Draw_Text(lastPointCo[0]-9, lastPointCo[1]-13, "•", 36, 0, 1, .6, .8, .8) + fun.Draw_2D_Circle(lastPointCo, 10, 16, (1, 1, 0, .8)) + self.active = True + self.nearPoint = numPoints - 1 + + fun.Draw_Text(200, 150, "Left Click to confirm extrude", 18, 0, 1, 0.75, 0.5, 1) + # MARCAMOS PUNTO ACTIVO + elif self.scaling or self.tilting: + # DIBUJAMOS PUNTO + fun.Draw_Text(self.nearCoord[0]-9, self.nearCoord[1]-13, "•", 36, 0, 1, .6, .8, .8) + fun.Draw_2D_Circle(self.nearCoord, 10, 8, (1, 1, 0, .8)) + # SCALING + if self.scaling: + rad = str(self.nearBPoint.radius)[0:4] + fun.Draw_Text(self.nearCoord[0]+20, self.nearCoord[1]-8, rad, 24, 0, 1, 1, 1, .8) + # TILTING + if self.tilting: + til = str(math.degrees(self.nearBPoint.tilt))[0:4] + fun.Draw_Text(self.nearCoord[0]+20, self.nearCoord[1]-8, til, 24, 0, 1, 1, 1, .8) + fun.Draw_Text(200, 150, "Left Click to confirm extrude", 18, 0, 1, 0.75, 0.5, 1) + elif (self.nearPoint != -1): + if (minDist < distThreshold): + self.active = True + if self.showHandlers: + self.hL = fun.Convert_3D_spaceCoords_to_2D_screenCoords(context, self.nearBPoint.handle_left) + self.hR = fun.Convert_3D_spaceCoords_to_2D_screenCoords(context, self.nearBPoint.handle_right) + # DIBUJAMOS LINEAS A HANDLERS + fun.Draw_2D_Line(self.hL, self.nearCoord, (.8, .8, .8, .5)) + fun.Draw_2D_Line(self.hR, self.nearCoord, (.8, .8, .8, .5)) + # DIBUJAMOS PUNTOS HANDLERS + fun.Draw_Text(self.hL[0]-6, self.hL[1]-8, "•", 24, 0, 1, 1, 0, .8) + fun.Draw_Text(self.hR[0]-6, self.hR[1]-8, "•", 24, 0, 1, 1, 0, .8) + # DIBUJAMOS PUNTO + if self.dragging: + if self.snapping: + snapPoint2D = fun.Convert_3D_spaceCoords_to_2D_screenCoords(context, self.nearestSnapPoint) + fun.Draw_Text(snapPoint2D[0]-9, snapPoint2D[1]-13, "•", 36, 0, 1, .6, .8, .8) + fun.Draw_2D_Circle(snapPoint2D, 10, 16, (1, 1, 0, .8)) + else: + fun.Draw_Text(self.mousePos[0]-9, self.mousePos[1]-13, "•", 36, 0, 1, .6, .8, .8) + fun.Draw_2D_Circle(self.mousePos, 10, 16, (1, 1, 0, .8)) + else: + fun.Draw_Text(self.nearCoord[0]-9, self.nearCoord[1]-13, "•", 36, 0, 1, .6, .8, .8) + fun.Draw_2D_Circle(self.nearCoord, 10, 16, (1, 1, 0, .8)) + else: + self.active = False + + # DRAW3 INFO + fun.Draw_Text(200, 100, "Ctrl: Move Point | Shift: Extrude | Alt: Show handlers", 18) + fun.Draw_Text(200, 50, "Ctrl + Shift: Snap | S: Scale | D: Rotate", 18) + +from bpy_extras import object_utils +class BAS_OT_Convert_Curves(Operator): + bl_idname = "bas.sculpt_notes_convert_to_curve" + bl_label = "Convert Notes to Curves" + bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} + + def modal(self, context, event): + if not context or context == None: + self.old_context.space_data.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + args = (self, context) + self._handle = bpy.types.SpaceView3D.draw_handler_add(editableCurve_draw_callback_px, args, 'WINDOW', 'POST_PIXEL') + self.old_context = context + #fun.AlignObjectToView(context.active_object) + #if event.type in {'J'}: + if not hasattr(self, '_handle'): + args = (self, context) + self._handle = bpy.types.SpaceView3D.draw_handler_add(editableCurve_draw_callback_px, args, 'WINDOW', 'POST_PIXEL') + if self.spline == None: + try: + self.spline = bpy.data.curves[context.scene.curve.name].splines[0] + except: + context.space_data.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + return {'CANCELLED'} + self.mousePos = Vector((event.mouse_region_x, event.mouse_region_y)) + #fun.Button_Events(event, self.button_1, self.mousePos, bpy.ops.object.voxel_remesh) # mousePos is optional, func will take it from event if it's none, but more convenient like this if you have multiple buttons + #fun.Button_Events(event, self.button_2, self.mousePos, None) + if event.type in {'C'} and event.value in {'PRESS'}: + fun.Curve_CloseSpline(self.spline) + if self.scaling: + if event.type in {'ESC', 'RIGHTMOUSE', 'LEFTMOUSE'}: + self.scaling = False + self.scalingPoint = -1 + dist = fun.DistanceBetween(self.mousePos, self.startMousePos) + #if dist > scaleThreshold: + if self.mousePos[0] < self.startMousePos[0]: # MOVE LEFT + self.nearBPoint.radius = -dist / 100 + else: # MOVE RIGHT + self.nearBPoint.radius = dist / 100 + return {'PASS_THROUGH'} + elif self.tilting: + if event.type in {'ESC', 'RIGHTMOUSE', 'LEFTMOUSE'}: + self.tilting = False + self.nearPoint = -1 + dist = fun.DistanceBetween(self.mousePos, self.startMousePos) + #if dist > scaleThreshold: + if self.mousePos[0] < self.startMousePos[0]: # MOVE LEFT + self.nearBPoint.tilt = -dist / 100 + else: # MOVE RIGHT + self.nearBPoint.tilt = dist / 100 + return {'PASS_THROUGH'} + elif not self.dragging and event.shift and not self.extruding: + self.extruding = True + elif self.extruding: + if event.type in {'LEFTMOUSE'} and event.value in {'PRESS'}: + self.extruding = False + hit, newCo, normal, index, obj, matrix = fun.RaycastHit_2D_to_3D(context, self.mousePos) + if not hit: + newCo = fun.Convert_2D_screenCoords_to_3D_spaceCoords(context, self.mousePos) + #newPointCo = penultPoint + self.spline.bezier_points.add(1) + #self.spline = bpy.data.curves[context.scene.curve.name].splines[0] + newLast = self.spline.bezier_points[len(self.spline.bezier_points) - 1] + newLast.handle_left_type = self.handleType + newLast.handle_right_type = self.handleType + newLast.co = newCo + self.extrudePoint = Vector((0, 0)) + self.extruding = False + elif event.type in {'ESC', 'RIGHTMOUSE', 'MIDDLEMOUSE'}: + self.extruding = False + #return {'PASS_THROUGH'} + elif self.nearPoint != -1 and self.active: # self.nearPoint != -1 + if event.ctrl: + self.dragging = True + #if not self.extruding and fun.DistanceBetween(self.mousePos, self.extrudePoint) < self.extrudeThreshold: + # self.extruding = True + if event.shift: + self.canSnap = True + if self.snapping: + if self.nearestSnapPoint != None: + self.nearBPoint.co = self.nearestSnapPoint + else: + # CON ESTO NOOO FUNCIONA EL SNAP CON PUNTOS ANTERIORES.... PORQUE DIABLOS? PERO NO HAY AUTO-SNAP-EXIT + hit, pos, normal, index, obj, matrix = fun.RaycastHit_2D_to_3D(context, self.mousePos) + if hit: + self.nearBPoint.co = pos + else: + self.canSnap = False + #self.snapping = False + hit, pos, normal, index, obj, matrix = fun.RaycastHit_2D_to_3D(context, self.mousePos) + if hit: + self.nearBPoint.co = pos + else: + self.nearBPoint.co = fun.Convert_2D_screenCoords_to_3D_spaceCoords(context, self.mousePos) + elif event.alt: + self.showHandlers = True + hit, pos, normal, index, obj, matrix = fun.RaycastHit_2D_to_3D(context, self.mousePos) + if hit: + if fun.DistanceBetween(self.mousePos, self.hL) < fun.DistanceBetween(self.mousePos, self.hR): + self.nearBPoint.handle_left = pos + else: + self.nearBPoint.handle_right = pos + elif event.type in {'S'}: + self.scalingPoint = self.nearPoint + self.scaling = True # Activate scaling (Radius) + self.startMousePos = Vector((event.mouse_region_x, event.mouse_region_y)) + self.baseRadius = self.nearBPoint.radius + elif event.type in {'D'}: + self.tiltingPoint = self.nearPoint + self.tilting = True # Activate scaling (Radius) + self.startMousePos = Vector((event.mouse_region_x, event.mouse_region_y)) + self.baseTilt = self.nearBPoint.tilt + else: + self.dragging = False + self.showHandlers = False + else: + self.scaling = False + self.tilting = False + self.extruding = False + if SN.curve == None or (event.type in {'ESC', 'RIGHTMOUSE'} and event.value in {'CLICK'} and not self.extruding) or context.mode != 'SCULPT' or context.active_object == None or self.spline == None: + try: + context.space_data.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + except: + self.old_context.space_data.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + return {'CANCELLED'} + return {'FINISHED'} + try: + context.area.tag_redraw() + except: + + pass + return {'PASS_THROUGH'} + ''' + def cancel(self, context): + SN.curve = None + SN.curveShape = None + SN.isCreated = False + ''' + def modal_setup(self, context): + self.old_context = context + #self.button_1 = fun.CreateButton_Rectangle(Vector((600, 50)), 300, 100, "Remesh", 24) + #self.button_2 = fun.CreateButton_Circle(Vector((300, 400)), 100, 32, "Hola!", 24) + #self.button_1 = False + self.active = False + self.handleType = 'AUTO' + self.nearPoint = -1 + self.nearBPoint = None + self.nearestSnapPoint = None + self.extrudePoint = Vector((0, 0)) + self.showHandlers = False + self.scaling = False + self.tilting = False + self.dragging = False + self.snapping = False + self.extruding = False + self.hL = Vector((0, 0, 0)) + self.hR = Vector((0, 0, 0)) + self.baseRadius = 1 + self.scaleFactor = 0.1 + self.snapThreshold = 38 + self.extrudeThreshold = 40 + self.canSnap = False + self.spline = None + areas = {a.type:a for a in context.screen.areas} + area = areas.get("VIEW_3D",None) + self.view = area.spaces.active.region_3d.view_matrix + + def execute(self, context): + obj = context.active_object + SN = context.window_manager.bas_sculptnotes + # USING ANNOTATIONS # CONVERTING ANNOTATIONS TO GP IS NOT WORKING PROPERLY NOW / NO SUPPORT, PROBABLY IN A FUTURE? + if SN.use == 'NOTES': + #notes = bpy.data.grease_pencils['Annotations'] + # GET NOTES DATA + noteData = context.scene.grease_pencil + # CHECK IF THERE ARE STROKES + try: + if len(noteData.layers.active.active_frame.strokes) == 0: # if len(noteData.layers.active.active_frame.strokes) == 0: + ShowMessageBox("No annotation strokes! Make some strokes before!", "Can't do this", 'ERROR') + return {'FINISHED'} + except: + ShowMessageBox("No annotation strokes! Make some strokes before!", "Can't do this", 'ERROR') + return {'FINISHED'} + # QUICK CHANGE TO OBJECT + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + # GET NOTES LAYER # access with name '' or with index + noteLayer = noteData.layers.active # noteData.layers[0] + # GET NOTES FRAME # access with index # opacity, thickness, select... # add/remove frame + noteFrame = noteLayer.active_frame # noteLayer.frames[0] + # GET NOTES STROKES # acess with index # display-mode, line-width, material index... # add/remove stroke + noteStrokes = noteFrame.strokes + numStrokes = len(noteStrokes) + # Si es tipo wrap se le quita el último trazo el cual se reserva para darle forma al wrapper + if SN.method == 3: # WRAP + numStrokes -= 1 + + # GET NOTES POINTS # access with index # location, pressure, strength, uv-rotation... ('foreach_get()') # add/remove point + # notePoints[numStrokes][] + noteObjs = [] + note_id = str(random.randint(0,500) + random.randint(0,500)) + # FOR EACH STROKE + # Default + vertices = [] + edges = [] + faces = [] + #face = () + oldPoint = None + + meshName = "" + note_id + "_CurveStroke" + mesh = bpy.data.meshes.new(meshName) # create a new mesh + ob = bpy.data.objects.new(meshName, mesh) # create an object with that mesh + ob.location = Vector((0,0,0)) #by.context.scene.cursor_location # position object at 3d-cursor + # Link light object to the active collection of current view layer, + # so that it'll appear in the current scene. + context.view_layer.active_layer_collection.collection.objects.link(ob) + + p = 0 + for n in range(0, numStrokes): + # FOR EACH POINT IN THE ACTUAL STROKE + strPoints = noteStrokes[n].points + numPoints = len(strPoints) + x = 0 + for point in strPoints: + # first stroke and first point + if n == 0 and p == 0: + vertices.append(point.co) + #face = face + (p,) + #primVert = p + #primPoint = point + oldPoint = point + p += 1 # Increment for next point + x += 1 + # last stroke and last point + elif n == numStrokes-1 and p == numPoints-1: + vertices.append(point.co) + #face = face + (p) + lastVert = p + primPoint = point + oldPoint = point + edges.append((p-1, p)) + p += 1 # Increment for next point + x += 1 + else: + a = npy.array((point.co[0] ,point.co[1], point.co[2])) + b = npy.array((oldPoint.co[0], oldPoint.co[1], oldPoint.co[2])) + d = npy.linalg.norm(a-b) + if d > SN.mergeDistThreshold: + vertices.append(point.co) + #face = face + (p,) + edges.append((p-1, p)) + p += 1 # Increment for next point + x += 1 + oldPoint = point + + # UPDATE AND VALIDATE NEW MESH + #faces.append(face) + if SN.curve_isCyclic: + edges.append((p-1, 0)) + mesh.from_pydata(vertices,edges,[]) # [] (empty) # Fill the mesh with verts, edges, faces + mesh.validate(verbose=False) # clean_customdata=True + #mesh.remesh_voxel_size = 0.01 + + ob.select_set(state=True) + context.view_layer.objects.active = ob + bpy.ops.object.convert(target='CURVE') + SN.curve = context.active_object + + # BEZIER CURVA + SIMPLIFY + bpy.ops.object.mode_set(mode='EDIT') + try: + bpy.ops.curve.select_all(action='SELECT') + except: + if SN.method == 3: + ShowMessageBox("For Wrap mode you have to create the shape (last stroke) you want to use to 'wrap' the path (first strokeS)!", "Please Create a second stroke", 'ERROR') + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(state=True) + context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='SCULPT') + return {'FINISHED'} + + bpy.ops.curve.spline_type_set(type='BEZIER') # SPLINE TYPE + + + #curveShape = bpy.data.curves[SN.curve.name] + #spline = curveShape.splines[0] + if SN.curve_simplify: + curve = bpy.data.curves[SN.curve.name] + spline = curve.splines[0] + numPoints = len(spline.bezier_points) + #print(numPoints) + if numPoints > 180: + bpy.ops.curve.decimate(ratio=0.1) + elif numPoints > 105: + bpy.ops.curve.decimate(ratio=0.15) + elif numPoints > 55: + bpy.ops.curve.decimate(ratio=0.25) + elif numPoints > 25: + bpy.ops.curve.decimate(ratio=0.4) + elif numPoints > 12: + bpy.ops.curve.decimate(ratio=0.55) + else: + bpy.ops.curve.decimate(ratio=0.75) + pass + + #bpy.ops.object.mode_set(mode='OBJECT') + #bpy.ops.object.mode_set(mode='EDIT') # UPDATE CURVE DATA + + #curve = bpy.data.curves[context.scene.curve.name] + #self.spline = curve.splines[0] + #self.points = self.spline.bezier_points + + #for point in curve.splines[0].bezier_points: + # point.handle_left_type = 'AUTO' + # point.handle_right_type = 'AUTO' + + bpy.ops.curve.handle_type_set(type='AUTOMATIC') # HANDLE TYPE # ALIGNED, AUTOMATIC, VECTOR + + if SN.curve_useCurveMapForSplinePointsRadius: + #if not SN.curve_simplify: + #curve = bpy.data.curves[SN.curve.name] + spline = curve.splines[0] + + numPoints = len(spline.bezier_points) + + #curveNode = CurveData('CurveData') + #curveMap = curveNode.mapping.curves[0] + + + #mapping = curveNode.mapping + #mapping.initialize() + + #ng = bpy.data.node_groups['NodeGroup'] + #curve = ng.nodes['CurveData'].mapping.curves[0] + #y = c.evaluate(x) + + node = bpy.data.node_groups['NodeGroup'].nodes['CurveData'] + node.mapping.update() + curveMap = node.mapping.curves[3] + + ''' + for point in curveMap.points: + print(point.location) + + if SN.method == 3: + mul = SN.radiusMultiplier + else: + mul = 1 + ''' + n = 0 + for point in spline.bezier_points: + x = n/numPoints + point.radius = curveMap.evaluate(x)# * mul + n += 1 + + #print(x) + #print(curveMap.evaluate(x)) + + bpy.ops.object.mode_set(mode='OBJECT') + # + + ob.select_set(state=False) + + SN.curveShape_pivot_mode = 'CENTER' + SN.curveShape_pivot_index = 0 + SN.curveShape_numNodes = 0 + + if SN.method == 3: # 3: WRAP + stroke = noteFrame.strokes[len(noteStrokes)-1] + # Default + vertices = [] + edges = [] + faces = [] + face = () + oldPoint = None + # Create next mesh for next stroke + meshName = "" + note_id + "_CurveShape" + mesh = bpy.data.meshes.new(meshName) # create a new mesh + ob = bpy.data.objects.new(meshName, mesh) # create an object with that mesh + ob.location = Vector((0,0,0)) #by.context.scene.cursor_location # position object at 3d-cursor + # Link light object to the active collection of current view layer, + # so that it'll appear in the current scene. + context.view_layer.active_layer_collection.collection.objects.link(ob) + # FOR EACH POINT IN THE ACTUAL STROKE + strPoints = stroke.points + numPoints = len(strPoints) + p = 0 # RESET FOR NEXT STROKE + for point in strPoints: + # notePoints[numStrokes][p] = point + if p == 0: + vertices.append(point.co) + #face = face + (p,) + primVert = p + oldPoint = point + p += 1 # Increment for next point + else: + a = npy.array((point.co[0] ,point.co[1], point.co[2])) + b = npy.array((oldPoint.co[0], oldPoint.co[1], oldPoint.co[2])) + d = npy.linalg.norm(a-b) + if d > SN.mergeDistThreshold: + vertices.append(point.co) + #face = face + (p,) + edges.append((p-1, p)) + p += 1 # Increment for next point + oldPoint = point + # UPDATE AND VALIDATE NEW MESH + #faces.append(face) + edges.append((p-1, 0)) + mesh.from_pydata(vertices,edges,[]) # [] (empty) # Fill the mesh with verts, edges, faces + mesh.validate(verbose=True) # clean_customdata=True + + ob.select_set(state=True) + context.view_layer.objects.active = ob + + bpy.ops.object.convert(target='CURVE') + + bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN') + + SN.curveShape = context.active_object + + curve = bpy.data.curves[SN.curve.name] + curveShape = bpy.data.curves[SN.curveShape.name] + curve.bevel_object = SN.curveShape + + # BEZIER CURVA + SIMPLIFY + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.curve.select_all(action='SELECT') + bpy.ops.curve.spline_type_set(type='BEZIER') + + #curveShape = bpy.data.curves[SN.curve.name] + #spline = curveShape.splines[0] + + if SN.curve_simplify: + curveShape = bpy.data.curves[SN.curveShape.name] + spline = curveShape.splines[0] + numPoints = len(spline.bezier_points) + if numPoints > 180: + bpy.ops.curve.decimate(ratio=0.1) + elif numPoints > 105: + bpy.ops.curve.decimate(ratio=0.15) + elif numPoints > 55: + bpy.ops.curve.decimate(ratio=0.25) + elif numPoints > 25: + bpy.ops.curve.decimate(ratio=0.45) + elif numPoints > 12: + bpy.ops.curve.decimate(ratio=0.80) + else: + pass + + SN.curveShape_pivot_index = 0 + SN.curveShape_numNodes = len(curveShape.splines[0].bezier_points) - 1 # p - 1 # 0 in count + + bpy.ops.object.mode_set(mode='OBJECT') + # + elif SN.method == 5: # PATH + SetPathWithObject(self, context) + else: + curve.bevel_depth = 1 + + #bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(state=True) + context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='SCULPT') + + SN.isCreated = True + + if SN.autoClear: + bpy.ops.bas.clear_note() + + if SN.curve_postEdit: + self.modal_setup(context) + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + return {'FINISHED'} + + +# PATH + +def SetPathWithObject(self, context): + SN = context.window_manager.bas_sculptnotes + bpy.ops.object.select_all(action='DESELECT') + # Seleccionar la curva y hacer activa + curvePath = SN.curve + curvePath.select_set(state=True) + # y mover el cursor a su origen + bpy.ops.view3d.snap_cursor_to_selected() + curvePath.select_set(state=False) + # Seleccionar el objeto + objPath = SN.path_object + objPath.select_set(state=True) + # aplicar su rot y scale + context.view_layer.objects.active = objPath + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + # Crear una copia? + if SN.path_object_makeCopy: + bpy.ops.object.duplicate_move() + # Ocultar Objeto Original + objPath.hide_set(True) + # Cogemos la copia y Guardar referencia del objeto copia en la scene + SN.path_object = context.active_object + objPath = SN.path_object + # Poner pivote en el centro? + if SN.path_object_pivotToCenter: + bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS') + # bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY') + # bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_VOLUME') + # Mover objeto al 3d cursor + bpy.ops.object.origin_set(type='ORIGIN_CURSOR') + # Crear modificar Array con fit curve -> curvePath && relOffset X + array = objPath.modifiers.new(name="_Array", type='ARRAY') + array.fit_type = 'FIT_CURVE' + array.curve = curvePath + array.use_constant_offset = True + array.use_relative_offset = False + array.constant_offset_displace[0] = 0.1 + array.constant_offset_displace[1] = 0 + array.constant_offset_displace[2] = 0 + # bpy.ops.object.modifier_apply(modifier="_Array") + # Crear modificador Curve / obj -> curvePath / Axis = Z + path = objPath.modifiers.new(name="_Path", type='CURVE') + path.object = curvePath + path.deform_axis = 'POS_X' + # bpy.ops.object.modifier_apply(modifier="_Path") + +# STRIPES +#Calculate curve length +#curve = bpy.data.objects['BezierCurve'] +#curveLength = sum(s.calc_length() for s in curve.evaluated_get(depsgraph).data.splines) + +# CALC CURVE LENGTH, to know how much quads are going to be created +# Use thickness of gp for size of quads and for distance threshold when creating curve from stroke +# convert to curve +# extrude by thickness +# convert to mesh + +def distance(x1,y1,x2,y2): + dist = math.sqrt((x2 - x1)**2 + (y2 - y1)**2) + return dist + +def draw_callback_px(self, context): + + #bgl.glEnable(bgl.GL_BLEND) + SN = context.window_manager.bas_sculptnotes + if self.start: + value = str(self.solid.thickness)[0:4] + start = "Thickness : " + end = " m" + else: + value = str(self.bevel.width)[0:4] + start = "Bevel Width : " + end = " m" + + text = start + value + end + + # ...api_current/bpy.types.Area.html?highlight=bpy.types.area + header_height = context.area.regions[0].height # 26px + npanel_width = context.area.regions[1].width + transorm_panel_width = context.area.regions[3].width + + width = context.area.width - npanel_width - transorm_panel_width + height = context.area.height + header_height + + + # draw text + + font_id = 0 # XXX, need to find out how best to get this. + #bgl.glColor(*(1.0, 1.0, 1.0, 1)) + blf.size(font_id, 32, 72) + blf.color(font_id, 1, 1, 1, 1) + dim_x = blf.dimensions(font_id, text)[0] + dim_y = blf.dimensions(font_id, text)[1] + #blf.position(font_id, width/2-dim_x, height/2-dim_y, 0) + blf.position(font_id, width/4+dim_x, height/6-dim_y, 0) + blf.draw(font_id, text) + + text = "Use CTRL + MouseWheel" + blf.size(font_id, 20, 72) + blf.color(font_id, .8, .7, .4, 1) + blf.position(font_id, width/4+dim_x, height/6+dim_y, 0) + blf.draw(font_id, text) + + if self.start and SN.strips_makeBevel: + text = "LMB -> Confirm and Edit Bevel / RMB -> Confirm and Finish" + else: + text = "LMB/RMB -> Confirm and Finish" + blf.size(font_id, 16, 72) + blf.color(font_id, .5, .9, .6, 1) + blf.position(font_id, width/4+dim_x, height/6-dim_y*3, 0) + blf.draw(font_id, text) + # restore opengl defaults + #bgl.glLineWidth(1) + #bgl.glDisable(bgl.GL_BLEND) + #bgl.glColor(0.0, 0.0, 0.0, 1.0) + +class BAS_OT_Convert_Strips(Operator): + bl_idname = "bas.sculpt_notes_convert_to_strip" + bl_label = "Convert Notes to Strips" + bl_options = {'REGISTER', 'UNDO'} + + def invoke(self, context, event): + # SETUP + self.start = True + self.end = False + self.sensibility = 30 + self.thickness = 0.01 + self.width = 0.01 + self.lastX = event.mouse_region_x + self.lastY = event.mouse_region_y + self.noteData = context.scene.grease_pencil + if not self.stroke2Mesh(context): + return {'FINISHED'} + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + def modal(self, context, event): + if self.end or event.type in {'RIGHTMOUSE', 'ESC'}: + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + del self._handle + if self.start: + bpy.ops.object.modifier_apply(modifier="Solid") + else: + bpy.ops.object.modifier_apply(modifier="Solid") + bpy.ops.object.modifier_apply(modifier="Bevel") + self.returnToSculpt(context, self.sculptObj) + return {'FINISHED'} + SN = context.window_manager.bas_sculptnotes + #self.cur = (event.mouse_region_x, event.mouse_region_y) + #diff = (self.cur[0] - self.prev[0], self.cur[1] - self.prev[1]) + if self.start: + if event.type in {'LEFTMOUSE'} and event.value in {'CLICK'}: + self.start = False + self.bevel = self.strip.modifiers.new(name="Bevel", type='BEVEL') + self.bevel.width = 0 + self.bevel.segments = 0 + else: + if not hasattr(self, '_handle'): + # the arguments we pass the the callback + args = (self, context) + # Add the region OpenGL drawing callback + # draw in view space with 'POST_VIEW' and 'PRE_VIEW' + self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL') + if event.type in {'WHEELUPMOUSE'} and event.value in {'PRESS'} and event.ctrl: + #print("MÁS") + self.solid.thickness += self.thickness + elif event.type in {'WHEELDOWNMOUSE'} and event.value in {'PRESS'} and event.ctrl: + #print("MENOS") + self.solid.thickness -= self.thickness + ''' + mX = event.mouse_region_x + mY = event.mouse_region_y + if distance(mX, mY, self.lastX, self.lastY) > self.sensibility: + if mY > self.lastY: # hacia arriba # CAMBIAR POR CENTRO DE REGION + self.solid.thickness += self.thickness + elif mY < self.lastY: # hacia abajo # CAMBIAR POR CENTRO DE REGION + self.solid.thickness -= self.thickness + self.lastX = mX + self.lastY = mY + ''' + elif SN.strips_makeBevel: + if event.type in {'LEFTMOUSE'} and event.value in {'CLICK'}: + self.end = True + else: + if event.type in {'WHEELUPMOUSE'} and event.value in {'PRESS'} and event.ctrl: + if self.bevel.width < 0.03: + self.bevel.width += self.width + self.bevel.segments += 1 + elif event.type in {'WHEELDOWNMOUSE'} and event.value in {'PRESS'} and event.ctrl: + if self.bevel.width > 0: + self.bevel.width -= self.width + self.bevel.segments -= 1 + ''' + mX = event.mouse_region_x + mY = event.mouse_region_y + if distance(mX, mY, self.lastX, self.lastY) > self.sensibility: + if mX > self.lastX: # hacia der # CAMBIAR POR CENTRO DE REGION + self.bevel.width += self.width + elif mX < self.lastX: # hacia izq # CAMBIAR POR CENTRO DE REGION + self.bevel.width -= self.width + self.lastX = mX + self.lastY = mY + ''' + else: + self.end = True + #self.xpos = event.mouse_region_x + #self.ypos = event.mouse_region_y + return {'PASS_THROUGH'} + + def stroke2Mesh(self, context): + self.sculptObj = context.active_object + noteStrokes = self.noteData.layers.active.active_frame.strokes + if not noteStrokes: + return False + strPoints = noteStrokes[0].points + numPoints = len(strPoints) - 1 + if numPoints < 5 and not self.poly: + bpy.ops.bas.clear_note() + return False + SN = context.window_manager.bas_sculptnotes + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + note_id = str(random.randint(0,500) + random.randint(0,500)) + meshName = "" + note_id + "_QuadStrip" + mesh = bpy.data.meshes.new(meshName) # create a new mesh + self.strip = bpy.data.objects.new(meshName, mesh) + self.strip.location = Vector((0,0,0)) + context.view_layer.active_layer_collection.collection.objects.link(self.strip) + vertices = [] + edges = [] + oldPoint = None + p = 0 + threshold = SN.mergeDistThreshold + #maxDist = self.noteData.layers.active.thickness if SN.strips_thicknessForSize == True else threshold + maxDist = threshold + for point in strPoints: + if p == 0: + vertices.append(point.co) + p += 1 + oldPoint = point + else: + a = npy.array((point.co[0] ,point.co[1], point.co[2])) + b = npy.array((oldPoint.co[0], oldPoint.co[1], oldPoint.co[2])) + d = npy.linalg.norm(a-b) + if d >= maxDist: + vertices.append(point.co) + edges.append((p-1, p)) + p += 1 + oldPoint = point + # + mesh.from_pydata(vertices, edges, []) + mesh.validate(verbose=False) + mesh.remesh_voxel_size = 0.01 + + self.sculptObj.select_set(state=False) + self.strip.select_set(state=True) + context.view_layer.objects.active = self.strip + + bpy.ops.object.convert(target='CURVE') + #bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN') + + #SN.gp = self.strip + curve = bpy.data.curves[self.strip.name] + + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.curve.select_all(action='SELECT') + bpy.ops.curve.spline_type_set(type='BEZIER') + bpy.ops.transform.tilt(value=1.570796) + + #bpy.ops.transform.rotate(value=1.570796, orient_axis='X', orient_type='NORMAL') #1.570796 + + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY') + curve.extrude = maxDist / 2 + bpy.ops.object.convert(target='MESH') + + if SN.mirror: + self.mirror = self.strip.modifiers.new(name="Mirror", type='MIRROR') + if SN.decimation_symmetry_axis == 'X': + n = 0 + elif SN.decimation_symmetry_axis == 'Y': + n = 1 + else: + n = 2 + self.mirror.use_axis[n] + self.mirror.use_clip = False + self.mirror.mirror_object = self.sculptObj + + if SN.strips_makeSolid: + self.solid = self.strip.modifiers.new(name="Solid", type='SOLIDIFY') + self.solid.thickness = maxDist #0.03 + self.solid.offset = 1 # add later + self.solid.thickness_clamp = 0 + self.solid.use_rim = True + self.solid.use_rim_only = True + else: + self.sculptObj.select_set(state=True) + context.view_layer.objects.active = self.sculptObj + bpy.ops.object.mode_set(mode='SCULPT') + if SN.autoClear: + bpy.ops.bas.clear_note() + #return {'FINISHED'} + return True + + def returnToSculpt(self, context, obj): + SN = context.window_manager.bas_sculptnotes + ''' + print("END") + obj.select_set(state=False) + self.strip.select_set(state=True) + context.view_layer.objects.active = self.strip + + if SN.remeshIt: + self.solid.use_rim_only = False + if SN.mirror: + + bpy.ops.object.modifier_apply(modifier="Solid") + bpy.ops.object.modifier_apply(modifier="Bevel") + + if SN.remeshIt: + self.solid.use_rim_only = False + if SN.mirror: + self.mirror.use_mirror_merge = False + bpy.ops.object.modifier_apply(modifier="Mirror") + + for mod in self.strip.modifiers: + try: + bpy.ops.object.modifier_apply(modifier=mod.name) + except: + pass + + if SN.remeshIt: + bpy.ops.object.mode_set(mode='SCULPT') + bpy.ops.object.voxel_remesh() + print("remesh") + bpy.ops.object.mode_set(mode='OBJECT') + ''' + self.strip.select_set(state=False) + obj.select_set(state=True) + context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='SCULPT') + if SN.autoClear: + bpy.ops.bas.clear_note() + +curve_classes = ( + BAS_OT_Convert_Curves, + BAS_OT_Convert_Strips +) diff --git a/atelier/tools/sculpt_notes/prop_fun.py b/atelier/tools/sculpt_notes/prop_fun.py new file mode 100644 index 0000000..c50dfb5 --- /dev/null +++ b/atelier/tools/sculpt_notes/prop_fun.py @@ -0,0 +1,253 @@ +import bpy + + +def update_sculptNotes_curveShape(self, context): + try: + if self.curveShape != None: + self.curveShape_numNodes = len(bpy.data.curves[self.curveShape.name].splines[0].bezier_points) - 1 # se da por hecho de que la curveShape sólo tiene una spline y es bezier + self.curveShape_pivot_index = 0 # se resetea el index como precaución + if self.curveShape_numNodes == 0: # si fuese 0 probar con puntos normales en vez de bezier por si el tipo de spline es diferente + self.curveShape_numNodes = len(bpy.data.curves[self.curveShape.name].splines[0].points) - 1 # 0 in count + except: + pass + return + +def update_sculptNotes_solid_thickness(self, context): + try: + obj = self.gp + if obj == None: + return + else: + obj.modifiers["Solid"].thickness = self.thickness + except: + return +''' +def update_sculptNotes_curve_radius(self, context): + try: + curve = bpy.data.curves[scn.sculptNotes_curve.name] + curveShape = bpy.data.curves[scn.sculptNotes_curveShape.name] + curve.bevel_object = curveShape + curveObj = context.scene.sculptNotes_curveShape + if curveObj == None: + curveShapeObj = context.scn.sculptNotes_gp + if curveShapeObj == None: + return + else: + curveShape.bevel_depth = self.sculptNotes_radius + else: + r = self.sculptNotes_radius + curveObj.scale = (r, r, r) + except: + return +''' +def update_sculptNotes_ngon(self, context): + if self.ngon: + self.reproject = False + return + +def update_sculptNotes_reproject(self, context): + if self.reproject: + self.ngon = False + return + +def update_sculptNotes_method(self, context): + # FILTER OF OPTIONS + if self.method_type == 'SOLID': + self.canJoinStrokes = True + self.canMergeStrokes = True + self.canReproject = True + self.canNgon = True + self.canMirror = True + self.canSmooth = True + self.canRemesh = True + self.method = 1 + elif self.method_type == 'FLAT': + self.canJoinStrokes = True + self.canMergeStrokes = True + self.canReproject = False + self.canNgon = True + self.canMirror = False + self.canSmooth = False + self.canRemesh = False + self.method = 2 + elif self.method_type == 'WRAP': + self.canJoinStrokes = False + self.canMergeStrokes = False + self.canReproject = False + self.canNgon = False + self.canMirror = True + self.canSmooth = False + self.canRemesh = True + self.method = 3 + elif self.method_type == 'CURVE': + self.canJoinStrokes = False + self.canMergeStrokes = False + self.canReproject = False + self.canNgon = False + self.canMirror = True + self.canSmooth = False + self.canRemesh = True + self.method = 4 + elif self.method_type == 'PATH': + self.canJoinStrokes = False + self.canMergeStrokes = False + self.canReproject = False + self.canNgon = False + self.canMirror = True + self.canSmooth = False + self.canRemesh = True + self.method = 5 + elif self.method_type == 'STRIPS': + self.canJoinStrokes = False + self.canMergeStrokes = False + self.canReproject = False + self.canNgon = False + self.canMirror = False + self.canSmooth = False + self.canRemesh = False + self.method = 6 + else: + self.canJoinStrokes = False + self.canMergeStrokes = False + self.canReproject = False + self.canNgon = False + self.canMirror = True + self.canSmooth = False + self.canRemesh = True + self.method = 0 + return + +def update_sculptNotes_curve_curveMap(self, context): + if self.curve_useCurveMapForSplinePointsRadius: + self.showCurveMapEditor = True + if not self.curve_curveMap_isCreated: + bpy.ops.bas.sculpt_notes_create_curve_map() + else: + self.showCurveMapEditor = False + return + +def update_sculptNotes_curveShape_pivot_mode(self, context): + obj = context.active_object + bpy.ops.object.mode_set(mode='OBJECT') + curveShape = self.curveShape + if curveShape == None: + curveShape = bpy.data.curves[self.curve.name].bevel_object + if curveShape == None: + try: + bpy.ops.object.mode_set(mode='SCULPT') + except: + pass + return + else: + if self.curveShape_numNodes <= 1: + numPoints = len(bpy.data.curves[curveShape.name].splines[0].bezier_points) + if numPoints == 0: + numPoints = len(bpy.data.curves[curveShape.name].splines[0].points) + self.curveShape_numNodes = numPoints - 1 + pass + curveShape.select_set(state=True) + obj.select_set(state=False) + context.view_layer.objects.active = curveShape + bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY') + #bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) # TEST + #bpy.ops.view3d.snap_selected_to_grid() + if self.curveShape_pivot_mode == 'CENTER': + pass + else: + # ref. a cursor y pos. + cursor = context.scene.cursor + prevLoc = cursor.location + # ref. a curveShape y sus puntos + spline = bpy.data.curves[curveShape.name].splines[0] + if spline.type == 'BEZIER': + points = spline.bezier_points + else: + points = spline.points + # dependiendo del modo posicionar el cursor + if self.curveShape_pivot_mode == 'FIRST': + cursor.location = points[0].co + elif self.curveShape_pivot_mode == 'LAST': + cursor.location = points[self.curveShape_numNodes].co + elif self.curveShape_pivot_mode == 'NODE': + p = points[self.curveShape_pivot_index].co + cursor.location = (p[0],p[1],p[2]) + #cursor.location = points[self.curveShape_pivot_index].co + elif self.curveShape_pivot_mode == 'AVERAGE': + cursor.location = (points[self.curveShape_numNodes].co - points[0].co) / 2 + #aplicar el pivote al cursor + bpy.ops.object.origin_set(type='ORIGIN_CURSOR') + # volver cursor a su estado anterior + cursor.location = prevLoc + curveShape.select_set(state=False) + obj.select_set(state=True) + context.view_layer.objects.active = obj + try: + bpy.ops.object.mode_set(mode='SCULPT') + except: + pass + return + +def update_sculptNotes_curveShape_pivot_index(self, context): + obj = context.active_object + bpy.ops.object.mode_set(mode='OBJECT') + curveShape = self.curveShape + + if curveShape == None: + curveShape = bpy.data.curves[self.curve.name].bevel_object + if curveShape == None: + try: + bpy.ops.object.mode_set(mode='SCULPT') + except: + pass + return + else: + if self.curveShape_numNodes <= 1: + numPoints = len(bpy.data.curves[curveShape.name].splines[0].bezier_points) + if numPoints == 0: + numPoints = len(bpy.data.curves[curveShape.name].splines[0].points) + self.curveShape_numNodes = numPoints - 1 + pass + curveShape.select_set(state=True) + obj.select_set(state=False) + context.view_layer.objects.active = curveShape + + bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY') + #bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) # TEST + #bpy.ops.view3d.snap_selected_to_grid() + + # ref. a cursor y pos. + cursor = context.scene.cursor + prevLoc = cursor.location + # ref. a curveShape y sus puntos + spline = bpy.data.curves[curveShape.name].splines[0] + if spline.type == 'BEZIER': + points = spline.bezier_points + else: + points = spline.points + + if self.curveShape_pivot_index > self.curveShape_numNodes: + self.curveShape_pivot_index = 0 + elif self.curveShape_pivot_index < 0: + self.curveShape_pivot_index = self.curveShape_numNodes + p = points[self.curveShape_pivot_index].co + cursor.location = (p[0],p[1],p[2]) + + # aplicar el pivote al cursor + bpy.ops.object.origin_set(type='ORIGIN_CURSOR') + # volver cursor a su estado anterior + cursor.location = prevLoc + + curveShape.select_set(state=False) + obj.select_set(state=True) + context.view_layer.objects.active = obj + try: + bpy.ops.object.mode_set(mode='SCULPT') + except: + pass + return + +def update_sculptNotes_path_object(self, context): + if self.isCreated: + if self.sculptNotes_path_object == None: + self.isCreated = False + return diff --git a/atelier/tools/sculpt_notes/ui.py b/atelier/tools/sculpt_notes/ui.py new file mode 100644 index 0000000..42b79a3 --- /dev/null +++ b/atelier/tools/sculpt_notes/ui.py @@ -0,0 +1,319 @@ +from bpy.types import Panel + + +class BAS_PT_sculpt_notes(Panel): + bl_label = "Sculpt Notes" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_context = ".paint_common" + bl_category = 'Sculpt' + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + return context.mode == 'SCULPT' + + def draw_curve_SN(self, context, layout, curve, name, drawMasterBox): + if drawMasterBox: + curveProps = layout.box() + curveProps.label(text=name) + curveProps = curveProps.box() + else: + layout.label(text=name) + curveProps = layout.box() + curveProps.use_property_split = True + + _col = curveProps.column() + + if curve.bevel_object is None: + + #col.prop(curve, "offset") + sub = _col.column() + #sub.active = (curve.bevel_object is None) + sub.prop(curve, "extrude") + + #col.prop(curve, "taper_object", text="Taper Curve") + + #col = curveProps.column() + #sub = col.column() + #sub.active = (curve.bevel_object is None) + sub.prop(curve, "bevel_depth", text="Depth") + sub.prop(curve, "bevel_resolution", text="Resolution") + + else: + bevelObj = curve.bevel_object + _col.prop(bevelObj, "scale", text="Scale ") + + _col.prop(curve, "bevel_object", text="Bevel Curve") + + sub = _col.column() + sub.active = curve.bevel_object is not None + sub.prop(curve, "use_fill_caps") + + _col = curveProps.column() + _col.active = ( + (curve.bevel_depth > 0.0) or + (curve.extrude > 0.0) or + (curve.bevel_object is not None) + ) + sub = _col.column(align=True) + sub.prop(curve, "bevel_factor_start", text="Bevel Start") + sub.prop(curve, "bevel_factor_end", text="End") + + def draw(self, context): + SN = context.window_manager.bas_sculptnotes + tool_settings = context.tool_settings + view = context.space_data + overlay = view.overlay + layout = self.layout + layout.scale_y = 1.1 + col = layout.column().box() + row = col.row() + row.scale_y = 1.2 + + # SYSTEM SELECTION + #row.prop( SN, 'use', text="Annotations", expand=True) # GREASEPENCIL + # ANNOTATION OPTIONS + #if SN.use == 'NOTES': + _row = col.row() + _row = _row.grid_flow(row_major=True, columns=3, align=False) + if overlay.show_annotation: + icon_show_annot = 'HIDE_OFF' + else: + icon_show_annot = 'HIDE_ON' + _row.scale_x = 1.2 + _row.prop(overlay, "show_annotation", text="", icon=icon_show_annot) + placement = _row.row(align=True) + placement.scale_x = 1.2 + placement.prop(tool_settings, "annotation_stroke_placement_view3d", text="", expand=True) + _row = _row.row(align=True) + _row.operator("bas.undo_note", text="", icon='LOOP_BACK') + _row.operator("bas.clear_note", text="Clear Notes", icon='PANEL_CLOSE') + if not SN.isCreated: + col.row().prop( SN, 'autoClear') + # GREASE PENCIL + #else: + # col.label(text="Work In Progress !") + # return + + if not SN.isCreated: + # AJUSTES GENERALES DE CONVERSION + col = layout.column().box() + col.prop( SN, 'mergeDistThreshold', slider=True) + col = layout.column().box() + # METHOD + OPTIONS #################### + row = col.row() + row.grid_flow(columns=3, align=True).prop( SN, "method_type", expand=True) + # JOIN STROKES + if SN.canJoinStrokes: + row = col.row() + row.prop( SN, 'joinStrokes') + # MERGE STROKES + if SN.canMergeStrokes: + row = col.row() + row.prop( SN, 'mergeStrokes', text="Merge strokes into one") + # REPROJECT + _row = col.row(align=True) + if SN.canReproject: + _col = _row.column() + _col.enabled = not SN.ngon + _col.prop( SN, 'reproject', text="Re-project?") + # NGONS + if SN.canNgon: + _col = _row.column() + _col.prop( SN, 'ngon', text="N-gon?") + + # FILTER OF OPTIONS + method = SN.method + if method == 1: + usingCurves = False + col.prop( SN, 'thickness', text="Thickness", slider=True) + elif method == 2: + usingCurves = False + pass + elif method == 3 or method == 4: + usingCurves = True + ''' + if not SN.isCreated: + if SN.curve_useCurveMapForSplinePointsRadius: + if SN.method == 3: + _row = col.row() + _row.prop( SN, 'radiusMultiplier', text="Radius Multiplier", slider=True) + else: + _row = col.row() + _row.prop( SN, 'radius', text="Radius", slider=True) + ''' + elif method == 5: + usingCurves = True + box = col.box() + box.label(text="Follow-Path Object :") + _row = box.row() + #_row.use_property_split = True + _row.prop( SN, 'path_object', text="Object") + _row = box.split(align=True, factor=0.48) + _row.prop( SN, 'path_object_makeCopy', text="Make a Copy") + _row.prop( SN, 'path_object_pivotToCenter', text="Pivot to Center") + elif method == 6: + usingCurves = False + box = col.box() + box.label(text="Strip Settings :") + _row = box.column() + #_row.prop( SN, 'strips_thicknessForSize', text="Note Thickness for Quad Size?") + _row.prop( SN, 'strips_makeSolid') + _row = _row.row() + _row.enabled = SN.strips_makeSolid + _row.prop( SN, 'strips_makeBevel') + else: + usingCurves = False + pass + + if usingCurves: + _row = col.row() + _row.alert=True + _row.prop( SN, "curve_postEdit", text="Post Edition") + _row.alert=False + # CURVE PROPERTIES + if SN.isCreated: + from bpy import data as D + + if method == 5: # PATH + objPath = SN.path_object + pathProps = layout.box() + pathProps.label(text="Path Properties :") + pathProps = pathProps.box() + _col = pathProps.column() + + # _col.prop( SN, "path_object", text="Path Object") + array = objPath.modifiers["_Array"] + #offset = array.constant_offset_displace[0] + _col.prop(array, "constant_offset_displace", text="Offset") + _row = _col.row(align=True) + _row.prop(array, "use_merge_vertices", text="Merge") + _row.prop(array, "merge_threshold", text="Distance") + _col.prop(array, "start_cap") + _col.prop(array, "end_cap") + + if objPath.type == 'CURVE': + objPathCurve = D.curves[objPath.name] + self.draw_curve_SN(context, pathProps, objPathCurve, "Sub-Curve Properties :", False) + + + curve = D.curves[SN.curve.name] + self.draw_curve_SN(context, layout, curve, "Curve Properties :", True) + + if curve.bevel_object != None and method != 5: + pivotProps = layout.box() + pivotProps.label(text="Curve-Shape Pivot :") + pivotProps = pivotProps.box() + _col = pivotProps.column() + _col.prop( SN, 'curveShape_pivot_mode', text="Mode") + if SN.curveShape_pivot_mode == 'NODE': + _col.prop( SN, 'curveShape_pivot_index', text="Index") + + + else: + col.prop( SN, 'curve_useCurveMapForSplinePointsRadius') + _row = col.row(align=True) + _row.prop( SN, 'curve_isCyclic', text="Is Cyclic?") + _row.prop( SN, 'curve_simplify', text="Simplify Curve") + + #if method == 5: + + + + # CURVE MAP EDITOR + + if SN.curve_curveMap_isCreated: + boxCol = col.column(align=True) + curveBox = boxCol.box() + curveBox.emboss = 'NONE' + arrowIcon = 'DOWNARROW_HLT' if SN.showCurveMapEditor else 'RIGHTARROW' + curveBox.row().prop( SN, 'showCurveMapEditor', icon=arrowIcon) + curveBox.scale_y = 0.7 + if SN.showCurveMapEditor: + from bpy import data as D + curveBox = boxCol.box() + try: + curveBox.enabled = SN.curve_useCurveMapForSplinePointsRadius + #curveBox.template_curve_mapping(CurveData('CurveData'), "mapping", type='NONE') # types: vector (tiene 3 curvas/canales: para x,y,z) # none (normal, 1 curva/canal solamente), COLOR (tiene 4 canales/curvas) + node = D.node_groups['NodeGroup'].nodes['CurveData'] + curveBox.template_curve_mapping(node, "mapping", type='NONE') + except: + curveBox.operator("bas.sculpt_notes_create_curve_map", text="Create Curve Map") + curveBox.label(text="An error has been ocurred. Please report it", icon='ERROR') + pass + curveBox.separator(factor=0.1) + + if not usingCurves or SN.isCreated: + ''' + # MIRROR + if SN.canMirror: + _row = col.row(align=True) # col to row + _row = _row.split(factor=0.5, align=True) + _row.prop( SN, 'mirror', icon='MOD_MIRROR') + _row = _row.grid_flow(columns=4, align=True) + _row.prop( SN, "decimation_symmetry_axis", expand=True) + # SMOOTH + if SN.canSmooth: + _row = col.row(align=True) + _row.prop( SN, 'smooth', icon='SMOOTHCURVE') + if SN.smooth: + _row.prop( SN, 'smoothPasses') + ''' + # REMESH + if SN.canRemesh: + _row = col.row(align=True) + _row.prop( SN, 'remeshIt', icon='OUTLINER_OB_META') + if SN.remeshIt: + mesh = context.active_object.data + _row.prop(mesh, "remesh_voxel_size") + + # OPCIONES PARA CONVERTIR / DIRECTA / APLICAR / BORRAR / CANCELAR + col = layout.column() + _col = layout.column_flow() + _col.scale_y = 1.3 + if SN.method_type in {'WRAP', 'PATH'}: + _col.alert = True + _col.label(text="May not be working in this version") + __row = _col.row() + _row = __row.split(factor=0.3, align=True) + if not SN.live: + if SN.applyModifiersDirectly and not SN.isCreated: + if usingCurves: + __row.operator("bas.sculpt_notes_convert_to_curve", text="> Convert To Curve <") + elif method == 6: + __row.operator("bas.sculpt_notes_convert_to_strip", text="> Convert To Strip <") + else: + if SN.joinStrokes or SN.mergeStrokes: + col.prop( SN, 'applyModifiersDirectly') + __row.operator("bas.sculpt_notes_convert_to_3d", text="> Convert To Mesh <") + #_row.prop( SN, 'live', text="LIVE", icon='REC') + else: + __row.operator("bas.sculpt_notes_convert_to_3d", text="> Convert To Mesh <") + else: + _row = __row + if SN.isCreated: + if usingCurves: + _row.operator("bas.sculpt_notes_cancel", text="Apply") + _row.operator("bas.sculpt_notes_curve_to_mesh", text="To Mesh") + _row.operator("bas.sculpt_notes_remove_curve", text="Remove") + else: + _row.operator("bas.sculpt_notes_apply_modifiers", text="Apply") + _row.operator("bas.sculpt_notes_cancel", text="Cancel") + _row.operator("bas.sculpt_notes_remove_mesh", text="Remove") + else: + if usingCurves: + __row.operator("bas.sculpt_notes_convert_to_curve", text="> Convert To Curve <") + elif method == 6: + __row.operator("bas.sculpt_notes_convert_to_strip", text="> Convert To Strip <") + else: + if SN.joinStrokes: + col.prop( SN, 'applyModifiersDirectly') + _row.operator("bas.sculpt_notes_convert_to_3d", text="> Convert To Mesh <") + + else: + _row = __row + _row.alert=True + _row.prop( SN, 'live', text="LIVE", icon='REC') + row = _row.row() + row.label(text="Coming Soon!") \ No newline at end of file diff --git a/atelier/tools/sculpt_notes/utils.py b/atelier/tools/sculpt_notes/utils.py new file mode 100644 index 0000000..b055616 --- /dev/null +++ b/atelier/tools/sculpt_notes/utils.py @@ -0,0 +1,136 @@ +import bpy +from bpy.types import Operator +from ...utils.others import ShowMessageBox + + +class BAS_OT_clear_note(Operator): + bl_idname = "bas.clear_note" + bl_label = "" + bl_options = {'REGISTER', 'UNDO'} + bl_description = "Clear Notes" + + def execute(self, context): + try: + note_data = context.scene.grease_pencil + frame = note_data.layers.active.active_frame + if len(frame.strokes) < 1: + ShowMessageBox("No notes to clear", "Can't do this!", 'INFO') + else: + frame.clear() + except: + ShowMessageBox("No notes to clear", "Can't do this!", 'INFO') + return {'FINISHED'} + +class BAS_OT_undo_note(Operator): + bl_idname = "bas.undo_note" + bl_description = "Undo Note" + bl_label = "" + bl_options = {'REGISTER', 'UNDO'} + + def error(self, context): + self.layout.label(text="No strokes to undo !") + + def execute(self, context): + note_data = context.scene.grease_pencil + frame = note_data.layers.active.active_frame + if len(frame.strokes) < 1: + bpy.context.window_manager.popup_menu(BAS_OT_undo_note.error, title = "Can't do this !", icon = 'INFO') + else: + stroke_count = len(frame.strokes) + last_stroke = frame.strokes[stroke_count-1] + frame.strokes.remove(last_stroke) + return {'FINISHED'} + +class BAS_OT_sculpt_notes_remove_mesh(Operator): + bl_idname = "bas.sculpt_notes_remove_mesh" + bl_label = "Sculpt Notes: Remove Mesh" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + def execute(self, context): + SN = context.window_manager.bas_sculptnotes + try: + bpy.data.objects.remove(SN.sculptNotes_gp) + except: + ShowMessageBox("No recent SculptNote mesh was found", "Can't do this!" 'INFO') + pass + SN.sculptNotes_isCreated = False + return {'FINISHED'} + +class BAS_OT_sculpt_notes_remove_curve(Operator): + bl_idname = "bas.sculpt_notes_remove_curve" + bl_label = "Sculpt Notes: Remove Curve" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + def execute(self, context): + SN = context.window_manager.bas_sculptnotes + try: + bpy.data.objects.remove(SN.sculptNotes_curve) + except: + ShowMessageBox("No recent SculptNote curve 'path' was found", "Can't do this!" 'INFO') + pass + if SN.method == 3: # WRAP + try: + bpy.data.objects.remove(SN.sculptNotes_curveShape) + except: + ShowMessageBox("No recent SculptNote curve 'shape' was found", "Can't do this!" 'INFO') + elif SN.method == 5: # PATH + try: + bpy.data.objects.remove(SN.sculptNotes_path_object) + except: + ShowMessageBox("No recent 'path' object was found", "Can't do this!" 'INFO') + SN.sculptNotes_isCreated = False + return {'FINISHED'} + +class BAS_OT_sculpt_notes_cancel(Operator): + bl_idname = "bas.sculpt_notes_cancel" + bl_label = "Sculpt Notes: Cancel Operation" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + def execute(self, context): + SN = context.window_manager.bas_sculptnotes + SN.sculptNotes_gp = None + SN.sculptNotes_curve = None + SN.sculptNotes_curveShape = None + SN.sculptNotes_isCreated = False + SN.sculptNotes_path_object = None + return {'FINISHED'} + +def SN_NodeTree(): + if 'SN_NodeGroup' not in bpy.data.node_groups: + ng = bpy.data.node_groups.new('SN_NodeGroup', 'ShaderNodeTree') + ng.fake_user = True + return bpy.data.node_groups['SN_NodeGroup'].nodes + +curve_node_mapping = {} +def SN_CurveData(curve_name): + if curve_name not in curve_node_mapping: + cn = SN_NodeTree().new('ShaderNodeRGBCurve') + cn.mapping.initialize() + curve_node_mapping[curve_name] = cn.name + return SN_NodeTree()[curve_node_mapping[curve_name]] + +class BAS_OT_sculpt_notes_create_curve_map(Operator): + bl_idname = "bas.sculpt_notes_create_curve_map" + bl_label = "Sculpt Notes: Create Curve Map" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + def execute(self, context): + if 'SN_NodeGroup' not in bpy.data.node_groups: + nodeGroup = bpy.data.node_groups.new('SN_NodeGroup', 'ShaderNodeTree') + #nodeGroup.fake_user = True + curveNode = nodeGroup.nodes.new('ShaderNodeRGBCurve') + curveNode.name = 'SN_CurveData' + curveNode.mapping.initialize() + #print(curveNode.__dir__()) + #print(curveNode.mapping.__dir__()) + context.window_manager.bas_sculptnotes.curve_curveMap_isCreated = True + context.window_manager.bas_sculptnotes.curve_useCurveMapForSplinePointsRadius = True + return {'FINISHED'} + + +utils_classes = ( + BAS_OT_clear_note, BAS_OT_undo_note, + BAS_OT_sculpt_notes_remove_curve, BAS_OT_sculpt_notes_remove_mesh, + BAS_OT_sculpt_notes_cancel, + BAS_OT_sculpt_notes_create_curve_map +) diff --git a/atelier/tools/texture_management/__init__.py b/atelier/tools/texture_management/__init__.py new file mode 100644 index 0000000..4980bc8 --- /dev/null +++ b/atelier/tools/texture_management/__init__.py @@ -0,0 +1,209 @@ +# Copyright (C) 2019 Juan Fran Matheu G. +# Contact: jfmatheug@gmail.com + +import bpy +import os +from bpy.types import Operator, Panel + +class Alphas_OT_LoadAll_2(Operator): + """Load All Alpha from Directory""" + bl_idname = "texture.alphas_load_all_2" + bl_label = "Autoload Texture Alphas" + + def execute(self, context): + path = context.scene.alphas_path + files = [] + # r=root, d=directories, f = files + for r, d, f in os.walk(path): + for file in f: + if '.png' in file: + files.append(os.path.join(r, file)) + + for f in files: + print(f) + + return {'FINISHED'} + +def SelectFormats(formats): + if formats == 'ALL': + return set([".png", ".jpg", ".tif", ".tga", ".psd"]) + else: + if formats == 'JPG': + fileFormat = ".jpg" + elif formats == 'PNG': + fileFormat = ".png" + elif formats == 'TGA': + fileFormat = ".tga" + elif formats == 'TIF': + fileFormat = ".tif" + elif formats == 'PSD': + fileFormat = ".psd" + return fileFormat + +def ConvertToTextures(images, path, fake_user, pack): + for img in images: + fullname = os.path.join(path, img) + tex = bpy.data.textures.new(img[:50]+'.alpha', type='IMAGE') # +'.autoload' + tex.image = bpy.data.images.load(fullname) + tex.use_alpha = True + tex.use_fake_user = fake_user + if pack: tex.image.pack() + +import glob +class Alphas_OT_LoadAll(Operator): + """Load All Alpha from Directory""" + bl_idname = "texture.alphas_load_all" + bl_label = "Load Texture Alphas" + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + scn = context.scene + #print("path:"+context.scene.alphas_path) + #print("abspath:"+bpy.path.abspath(context.scene.alphas_path)) + #path = bpy.path.abspath(context.scene.alphas_path) + path = scn.alphas_path + + if not os.path.exists(path) or not os.path.isdir(path): + self.report({'ERROR'}, "Invalid directory") + return {'FINISHED'} + + formats = scn.alphas_format + oktypes = SelectFormats(formats) + #print(oktypes) + + # SUB-DIRS + if scn.alphas_includeSubDirs: + # ALL FORMATS + if formats == 'ALL': + okfiles = [] + for t in oktypes: + for f in glob.glob(path + "**/*" + t, recursive=True): + okfiles.append(f) + #print(t) + #print(f) + + # SPECIFIC FORMAT + else: + okfiles = [f for f in glob.glob(path + "**/*" + oktypes, recursive=True)] + # NOT SUB-DIRS + else: + dirfiles = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))] + if formats != 'ALL': + _oktypes = set([oktypes]) + okfiles = [f for f in dirfiles if f[-4:].lower() in _oktypes] + + #print(okfiles) + ConvertToTextures(okfiles, path, scn.alphas_markFakeUsers, scn.alphas_packInsideBlendFile) + + return {'FINISHED'} + +class Alphas_OT_RemoveAll(Operator): + """Remove All Alphas from Directory""" + bl_idname = "texture.alphas_remove_all" + bl_label = "Remove Texture Alphas" + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + # LAS QUE CONTENGAN LA PARTÍCULA .alpha AÑADIDA AL CARGARLAS + # CAMBIAR ESTO POR ALGO MÁS CONVENIENTE EN FUTURO + remove_these = [i for i in bpy.data.textures.keys() if 'alpha' in i.split('.')] + for item in remove_these: + tex = bpy.data.textures[item] + img = tex.image + + if not tex.users: # SIN USERS [TEXTURES] + bpy.data.textures.remove(tex) + if img: + img.user_clear() + if not img.users: # SIN USERS [IMAGES] + bpy.data.images.remove(img) + self.report({'INFO'}, "%i textures cleaned" % len(remove_these)) + return {'FINISHED'} + +class ALPHAS_PT_Manager(Panel): + """Panel for Texture Manager""" + bl_label = "Textures / Alphas" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = "Sculpt" + bl_context = ".paint_common" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + scn = context.scene + obj = context.object + row = layout.row(align=True) + row.label(text="Directory :", icon='TEXTURE') + row.prop(context.scene, "alphas_path", text="") + row = layout.row(align=True) + col = row.column(align=True) + col.ui_units_x = 2 + col.prop(scn, 'alphas_format', expand=False, text="") + row.prop(scn, 'alphas_includeSubDirs', text="Include Sub-Directories", toggle=False) + col = layout.column(align=True) + col.prop(scn, 'alphas_markFakeUsers') + col.prop(scn, 'alphas_packInsideBlendFile') + col = layout.column(align=False) + col.operator("texture.alphas_load_all", icon='ADD', text="Create Textures") + col.operator("texture.alphas_remove_all", icon='REMOVE', text="Remove - 0 users - Textures") + +classes = ( + ALPHAS_PT_Manager, + Alphas_OT_RemoveAll, + Alphas_OT_LoadAll, + Alphas_OT_LoadAll_2 +) + + +def register(): + + bpy.types.Scene.alphas_includeSubDirs = bpy.props.BoolProperty( + name="Include Sub-Directories", default=True + ) + bpy.types.Scene.alphas_markFakeUsers = bpy.props.BoolProperty( + name="Mark fake user", description="This will keep texture data inside blend file even if you close Blender", default=True + ) + bpy.types.Scene.alphas_packInsideBlendFile = bpy.props.BoolProperty( + name="Pack in blend file", default=False + ) + bpy.types.Scene.alphas_path = bpy.props.StringProperty( + name="Alphas Path", + description="Alphas Location", + subtype='DIR_PATH' + ) + bpy.types.Scene.alphas_format = bpy.props.EnumProperty( + name="Images Format", + description="Just import images of this file format", + items=( + ('ALL', "All", ""), + ('PNG', ".png", ""), + ('TGA', ".tga", ""), + ('TIFF', ".tif", ""), + ('JPG', ".jpg", ""), + ('PSD', ".psd", "") + ), + default='ALL' + ) + + from bpy.utils import register_class + for cls in classes: + register_class(cls) + +def unregister(): + + from bpy.utils import unregister_class + for cls in reversed(classes): + unregister_class(cls) + + del bpy.types.Scene.alphas_path + del bpy.types.Scene.alphas_includeSubDirs + del bpy.types.Scene.alphas_format + del bpy.types.Scene.alphas_markFakeUsers + del bpy.types.Scene.alphas_packInsideBlendFile diff --git a/atelier/tools/texture_management/__pycache__/__init__.cpython-37.pyc b/atelier/tools/texture_management/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd4c418d16bf486f39cbca7990b0c7b94cc670e8 GIT binary patch literal 6360 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 literal 0 HcmV?d00001 diff --git a/atelier/user_data/sculpt_brush_icons/Blob_icon.png b/atelier/user_data/sculpt_brush_icons/Blob_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a90520c68c2b4eebd55d5ecfef4cc596cecc96ca GIT binary patch literal 21335 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 P00000NkvXXu0mjf*vbKz literal 0 HcmV?d00001 diff --git a/atelier/user_data/sculpt_brush_icons/Clay Thumb_icon.png b/atelier/user_data/sculpt_brush_icons/Clay Thumb_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..390cc9ba02a24020f6e192b43cc75223d4253a3c GIT binary patch literal 17733 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 literal 0 HcmV?d00001 diff --git a/atelier/user_data/sculpt_brush_icons/Crease_icon.png b/atelier/user_data/sculpt_brush_icons/Crease_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1a6ebf068b67391d6360e4d4a69ed0cff0027eba GIT binary patch literal 17308 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 Hu0mjfPR-~r literal 0 HcmV?d00001 diff --git a/atelier/user_data/sculpt_brush_icons/Draw Face Sets_icon.png b/atelier/user_data/sculpt_brush_icons/Draw Face Sets_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..90f0fea34e1d08bea4ac0efbebe6ac5f21e33ff4 GIT binary patch literal 16772 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 literal 0 HcmV?d00001 diff --git a/atelier/user_data/sculpt_brush_icons/Draw Sharp_icon.png b/atelier/user_data/sculpt_brush_icons/Draw Sharp_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3ea78ac179c4d0df9f7ad87e138fd161711be548 GIT binary patch literal 20926 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
l@tPX9nacg|xRP7yD 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 literal 0 HcmV?d00001 diff --git a/atelier/user_data/sculpt_brush_icons/Flatten/Contrast_icon.png b/atelier/user_data/sculpt_brush_icons/Flatten/Contrast_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9300f3e40b8f11e738b01a5ed44e43250aec1b4d GIT binary patch literal 16807 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 VWdigR4%h$y002ovPDHLkV1k7g!>j-R literal 0 HcmV?d00001 diff --git a/atelier/user_data/sculpt_brush_icons/Mask_icon.png b/atelier/user_data/sculpt_brush_icons/Mask_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..90f0fea34e1d08bea4ac0efbebe6ac5f21e33ff4 GIT binary patch literal 16772 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 literal 0 HcmV?d00001 diff --git a/atelier/user_data/sculpt_brush_icons/Multi-plane Scrape_icon.png b/atelier/user_data/sculpt_brush_icons/Multi-plane Scrape_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a2eb558eddd30b9e0bf8f98b9d8fe0e630613af4 GIT binary patch literal 17342 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 z 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 JPDHLkV1m^T9y|a5 literal 0 HcmV?d00001 diff --git a/atelier/user_data/sculpt_brush_icons/Rotate_icon.png b/atelier/user_data/sculpt_brush_icons/Rotate_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a658cbfd2374891e292003ee25d099b1e752120a GIT binary patch literal 16809 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¨X#F3!oH5kOiK5W9Ge05L+} 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 literal 0 HcmV?d00001 diff --git a/atelier/user_data/sculpt_brush_icons/Snake Hook_icon.png b/atelier/user_data/sculpt_brush_icons/Snake Hook_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9415b65851cc753df2026163ef5400e323a77b1e GIT binary patch literal 17265 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 QbpQYW07*qoM6N<$g8f;~wEzGB literal 0 HcmV?d00001 diff --git a/atelier/user_data/sculpt_brush_icons/Thumb_icon.png b/atelier/user_data/sculpt_brush_icons/Thumb_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b3fb0966e3f4047af3b06147c0018867dac1a2e7 GIT binary patch literal 16391 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 AOaK4? literal 0 HcmV?d00001 diff --git a/atelier/utils/__pycache__/draw2d.cpython-37.pyc b/atelier/utils/__pycache__/draw2d.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81e60086b8219cea060c9136fea35f163b33c186 GIT binary patch literal 17483 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 AApigX literal 0 HcmV?d00001 diff --git a/atelier/utils/__pycache__/draw3d.cpython-37.pyc b/atelier/utils/__pycache__/draw3d.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71f09ac4afef331c5671e28415a1ece7427ab18b GIT binary patch literal 1715 zcmah}&2HO95aupPiJ~M&ahs$?i-HAuFoQsFBlpmzO`6zs;lhzsE6#x+K+xQkOoJkY z-IeoGr5EQVTJ#{JrviP8zQx{(o{YXhPn}(n&JSvIfgR2ccW36??;{_y+pYm)j)#NBEVgZ?kNu1IDt)Gs(H4c51`+e`Ox8D8C z_ejc<2->*lLUwvv(LsM_dvE_?w7Ivtx1V5j=`fTMKpn;= zOuhw17;{9BKmrr|3qCbp;5k~DX9j}!!XoC}IJS>#XSiHj#MVAu)aU37DU|@HwZ3$B zclzH%!$QUB`qF{svO*kKAIsuo~ zVVV=!F0VJz*DIThj)C5Y#3GifQ_A>Mso{i1pb?ORvdeL=I7vSBjIrXrk)G~=Oaqer^aXL5B5+z3e8Duke9H_hqC&;efoV<3zy z)WQzxpcZ29Lc}*2g2yZ4o98-kO+}S?gI^m0n%~>tEcqpPZltJW7nST{ViK!l(|nCh zyDx3U=1`hIqqGyHGvCsMmgb^3L={_1F~^$9C09DD^ zTtNIn!d73_275dGM?-c4(y{lzT!PM8qEkCAhqzKf9299TxT4}sj31~1SEP9edN9Y+ znQVaRif2>Gnfh8~2i6hDmBqZPb*isAaMiZuFOn(X!G|Z5fvihSM?De|t*R)4%MCsn z!JlG6b8*T0Ys*w`AG`4?Tzzd?23n7rAEp4jMy#e-v+I00Dab6Nca==|buf;mwS`(O O%eAnJ9JAG2t^Er+ow6$c literal 0 HcmV?d00001 diff --git a/atelier/utils/__pycache__/geo2dutils.cpython-37.pyc b/atelier/utils/__pycache__/geo2dutils.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..afb282144d2d4a73f29a89ef6ca0887aeb4d5024 GIT binary patch literal 4120 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 j%B4f6zCthNeekJpC(iqokOd1 zm+@a9r0vMR`Z(e)fBgzj^O{lFg;18iMiY^UtGG2caLb7C3Phc2kC_ytkxnJOOtep`V zxmJm}1sZx6c;;xYaLzDBA5psDq5_X9CpFo}XhtxaF2gAqRp;lB2}DoH0Xjsfa{wM> z;B}>a`P6BS4H2<0)i_m}CP~9LHI)y9%p$IosYk~=ItjB}i(DHr5EDbF5N34{r&LZt zLG^JcCi*x_jVENBYO~q~8P0lhg^rY&G?lqP!!>n5qZ2yhVM@nb+QQ``7=!tDB$r|L zOSPW_?-iFSc)cC8gxI2*2D=#@$LTQGOnAz;ytkd@DWf{h((6$0aO3fVKt(bZS_PD` zELA~E^CaeSFUk|4gIvdn3a%5lDwxX+C~D=(gM!NRY+|Yo24NpNcn#O_Dwa3E{{zhf z$A?)$`v4n&<|Js0XMm*y=**k~Yd|;h&hU%?k_sbt5OdcN^VZSKEu8bJ_6u@`S%p=Z z&lU>zEBfXD!dSXq|ReuI*LnJzS&n#v*9 z&l(G+YB|9bdDEVkU9Z?()f!ubeM0h7D^r^jj{u?}O3Q{PZ6E&L>@W5X_jlfP!(MBD zr+;hnZKu1{*$wwz?ROy5e$#r{DM8N7iWR?28PE&=>#>zpHZ`>*6-~a#s literal 0 HcmV?d00001 diff --git a/atelier/utils/__pycache__/space_conversion.cpython-37.pyc b/atelier/utils/__pycache__/space_conversion.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e14961691bfea9ff00c05d55337d86c330ad19e GIT binary patch literal 1284 zcma)+&2AGh5XbjBn>3*aC@Kd~ugjrAEf-LQXo`B^5CMftSZTFgyXl6_dNp=JgVIyE z@(%40i6`=v3w;Gn%&gPUR6@d%?b(Svp5Nc&bUU4vh3)CvAIY!4vVPLXRddjIiCymC zz$|7L*2wPLw#6Lg7S71+yJ&mN|7!I;7O)1+J_}hB=YX}?3eFAIKCq(BB}T9!Tel}U z|GdqTvC0d1X&dM2VZlqrA(?QHxhOO8Q=^%Z*uySw;V`p?)a{{j!2}@O~o5q)HGdwd)GEpjiqP|>%u+tlzr|R%9(dTjCkAL zHL&~hJI2E?g$d7;0L)FVFZr+0M}7^g1I+gd7^tfB%A_Y?lot6H&i;vg1CkD1R)RuF zf+Or(4%|a)0Z#q}_4#ENY9TDF@wTypjV*tK%r>dr78F;0~^-3#rx>?$VaP#Js6%2Ln@FDxPh&hASnNqtOo4 zdx&-~3U%wSnkM*oA(f_}1EG%b?bi7i7^<*Z?RwL-gQyUpF5YlwB-ps%FX%5tlB;dE K-OvmDu<-| 1 and 1 -> 0. + +def toggleBit(int_type, offset): + mask = 1 << offset + return(int_type ^ mask) + +def bitStringToInt(s): + return int(s, 2) + +def intToBinString(s): + return str(s) if s<=1 else bin(s>>1) + str(s&1) + +def bitIsOn(int_type, offset): + mask = 1 << offset + return (int_type & mask) != 0 diff --git a/atelier/utils/cursor.py b/atelier/utils/cursor.py new file mode 100644 index 0000000..1a1989f --- /dev/null +++ b/atelier/utils/cursor.py @@ -0,0 +1,31 @@ +from enum import Enum + +class CursorIcon(Enum): + DEFAULT = 'DEFAULT' + NONE = 'NONE' + WAIT = 'WAIT' + CROSSHAIR = 'CROSSHAIR' + MOVE_X = 'MOVE_X' + MOVE_Y = 'MOVE_Y' + KNIFE = 'KNIFE' + TEXT = 'TEXT' + PAINT_BRUSH = 'PAINT_BRUSH' + PAINT_CROSS = 'PAINT_CROSS' + HAND = 'HAND' + SCROLL_X = 'SCROLL_X' + SCROLL_Y = 'SCROLL_Y' + EYEDROPPER = 'EYEDROPPER' + DOT = 'DOT' + ERASER = 'ERASER' + +class Cursor: + @staticmethod + def set_icon(context, cursor = CursorIcon.DEFAULT): + context.window.cursor_modal_set(cursor.value) + #for wm in bpy.data.window_managers: + # for win in wm.windows: + # win.cursor_modal_set(cursor) + + @staticmethod + def wrap(context, x, y): + context.window.cursor_warp(x, y) \ No newline at end of file diff --git a/atelier/utils/curves.py b/atelier/utils/curves.py new file mode 100644 index 0000000..cccb4a9 --- /dev/null +++ b/atelier/utils/curves.py @@ -0,0 +1,160 @@ +import bpy +from mathutils.geometry import interpolate_bezier +from math import * +from numpy import linspace + + +def convert_handles_type(_points, _handleType = 'AUTO'): + for p in _points: + p.handle_left_type = _handleType + p.handle_right_type = _handleType + +def close_spline(_spline): + if _spline.use_cyclic_u: + _spline.use_cyclic_u = False + _spline.use_cyclic_v = False + else: + _spline.use_cyclic_u = True + _spline.use_cyclic_v = True + # bpy.ops.curve.cyclic_toggle() + +def interpolate_bezier(curve): + spline = curve.splines[0] + + if len(spline.bezier_points) >= 2: + r = spline.resolution_u + 1 + segments = len(spline.bezier_points) + if not spline.use_cyclic_u: + segments -= 1 + + points = [] + for i in range(segments): + inext = (i + 1) % len(spline.bezier_points) + + knot1 = spline.bezier_points[i].co + handle1 = spline.bezier_points[i].handle_right + handle2 = spline.bezier_points[inext].handle_left + knot2 = spline.bezier_points[inext].co + + _points = interpolate_bezier(knot1, handle1, handle2, knot2, r) + points.extend(_points) + return points + return None + + +def interpolate_3d_cursor_in_curve(curve_object, t): + def interpBez3(bp0, t, bp3): + # bp1, HR, HL, bp2 + + return interpBez3_(bp0.co, bp0.handle_right, bp3.handle_left, bp3.co, t) + # return interpBez3_(bp0.co, bp0.handle_left, bp3.handle_right, bp3.co, t) + + def interpBez3_(p0, p1, p2, p3, t): + r = 1-t + return (r*r*r*p0 + + 3*r*r*t*p1 + + 3*r*t*t*p2 + + t*t*t*p3) + + def interpBlenderSpline(spline, i1, t2): + + bp1 = spline.bezier_points[i1] + bp2 = spline.bezier_points[i1+1] + return interpBez3(bp1, t2, bp2) + + def mission1(obj, t): + """ mathematically correct spline interpolation """ + + i1 = floor(t) + + curve = obj.data + + bpy.context.scene.cursor.location = obj.matrix_world @ interpBlenderSpline(curve.splines[0], i1, t-i1) + + + def mission2(obj, t): + """ spline interpolation that matches the resolution_u setting inside blender """ + i1 = floor(t) + t2 = t - i1 + spline = obj.data.splines[0] + + res = obj.data.render_resolution_u or obj.data.resolution_u + t3 = t2*res + t3i = floor(t3) / res + t3f = t3 - floor(t3) + + p8 = interpBlenderSpline(spline, i1, t3i) + p9 = interpBlenderSpline(spline, i1, t3i + 1/res) + + p = (1-t3f)*p8 + t3f*p9 + + bpy.context.scene.cursor_location = obj.matrix_world * p + + mission1(curve_object, t) + + +def interpolate_curve(curve_object, t): + def interpBez3(bp0, t, bp3): + # bp1, HR, HL, bp2 + + return interpBez3_(bp0.co, bp0.handle_right, bp3.handle_left, bp3.co, t) + # return interpBez3_(bp0.co, bp0.handle_left, bp3.handle_right, bp3.co, t) + + def interpBez3_(p0, p1, p2, p3, t): + r = 1-t + return (r*r*r*p0 + + 3*r*r*t*p1 + + 3*r*t*t*p2 + + t*t*t*p3) + + def interpBlenderSpline(spline, i1, t2): + + bp1 = spline.bezier_points[i1] + bp2 = spline.bezier_points[i1+1] + return interpBez3(bp1, t2, bp2) + + def mission1(obj, t): + """ mathematically correct spline interpolation """ + + i1 = floor(t) + + curve = obj.data + + return obj.matrix_world @ interpBlenderSpline(curve.splines[0], i1, t-i1) + + + def mission2(obj, t): + """ spline interpolation that matches the resolution_u setting inside blender """ + i1 = floor(t) + t2 = t - i1 + spline = obj.data.splines[0] + + res = obj.data.render_resolution_u or obj.data.resolution_u + t3 = t2*res + t3i = floor(t3) / res + t3f = t3 - floor(t3) + + p8 = interpBlenderSpline(spline, i1, t3i) + p9 = interpBlenderSpline(spline, i1, t3i + 1/res) + + p = (1-t3f)*p8 + t3f*p9 + + bpy.context.scene.cursor_location = obj.matrix_world * p + + return mission1(curve_object, t) + + +def get_bezier_curve_points(curve_object, ratio=0.01): + num = len(curve_object.data.splines[0].bezier_points) + pairs = int(num / 2.0) + loops = int(1.0 / ratio) + + points = [] + for k in range(0, pairs): + factors = linspace(k, k + 1, num=loops) + for l in range(0, loops): + t = float(factors[l]) + if k == (pairs - 1) and t >= k + 1: + break + points.append(interpolate_curve(curve_object, t)) + return points diff --git a/atelier/utils/draw2d.py b/atelier/utils/draw2d.py new file mode 100644 index 0000000..6ca6295 --- /dev/null +++ b/atelier/utils/draw2d.py @@ -0,0 +1,906 @@ +from gpu_extras.batch import batch_for_shader +from gpu.shader import from_builtin +from gpu_extras.presets import draw_circle_2d, draw_texture_2d +from .geo2dutils import is_inside_2d_rect +from enum import Enum +from bpy.types import Image, Space, Area +from bpy.types import ( + SpaceClipEditor, SpaceConsole, + SpaceDopeSheetEditor, SpaceFileBrowser, + SpaceGraphEditor, SpaceImageEditor, + SpaceInfo, SpaceNLA, SpaceNodeEditor, + SpaceOutliner, SpacePreferences, + SpaceProperties, SpaceSequenceEditor, + SpaceTextEditor, SpaceView3D +) +import uuid +import bpy +import bgl +import blf +''' +import mouse +''' +from os.path import isfile +from string import Template +from ..utils.others import override_context + + +TEXT_DPI_DEFAULT = 72 +TEXT_SHADOW_BLUR_LEVELS = [0, 3, 5] + + +# BUILT-IN SHADERS +shader_2d_image = from_builtin('2D_IMAGE') +shader_2d_color_unif = from_builtin('2D_UNIFORM_COLOR') +shader_2d_color_flat = from_builtin('2D_FLAT_COLOR') +shader_2d_color_smooth = from_builtin('2D_SMOOTH_COLOR') + + +class Color(Enum): + Blender_Theme = None + # BASIC + Black = (0, 0, 0, 1) + White = (1, 1, 1, 1) + Red = (1, 0, 0, 1) + Green = (0, 1, 0, 1) + Blue = (0, 0, 1, 1) + # CYAN DERIVATES + Cyan = (0, 1, 1, 1) + Aquamarine = (.498, 1, .831, 1) + Turquoise = (.251, .878, .816, 1) + # ORANGE DERIVATES + Orange = (1, .647, 0, 1) + Orange_Dark = (1, .549, 0, 1) + Orange_Red = (1, .27, 0, 1) + Gold = (1, .843, 0, 1) + Coral = (1, .498, .314, 1) + Tomato = (1, .388, .278, 1) + + +def rectangle_points(x, y, w, h): + return ( + (x, y), + (x+w, y), + (x, y+h), + (x+w, y+h) + ) + + +rectangle_indices = ( + (0, 1, 2), + (2, 1, 3) +) + + +def Draw_2D_Rectangle(_posX, _posY, _width, _height, _color=[0, 0.5, 0.5, 1.0], _shader=shader_2d_color_unif): + batch = batch_for_shader(_shader, 'TRIS', {"pos": rectangle_points( + _posX, _posY, _width, _height)}, indices=rectangle_indices) + _shader.bind() + _shader.uniform_float("color", _color) + bgl.glEnable(bgl.GL_BLEND) + batch.draw(_shader) + bgl.glDisable(bgl.GL_BLEND) + + +def Draw_2D_Lines(coords=[(0, 0, 0), (1, 1, 1)], _color=(1, 1, 0, 1), _shader=shader_2d_color_unif): + batch = batch_for_shader(_shader, 'LINES', {"pos": coords}) + _shader.bind() + _shader.uniform_float("color", _color) + batch.draw(_shader) + + +def Draw_2D_Line(_p1, _p2, _color=(0, 0, 0, .8), _shader=shader_2d_color_unif): + coords = [_p1, _p2] + batch = batch_for_shader(_shader, 'LINES', {"pos": coords}) + _shader.bind() + _shader.uniform_float("color", _color) + batch.draw(_shader) + +def Draw_2D_Point(_pos, _color = (0, 0, 0, .8), _shader = shader_2d_color_unif): + coords = [_pos] + batch = batch_for_shader(_shader, 'POINTS', {"pos": coords}) + _shader.bind() + _shader.uniform_float("color", _color) + batch.draw(_shader) + +def Draw_2D_Points(coords, color=(1, .55, .15, .85), _shader=shader_2d_color_unif): + batch = batch_for_shader(_shader, 'POINTS', {"pos": coords}) + _shader.bind() + _shader.uniform_float("color", color) + batch.draw(_shader) + +def Draw_Text(_x, _y, _text, _size, _font_id=0, _r=1, _g=1, _b=1, _a=1): + blf.color(_font_id, _r, _g, _b, _a) + blf.position(_font_id, _x, _y, 0) # -6/-6 para "o" # + blf.size(_font_id, _size, 72) + blf.draw(_font_id, _text) + + +def Draw_2D_Circle(_center, _radius, _segments=32, _color=(0, 0, 0, .8)): + draw_circle_2d(_center, _color, _radius, _segments) + + +register_menu_template = """ +import bpy + +class ${name}(bpy.types.Menu): + # label is displayed at the center of the pie menu. + bl_label = "$label" + + def draw(self, context): + layout = self.layout + layout.operator('render.render') + +bpy.utils.register_class(${name}) +""" + +unregister_menu_template = """from bpy.utils import unregister_class +from bpy.types import ${name} +unregister_class(${name}) +""" + +register_pie_menu_template = """ +import bpy + +class ${name}(bpy.types.Menu): + # label is displayed at the center of the pie menu. + bl_label = "$label" + + def draw(self, context): + layout = self.layout + pie = layout.menu_pie() + pie.operator_enum("mesh.select_mode", "type") + +bpy.utils.register_class(${name}) +""" + +unregister_pie_menu_template = """from bpy.utils import unregister_class +from bpy.types import ${name} +unregister_class(${name}) +""" + +register_panel_template = """ +import bpy + +class ${name}(bpy.types.Panel): + # label is displayed at the center of the pie menu. + bl_label = "$label" + + def draw(self, context): + layout = self.layout + layout.operator_enum("mesh.select_mode", "type") + +bpy.utils.register_class(${name}) +""" + +unregister_panel_template = """from bpy.utils import unregister_class +from bpy.types import ${name} +unregister_class(${name}) +""" + + +class MenuTypes(Enum): + Default = ("VIEW3D_MT_", register_menu_template, unregister_menu_template) + Pie = ("VIEW3D_MT_PIE_", register_pie_menu_template, unregister_pie_menu_template) + Panel = ("VIEW3D_PT_POPOVER_", register_panel_template, unregister_panel_template) + + +class Menu(): + def __init__(self, item, menu_type=MenuTypes.Default): + self.item = item + self.menu_type = menu_type + self.name = self.menu_type.value[0] + self.item.name.lower() + "_context_menu" + code = Template(self.menu_type.value[1]).substitute(name=self.name, label=self.item.name) + print(code) + exec(code) + + def __call__(self, area_type, region_type): + print("MENU::call") + if self.menu_type == MenuTypes.Pie: + print("PIE") + bpy.ops.wm.call_menu_pie(override_context(area_type, region_type), name=self.name) + elif self.menu_type == MenuTypes.Default: + print("DEFAULT") + bpy.ops.wm.call_menu(override_context(area_type, region_type), name=self.name) + #clase = eval("bpy.types."+self.name) + #bpy.context.window_manager.popup_menu(clase.draw, title="Greeting", icon='INFO') + + def __del__(self): + try: + exec(Template(menu_type.value[2]).substitute(classname=self.name)) + except ImportError as e: + print("Menu class couldn't be found.", e) + except NameError as e: + print("Menu class name in not valid.", e) + + +class Label(): + def __init__(self, text, pos=[0, 0], size=14, color=Color.Black): + self.text = text + self.x, self.y = pos + self.size = size + self.color = list(color.value) if isinstance(color, Color) else list(color) + self.context_menu = None + + def get_rgba(self): + co = self.color + return co[0], co[1], co[2], co[3] + + def get_text_dimensions(self): + blf.size(0, self.size, TEXT_DPI_DEFAULT) + return blf.dimensions(0, self.text) + + def set_alpha(self, alpha_value=1): + self.color[3] = alpha_value + + def set_text(self, text): + self.text = text + + def draw(self): + Draw_Text(self.x, self.y, self.text, self.size, 0, *self.get_rgba()) + + def draw_off(self, off_x, off_y): + Draw_Text(self.x + off_x, self.y + off_y, self.text, self.size, 0, *self.get_rgba()) + + def serialize(self): + return { + 'text': self.text, + 'pos': tuple(self.x, self.y), + 'size': self.size, + 'color': tuple(self.color) + } + + +class Alignment(Enum): + Left = 0, + Center = 1, + Right = 2, + Bottom = 3, + Top = 4, + Bottom_Left = 5, + Bottom_Right = 6, + Top_Left = 7, + Top_Right = 8, + + +class Icon(): + def __init__(self, image, pos, size): + self.image = image + self.x, self.y = pos + self.pos = pos + self.width, self.width = size + self._activate_image() + self.name = self.image.name if self.image else "" + self.path = self.image.filepath if self.image else "" + self.context_menu = None + + def load_image_from_path(self, image_path): + if not isfile(image_path): + return None + self.image = bpy.data.images.load(image_path, check_existing=False) + self.name = self.image.name + self.path = image_path + self._activate_image() + + def load_image_from_data(self, image_name): + self.image = bpy.data.images.get(image_name, None) + if not self.image: + return None + self.name = image_name + self.path = self.image.filepath + self._activate_image() + + def _activate_image(self): + if self.image and isinstance(self.image, Image): + self.image.gl_load() + + def draw(self): + self.image.gl_touch() + draw_texture_2d(self.image.bindcode, self.pos, self.width, self.height) + + def serialize(self): + return { + 'name': self.name, + 'path': self.path, + 'pos': tuple(self.x, self.y), + 'size': tuple(self.width, self.height) + } + + def __del__(self): + if self.image: + self.image.gl_free() + self.image.buffers_free() + + +class ToggleGroup(): + def __init__(self): + self.buttons = [] + self.id = str(uuid.uuid4()) + + def set_active(self, target, state): + target.is_active = state + + def deactivate_buttons(self): + for b in self.buttons: + self.set_active(False) + + def toggle(self, target): + self.deactivate_buttons() + self.set_active(target, True) + + def add_button(self, button): + if isinstance(button, Button): + self.buttons.append(button) + button.toggle_group = self + + def add_buttons(self, buttons): + if isinstance(buttons, list): + for b in buttons: + self.add_button(b) + + def remove_button(self, button): + if isinstance(button, int): + self.buttons.pop(button) + elif isinstance(button, Button): + self.buttons.remove(button) + + +class Button(): + buttons = [] + + def __init__(self, pos, size, color, on_hover_color, fun, label=None, icon=None): + self.id = uuid.uuid4().__str__() + self.name = "Button_" + str(len(Button.buttons)) + self.x, self.y = pos + self.width, self.height = size + self.color = list(color.value) if isinstance(color, Color) else list(color) + self.color_on_hover = list(on_hover_color.value) if isinstance(on_hover_color, Color) else list(on_hover_color) + self.on_click = fun + self.on_click_args = {} + self.is_active = False + self.on_hover = False + self.label = label + self.icon = icon + self.context_menu = Menu(self) + self.toggle_group = None + #self._parent = None + + Button.buttons.append(self) + + def set_label(self, label): + if isinstance(label, Label): + return None + self.label = label + + def set_icon(self, icon): + if isinstance(icon, Icon): + return None + self.icon = icon + + def set_context_menu(self, context_menu): + if isinstance(context_menu, Menu): + return None + self.context_menu = context_menu + + # def _set_parent(self, parent): + # if isinstance(parent, Canvas): + # self._parent = parent + + def add_label(self, text='Text', size=14, alignment=Alignment.Center, color=(0, 0, 0, 1)): + if self.label or not isinstance(alignment, Alignment): + return None + self.label = Label(text, [0, 0], size, color) + width, height = self.label.get_text_dimensions() + if alignment == Alignment.Center: + self.label.x = int(width / 2.0) - 2 + self.label.y = int(self.height / 2.0 - height / 2.0) + return self.label + + def add_icon(self, image, pos, size): + if self.icon: + return None + self.icon = Icon(image, pos, size) + + def add_icon_from_data(self, image_name, pos, size): + add_icon(None, pos, size) + self.icon.load_image_from_data(image_name) + + def add_icon_from_path(self, image_path, pos, size): + add_icon(None, pos, size) + self.icon.load_image_from_path(image_path) + + def set_alpha(self, alpha_value=1): + self.color[3] = self.color_on_hover[3] = alpha_value + return self + + def move(self, pos): + self.x = int(pos[0] - self.width / 2.0) + self.y = int(pos[1] - self.height / 2.0) + + def draw(self): + Draw_2D_Rectangle( + self.x, self.y, + self.width, self.height, + self.color if not self.is_active and not self.on_hover else self.color_on_hover + ) + if self.icon: + self.image.draw_off(self.x, self.y) + if self.label: + self.label.draw_off(self.x, self.y) + + def __call__(self): + #print("Button::call", self) + try: + self.on_click() + except Exception as e: + print(e) + finally: + if self.toggle_group: + self.toggle_group.toggle(self.index_toggle_group) + + def evaluate(self): + Button.deactivate_buttons() + self.set_active(True) + eval(self.on_click)() + + def serialize(self): + return { + 'id': self.id, + 'toggle_group_index': self.toggle_group.id if self.toggle_group else None, + 'pos': tuple(self.x, self.y), + 'size': tuple(self.width, self.height), + 'color': tuple(self.color), + 'color_on_hover': tuple(self.color_on_hover), + 'on_click': self.on_click, # TODO: ops, looks for a string representation of this code maybe? + 'label': self.label.serialize(), + 'icon': self.icon.serialize() + } + + +class Space(Enum): + VIEW_3D = bpy.types.SpaceView3D + IMAGE_EDITOR = bpy.types.SpaceImageEditor + NODE_EDITOR = bpy.types.SpaceNodeEditor + SEQUENCE_EDITOR = bpy.types.SpaceSequenceEditor + CLIP_EDITOR = bpy.types.SpaceClipEditor + DOPESHEET_EDITOR = bpy.types.SpaceDopeSheetEditor + GRAPH_EDITOR = bpy.types.SpaceGraphEditor + NLA_EDITOR = bpy.types.SpaceNLA + TEXT_EDITOR = bpy.types.SpaceTextEditor + CONSOLE = bpy.types.SpaceConsole + INFO = bpy.types.SpaceInfo + OUTLINER = bpy.types.SpaceOutliner + PROPERTIES = bpy.types.SpaceProperties + FILE_BROWSER = bpy.types.SpaceFileBrowser + PREFERENCES = bpy.types.SpacePreferences + + +class Region(Enum): + WINDOW = 'WINDOW' + HEADER = 'HEADER' + TOOL_HEADER = 'TOOL_HEADER' + UI = 'UI' + TOOLS = 'TOOLS' + TOOL_PROPS = 'TOOL_PROPS' + FOOTER = 'FOOTER' + CHANNELS = 'CHANNELS' + TEMPORARY = 'TEMPORARY' + PREVIEW = 'PREVIEW' + HUD = 'HUD' + NAVIGATION_BAR = 'NAVIGATION_BAR' + EXECUTE = 'EXECUTE' + +''' +class Canvas(): + def __init__(self, space_type=Space.VIEW_3D, region_type=Region.WINDOW, win_x=0, win_y=0): + print("CANVAS::Init") + self.buttons = [] + self.labels = [] + self.images = [] + self.custom = [] + self.space = space_type.value + self.region_type = region_type.value + self.area_type = str(space_type).split('.')[1] + self.draw_type = 'POST_PIXEL' + self._handler = None + self.item_on_hover = None + self.win_x = win_x + self.win_y = win_y + self.has_modal = False + self.mouse_pos = [-1, -1] + self.dragging = False + + def use_for_custom_operator(self): + CanvasOperator.canvas = self + return self + + def start(self, args=()): + if not self.space: + return None + if not isinstance(args, tuple): + return None + if self._handler: + return None + print("CANVAS::Start") + if not args: + args = (bpy.context,) + self._handler = self.space.draw_handler_add(self.draw, args, self.region_type, self.draw_type) + if not self.has_modal: + mouse.on_click(self.event, args=()) + mouse.on_right_click(self.context_menu, args=()) + #mouse.on_middle_click(self.drag_item, args=()) + mouse.on_button(self.drag_item, args=(), buttons=('middle'), types=('down')) + return self + + def stop(self): + if not self._handler: + return None + print("CANVAS::Stop") + self.space.draw_handler_remove(self._handler, self.region_type) + #del self._handler + + def new_button(self, pos, size, color, on_hover_color, fun, label=None, icon=None): + return self.add_button(Button(pos, size, color, on_hover_color, fun, label, icon)) + + def add_button(self, button): + if isinstance(button, Button): + self.buttons.append(button) + # button._set_parent(self) + return button + + def new_image(self, image, pos, size): + return self.add_image(Icon(image, pos, size)) + + def add_image(self, image): + if isinstance(button, Image): + self.images.append(image) + return image + + def new_label(self, text, pos, size, color): + return self.add_label(Label(text, pos, size, color)) + + def add_label(self, label): + if isinstance(button, Label): + self.labels.append(label) + return label + + def check_on_hover(self, item, mouse): + return is_inside_2d_rect(mouse, item.x, item.y, item.width, item.height) + + # Limite drawing canvas to the target area you specify. + # Use area type or context to specify the area target. None to use all them. + + def lock_to_area(self, area_target): + # Area. + if isinstance(area_target, Area) and area_target.type == self.area_type: + self.area = area_target + # Context. + elif hasattr(area_target, "area"): + if area_target.area.type == self.area_type: + self.area = area_target.area + # None. + elif area_target is None and hasattr(self, "area"): + del self.area + return self + + def event(self): + if not self.item_on_hover: + print("CANVAS::Nothing to click") + return False + #bpy.ops.fake.click_op(override_context(self.area_type, self.region_type), 'INVOKE_DEFAULT') + print("CANVAS::ON CLICK!") + self.item_on_hover() + return True + + def context_menu(self): + if not self.item_on_hover: + return False + if not self.item_on_hover.context_menu: + return False + print("CANVAS::Context Menu") + self.item_on_hover.context_menu(self.area_type, self.region_type) + return True + + def drag_item(self): + if not self.item_on_hover: + return False + print("CANVAS::Drag") + self.dragging = True + while mouse.is_pressed(button=mouse.MIDDLE): + self.drag() + self.dragging = False + return True + + def drag(self): + self.item_on_hover.move(self.mouse_pos) + + def draw(self, context): + if hasattr(self, "area"): + if self.area != context.area: + return + if not self.has_modal: + # self.event() + # NOTE: We are supposing all screens are 16:9_1080p. + # Get mouse position. + mouse_pos = list(mouse.get_position()) + # Invert Y axis. + mouse_pos[1] -= 1080 # if using event from modal this should be skipped. + # Apply editor/space window offset. + mouse_pos[0] -= self.win_x + mouse_pos[1] += self.win_y # if using event from modal this should be -=. + # Detects if it's a second monitor. + if mouse_pos[0] > 1920: + mouse_pos[0] -= 1920 # TODO: multiply per int(mouse_pos[0] / 1920) if > 2 screens. + mouse_pos[0] = abs(mouse_pos[0]) + mouse_pos[1] = abs(mouse_pos[1]) + #print(mouse_pos[0], mouse_pos[1]) + self.mouse_pos = mouse_pos + print(self.mouse_pos) + for i in self.images: + i.draw() + if not self.dragging: + self.item_on_hover = None + for b in self.buttons: + if self.check_on_hover(b, self.mouse_pos): + #print("ON HOVER!") + b.on_hover = True + self.item_on_hover = b + else: + b.on_hover = False + b.draw() + for t in self.labels: + t.draw() + for c in self.custom: + c.draw() + + @staticmethod + def serialize_list(list): + serialized_items = [] + for item in self.list: + serialized_items.append(item.serialize()) + return serialized_items + + def serialize(self): + return { + # 'id' : self.id, + 'space_type': self.space_type, + 'space': self.space, + 'region_type': self.region_type, + 'draw_type': self.draw_type, + 'buttons': Canvas.serialize_list(self.buttons), + 'images': Canvas.serialize_list(self.images), + 'labels': Canvas.serialize_list(self.labels) + } + + def __del__(self): + self.stop() + + +class CanvasModal(Canvas): + class Modal(bpy.types.Operator): + bl_idname = "canvas.modal" + bl_label = "" + + canvas = None + stop = False + + def execute(self, context): + print("EXECUTE") + self.dragging = False + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + def modal(self, context, event): + # print("MODAL") + self.canvas.mouse_pos = [event.mouse_region_x, event.mouse_region_y] + if self.dragging: + print("MODAL::Dragging...") + if event.type == 'MIDDLEMOUSE' and event.value == 'RELEASE': + self.dragging = False + return {'PASS_THROUGH'} + self.canvas.drag_item() + if event.type == 'LEFTMOUSE' and event.value == 'PRESS': + if self.canvas.event(): + print("LEFT!") + return {'RUNNING_MODAL'} + elif event.type == 'RIGHTMOUSE' and event.value == 'PRESS': + if self.canvas.context_menu(): + print("RIGHT!") + return {'RUNNING_MODAL'} + elif event.type == 'MIDDLEMOUSE' and event.value == 'PRESS': + if self.canvas.item_on_hover: + print("MIDDLE!") + self.dragging = True + return {'RUNNING_MODAL'} + elif event.type == 'ESC': + print("ESC!") + self.canvas.stop() + return {'FINISHED'} + return {'PASS_THROUGH'} + + def __init__(self, space_type=Space.VIEW_3D, region_type=Region.WINDOW): + self.buttons = [] + self.labels = [] + self.images = [] + self.custom = [] + self.space = space_type.value + self.region_type = region_type.value + self.area_type = str(space_type).split('.')[1] + self.draw_type = 'POST_PIXEL' + self._handler = None + self.item_on_hover = None + self.has_modal = True + self.mouse_pos = [-1, -1] + self.dragging = False + + self.Modal.canvas = self + bpy.utils.register_class(CanvasModal.Modal) + self._modal = bpy.ops.canvas.modal + + def start(self, args=()): + super().start(args) + self._modal() + + def stop(self): + super().stop() + self.Modal.stop = True + self.Modal.canvas = None + del self + + def drag_item(self): + print("CANVAS::Drag Item...") + self.item_on_hover.move(self.mouse_pos) + + def __del__(self): + super().__del__() + del self._modal + bpy.utils.unregister_class(CanvasModal.Modal) + + +canvas_operator_property_group = """ +from bpy.types import PropertyGroup, WindowManager as wm +from bpy.utils import register_class +from bpy.props import PointerProperty +class PG_${name}(PropertyGroup): + modal = [] + canvas = [] +register_class(PG_${name}) +wm.${name} = PointerProperty(type=PG_${name}) +""" + + +class CanvasOperator(Canvas, bpy.types.Operator): + bl_idname = "canvas.operator" + bl_label = "" + + canvas = Canvas() + + # def __init__(self, space_type=Space.VIEW_3D, region_type=Region.WINDOW, win_x=0, win_y=0): + # super().__init__(space_type, region_type, 0, 0) + + def execute(self, context): + print("EXECUTE") + #self.index = len(pgs) + #code = Template(canvas_operator_property_group).substitute(name="CanvasOperator_"+str(self.index)) + # exec(code) + # self.pgs.append() + if CanvasOperator.canvas is None: + print("No canvas on operator") + return {'FINISHED'} + + self.canvas = CanvasOperator.canvas # self.__init__() + CanvasOperator.canvas = None + self.canvas.has_modal = True + self.canvas.start() + + self.dragging = False + self.area = context.area + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + def modal(self, context, event): + # print("MODAL") + if not self.canvas: + return {'FINISHED'} + if self.area != context.area: + self.canvas.stop() + return {'FINISHED'} + self.area.tag_redraw() + self.canvas.mouse_pos = [event.mouse_region_x, event.mouse_region_y] + if self.dragging: + print("MODAL::Dragging...") + if event.type == 'MIDDLEMOUSE' and event.value == 'RELEASE': + self.dragging = False + return {'RUNNING_MODAL'} + self.canvas.drag() + return {'PASS_THROUGH'} + elif event.type == 'LEFTMOUSE' and event.value == 'PRESS': + if self.canvas.event(): + print("LEFT!") + return {'RUNNING_MODAL'} + elif event.type == 'RIGHTMOUSE' and event.value == 'PRESS': + if self.canvas.context_menu(): + print("RIGHT!") + return {'RUNNING_MODAL'} + elif event.type == 'MIDDLEMOUSE' and event.value == 'PRESS': + if self.canvas.item_on_hover: + print("MIDDLE!") + self.dragging = True + return {'RUNNING_MODAL'} + elif event.type == 'ESC': + print("ESC!") + self.canvas.stop() + return {'FINISHED'} + return self.loop(context, event) + + def loop(self, context, event): + return {'PASS_THROUGH'} +''' + +def Draw_Texture(_bincode, _pos, _width, _height): + draw_texture_2d(_bincode, _pos, _width, _height) + +def Draw_Image_Texture(_image, _pos, _width, _height): + try: + if _image.gl_load(): + raise Exception() + except: + pass + draw_texture_2d(_image.bindcode, _pos, _width, _height) + +shader_img = shader_2d_image +def Draw_Image(image, _pos, _width, _height, _use_transparency = False, _flipX = False, _flipY = False): + off_x, off_y = _pos + if image.gl_load(): + raise Exception() + # bottom left, top left, top right, bottom right + if _flipX: + vertices = ( + (off_x + _width, _height + off_y), + (off_x + _width, off_y), + (off_x, off_y), + (off_x, _height + off_y) + ) + elif _flipY: + vertices = ( + (off_x, off_y), + (off_x, _height + off_y), + (off_x + _width, _height + off_y), + (off_x + _width, off_y) + ) + else: + vertices = ( + (off_x, _height + off_y), + (off_x, off_y), + (off_x + _width, off_y), + (off_x + _width, _height + off_y) + ) + batch_img = batch_for_shader(shader_img, 'TRI_FAN', { "pos" : vertices, "texCoord": ((0, 1), (0, 0), (1, 0), (1, 1)) },) + #if _image is not None: + try: + if _use_transparency: + bgl.glEnable(bgl.GL_BLEND) + # TRANSPARENCIA + bgl.glBlendFunc(bgl.GL_SRC_COLOR, bgl.GL_ONE) + #bgl.glBlendFunc(bgl.GL_SRC_COLOR, bgl.GL_ONE_MINUS_SRC1_ALPHA) # GL_DST_ALPHA # GL_ONE_MINUS_CONSTANT_ALPHA # GL_SRC_ALPHA # GL_ONE + # AUMENTA CONTRASTE Y SATURACION + #bgl.glBlendFunc(bgl.GL_ONE_MINUS_CONSTANT_ALPHA, bgl.GL_SRC_COLOR) + # bgl.glBlendFunc(bgl.GL_ONE, bgl.GL_SRC_COLOR) + # bgl.glBlendFunc(bgl.GL_SRC_COLOR, bgl.GL_CONSTANT_ALPHA) + if image.ref.in_front: + bgl.glDisable(bgl.GL_DEPTH_TEST) # DELANTE + else: + bgl.glEnable(bgl.GL_DEPTH_TEST) # DETRÁS + bgl.glActiveTexture(bgl.GL_TEXTURE0) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, image.bindcode) + #bgl.glColor4ub(0,0,0, 120) + #bgl.glTexEnvf(bgl.GL_TEXTURE_2D, bgl.GL_SOURCE0_ALPHA, bgl.GL_TEXTURE0) + #bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA) + shader_img.bind() + shader_img.uniform_int("image", 0) + batch_img.draw(shader_img) + bgl.glDisable(bgl.GL_BLEND) + bgl.glDisable(bgl.GL_DEPTH_TEST) + return True + except: + return False diff --git a/atelier/utils/draw3d.py b/atelier/utils/draw3d.py new file mode 100644 index 0000000..b3ab659 --- /dev/null +++ b/atelier/utils/draw3d.py @@ -0,0 +1,54 @@ +import bpy +import gpu +from gpu.types import GPUShader +from gpu_extras.batch import batch_for_shader +from gpu.shader import from_builtin + + +vertex_shader_3d_dotted_line = ''' + uniform mat4 u_ViewProjectionMatrix; + + in vec3 position; + + void main() + { + gl_Position = u_ViewProjectionMatrix * vec4(position, 1.0f); + } +''' + +fragment_shader_3d_dotted_line = ''' + uniform float u_Scale; + + void main() + { + if (sin(u_Scale) > 0.5) discard; + gl_FragColor = vec4(1.0); + } +''' + +shader_3d_dotted_line = GPUShader(vertex_shader_3d_dotted_line, fragment_shader_3d_dotted_line) +shader_3d_uniform_color = from_builtin('3D_UNIFORM_COLOR') + + +def Draw_3D_DottedLine(context, _shader = shader_3d_dotted_line): + batch = batch_for_shader( + _shader, 'LINE_STRIP', + {"position": coords, "arcLength": arc_lengths}, + ) + _shader.bind() + _shader.uniform_float("u_ViewProjectionMatrix", context.region_data.perspective_matrix) + _shader.uniform_float("u_Scale", 10) + batch.draw(_shader) + + +def Draw_3D_Lines(coords=[(0,0,0), (1, 1, 1)], _shader = shader_3d_uniform_color): + batch = batch_for_shader(_shader, 'LINES', {"pos": coords}) + _shader.bind() + _shader.uniform_float("color", (1, 1, 0, 1)) + batch.draw(_shader) + +def Draw_3D_Points(coords, color = (1, .55, .15, .85), _shader = shader_3d_uniform_color): + batch = batch_for_shader(_shader, 'POINTS', {"pos": coords}) + _shader.bind() + _shader.uniform_float("color", color) + batch.draw(_shader) diff --git a/atelier/utils/easing/__init__.py b/atelier/utils/easing/__init__.py new file mode 100644 index 0000000..00e4981 --- /dev/null +++ b/atelier/utils/easing/__init__.py @@ -0,0 +1,37 @@ +from .easing import ( + LinearInOut, + QuadEaseInOut, + QuadEaseIn, + QuadEaseOut, + CubicEaseInOut, + CubicEaseIn, + CubicEaseOut, + QuarticEaseInOut, + QuarticEaseIn, + QuarticEaseOut, + QuinticEaseInOut, + QuinticEaseIn, + QuinticEaseOut, + SineEaseInOut, + SineEaseIn, + SineEaseOut, + CircularEaseIn, + CircularEaseInOut, + CircularEaseOut, + ExponentialEaseInOut, + ExponentialEaseIn, + ExponentialEaseOut, + ElasticEaseIn, + ElasticEaseInOut, + ElasticEaseOut, + BackEaseIn, + BackEaseInOut, + BackEaseOut, + BounceEaseIn, + BounceEaseInOut, + BounceEaseOut, +) + +__all__ = "LinearInOut, QuadEaseInOut, QuadEaseIn, QuadEaseOut, CubicEaseInOut, CubicEaseIn, CubicEaseOut, QuarticEaseInOut, QuarticEaseIn, QuarticEaseOut, QuinticEaseInOut, QuinticEaseIn, QuinticEaseOut, SineEaseInOut, SineEaseIn, SineEaseOut, CircularEaseIn, CircularEaseInOut, CircularEaseOut, ExponentialEaseInOut, ExponentialEaseIn, ExponentialEaseOut, ElasticEaseIn, ElasticEaseInOut, ElasticEaseOut, BackEaseIn, BackEaseInOut, BackEaseOut, BounceEaseIn, BounceEaseInOut, BounceEaseOut".split( + ", " +) diff --git a/atelier/utils/easing/__pycache__/__init__.cpython-37.pyc b/atelier/utils/easing/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9be17aad8527071f5ef9d5ac3f8b91a01e7c75a2 GIT binary patch literal 1500 zcmZXU%Zk)c5QfwDo=&H4({sOc9NaWCgNPeN#OZMcMnoA0H$@;EJ3)e}bj(H6wNKy^ z=u+^B&Ps6QE4cDhuBUqf`KZ56RZ=HEIde_xDB^l>{mcAVSCl^_@~b9-r{WI330R6% zaTTmU1*=fS8q}~3b!)K=s-$_h5L7x4eL*A_ z(@V+>?@2OiaJ=;fDMJl~OoqM@J1A%Al#;7?oDS^-i`ZpBTFZy$E1F9Ba*7h;i%Ch{tiaA&Gt&$|M=3VxRTiKbumP(stlSPRv*9#JzJ9*{J^fF#qN`+*y zsFc*&vXEb@GyUcCgg0##I^MgWDt_*O&Y(aXOiIuoGzl$2o3KHk11wMpU4lmF5&DDy zVMrJeHVI?G7GayPL)azk5%viOghRp+;g~QX=mNYE`mPi4mT5BAHO-$&aF^By|2~{Q zGTwxC5E{=fjWgeW#v*3CUNRJWGUl%BS$3dbiZ5ZY$XR-M%D8{}@SYJa0>_U+gIU(n z3yrhLb{#vo5x=}28FA#ep^<(a!!#YwiA?j<|IFKuacRY_{e;fLkiOQ}lX~SpIcMQE literal 0 HcmV?d00001 diff --git a/atelier/utils/easing/__pycache__/easing.cpython-37.pyc b/atelier/utils/easing/__pycache__/easing.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f255fe7a41063af3f597fdd83e361009e7914a5e GIT binary patch literal 10819 zcmbtaTWlOx8J^qP>%Dk4_9nKQn{$tQNz*26leVchb`mJ6OhZ(JjFGkYY@JQl>&?ts zP|F30v=Tfa(FZO9sf9|V5(u7vH=f{uQZ7%G_6ey-u%gfh5JEgOeBVEFc4v2;tWTNM zoSAPf|M~y#{QtT9vvb*O%EET`>W`L-yDjTI0=;=SDPj*&j%6uJ*;Q-N`k190<-Tkw z_m;D019>U|lqlP`+(id?NDTuI3wD8%Y6N&hum_w{Y2dWr1aL-WfwO{#fJaphI45`* zIIngA?-HB@9#iAM)73en9XZ;8W^B;0Fay0H0P50Y4;o68K?t2KbENDd4l}5#UDz-vj)p zdJOn6!Fz$vsq?_+1@8lXTs;B&gy8+aPpS*R7X%*weoD;(&k8;WTvQi6>_xAf2z}$0y*Lzl#p19^L0+brx?Bn> zwbi*&P@Zvo8zNdP$PC%VxwHGl>KWf z8`XMaX`@l821{kJG&-EBzt~JJFIQ@n#`5wost>4&We?%EIo=`VlzwJ~wsYf=6bTPh zykUI>oQIa%&GCix#?x!{YI&_(Yn0Vx-(UA9u*a2AMo zXEPV8n)lk(k~8j7?K3#pvTmSbm_nzpWlvg#5Ia+7mVz~5z2vZAZrJ;IX1xaVy8RLw z>4v-I%CjEcbNSu`Rd*LUEk2WICaRUSN<&)5A4OeZW3l=f&nH{WM5$W8Uh=a%w@ZCy zf$MS}_W}}R+0%9kzb@nW1U#ivt$5o^lC4rJt(BLTo2ljHwRN>o<#}d#`Q}Ec8s719 z5bCr0Y^GPLr65=E60CWuZ++jCNhj3-xDaJ9Yhu_E@MfT9ym z%6ZvBOt~P-Yo?#6)XF9Q>DnhZ8toS1i*3hXMOzG6dqMO-aFI75tXDBIycc`u1NLS< zCjAthU;(U;^KGi+W3aG7Q@68n0(bAhF2a(wPi$;o_UcAS!4u2<7P^opK6pP)wrpj; z0JZ3Y@G9)rFg)pV<%Pbe5|i|?p@mhvg6&VUEppZR@b>N7EfOV7zayHC;^`<-lXk}5 z9P3iln5;>NJ3^7v2V@Z&la=b|(B%u1DNM1M&{+yQJN1YA@Q(I}T&DtK3->|Jy;>G5 zBsR7%trh5XcNfycM7l6dRMHit+1$>8KZUD1YSz?0y8req${ssxQL|QkT&rdw%44+{+j#(j&TCV_saq}cV6gT_2<0kY3 z+zaEz(=nru%I%GvmdtY~zVaVT8N$9M7CZhS+}bXBK8x$^=qcDqdvkZM_QoW#l@}>D ziXUQQ5)nU@+JN}sT(3KR%wp)eRx*sCSbfGeABQN;-L%cb#x^5{+B(GDhCH%u=UIWC zR8B^b#7Sj1r|3O?gjiZ*4XiGVrMNc6l$?N+s+NaXA~vQ3vD8-LE@RNgAv=nt>Hb(6 zG#0hvs&7ncv5UUTm~_Xu@@Y7O(n3s4a#TWGbz5Rg=t)SsNx4y65gQYlx`uVxHWN1w z!oU0 zjw;4naKip2Eep;?Y|KSkE6^KkZ8F%ygk(fS*d(Geb;s(Q%MhPI=|6yCko_;?8Et<) zR=qI+Ol95D!axAAF#%bvL3e1;znJTpr+hB>l&zdN4X?`@1~j;_>6?oOBR-JJSr2x^ z$Jbaz)ZvVEX=BU?7d2ni;=l;8F(ae%6(1R~lD|!ev?*a~PuU~{b6Q!oPQoi>1s_fJ z;Rf^zB=c_ijX^%~s#cnvZg>60Bs>IJ-_Y_v02A%)l84`=%BM28fLr*cI2JKRDR2&$EsN6w$bITn7n1L4;mWutL9( zoKNU4lJ8-*CykF2onMr9e z;TdL0qX2a4>t7h;esAHWHccLA>Qm2{rbi+2hZGypL~KmcSdSjPk=X8!rXfAfQ`9#J zQ}8W#7g;a5!c+85^dDdf($SDiLb}R}J^R9X1gv;vz30Hs&X*HRuj!GSm*|IzSZ8XTzLM_zmH1Oi2i1 zRH17zdm6HfK*iU6{q12pe(`je-0)(d+wM#IeQ_t8e?XGL!8-}ok2-m0bF9m5WA5i5 z@*fl%aZhZ_eP#}S5LwwS2gLuI%u_t4F6=>XMN8cN~tSI@|n)>q!2U48RkU;q2a;giMHH{QJQ?2o^Hrnq|PgLmHe-P?zX ztKa!Hw`0ZCSN?YO7YANBTU@<)A@%ML>c@&Z`Y!3=UmL+F6k`98$37`L{JHH*@B-s@ zWD~d)P5RM}(j=@!Z@gZ_ez$*`fBc3@Fj+-=I{@Eh`*pNRp$fth! z4lZAV{b@dOn8gtmM_C+aae~E37WcAXYEiOjl1-9?iqCbw&sD9@Wt-3CgG`BKg6DJQ zAOoj=k!N!(ge%hIQ_a}7D0cY%2#RL%$?(riPr)WDD56NZqkHlr`QdybpTeHVXZy _point[0] > _x) and ((_y + _height) > _point[1] > _y) + +def is_inside_2d_circle(_mousePos, _coords, _radius): + return distance_between(_mousePos, _coords) < _radius + +def get_nearest_2d_point(_base, _points, _minDist = 0): # POINTS SHOULD BE VECTORS (2D) + n = 0 + index = 0 + nearestPoint = None + minDist = _minDist + for point in _points: + dist = distance_between(_base, point) + if dist < minDist: + minDist = dist + nearestPoint = point + index = n + n+=1 + return nearestPoint, minDist, index # point.co & float + + +# Function to return the minimum distance +# between a line segment AB and a point E +def min_distance_line_point(A, B, E): + + # vector AB + AB = [None, None] + AB[0] = B[0] - A[0] + AB[1] = B[1] - A[1] + + # vector BP + BE = [None, None] + BE[0] = E[0] - B[0] + BE[1] = E[1] - B[1] + + # vector AP + AE = [None, None] + AE[0] = E[0] - A[0] + AE[1] = E[1] - A[1] + + # Variables to store dot product + + # Calculating the dot product + AB_BE = AB[0] * BE[0] + AB[1] * BE[1] + AB_AE = AB[0] * AE[0] + AB[1] * AE[1] + + # Minimum distance from + # point E to the line segment + reqAns = 0 + + # Case 1 + if (AB_BE > 0) : + + # Finding the magnitude + y = E[1] - B[1] + x = E[0] - B[0] + reqAns = sqrt(x * x + y * y) + + # Case 2 + elif (AB_AE < 0) : + y = E[1] - A[1] + x = E[0] - A[0] + reqAns = sqrt(x * x + y * y) + + # Case 3 + else: + + # Finding the perpendicular distance + x1 = AB[0] + y1 = AB[1] + x2 = AE[0] + y2 = AE[1] + mod = sqrt(x1 * x1 + y1 * y1) + reqAns = abs(x1 * y2 - y1 * x2) / mod + + return reqAns + +def rotate_point_around_another(o, angle, p): + s = sin(angle) + c = cos(angle) + + # translate point back to origin: + p.x -= o.x + p.y -= o.y + + # rotate point + xnew = p.x * c - p.y * s + ynew = p.x * s + p.y * c + + # translate point back: + p.x = xnew + o.x + p.y = ynew + o.y + return p + +def angle_from_vector(vector): + return atan2(vector[1], vector[0])*180/pi + +def perpendicular_vector3(v): + if v.x == 0 and v.y == 0: + if v.z == 0: + # v is Vector(0, 0, 0) + raise ValueError('zero vector') + + # v is Vector(0, 0, v.z) + return Vector(0, 1, 0) + + return Vector((-v.y, v.x, 0)) + +def perpendicular_vector2(v): + if v.x == 0 and v.y == 0: + return Vector(0, 1) + + return Vector((v.y, -v.x)) # -v.y, v.x + +def centroid_of_triangle(p1, p2, p3): + # Formula to calculate centroid + x = round((p1.x + p2.x + p3.x) / 3, 2) + y = round((p1.y + p2.y + p3.y) / 3, 2) + return Vector((x, y)) + + +import numpy as np + +def unit_vector(vector): + """ Returns the unit vector of the vector. """ + return vector / np.linalg.norm(vector) + +def angle_between_vectors(v1, v2, as_degree=False): + """ Returns the angle in radians between vectors 'v1' and 'v2':: + + >>> angle_between((1, 0, 0), (0, 1, 0)) + 1.5707963267948966 + >>> angle_between((1, 0, 0), (1, 0, 0)) + 0.0 + >>> angle_between((1, 0, 0), (-1, 0, 0)) + 3.141592653589793 + """ + v1_u = unit_vector(v1) + v2_u = unit_vector(v2) + return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0)) if not as_degree else degrees(np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0))) + +def mathutils_vector_to_numpy_array(vector): + return np.array(list(vector)) diff --git a/atelier/utils/others.py b/atelier/utils/others.py new file mode 100644 index 0000000..c058b9d --- /dev/null +++ b/atelier/utils/others.py @@ -0,0 +1,30 @@ +import bpy + + +def ShowMessageBox(_message = "", _title = "Message Box", _icon = 'INFO'): + def draw(self, context): + self.layout.label(text=_message) + bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon) + + +def override_context(area_type, region_type): + for window in bpy.context.window_manager.windows: + for area in window.screen.areas: + if area.type == area_type: + for region in area.regions: + if region.type == region_type: + return { + 'window' : window, + #'screen' : window.screen, + 'area' : area, + 'region' : region + } + return None + +def blender_version() -> tuple: + version = bpy.app.version + return (version[0], version[1]) + +import uuid +def Generate_UUID(): + return str(uuid.uuid4()) diff --git a/atelier/utils/others/cubic.splines.py b/atelier/utils/others/cubic.splines.py new file mode 100644 index 0000000..fe4986f --- /dev/null +++ b/atelier/utils/others/cubic.splines.py @@ -0,0 +1,94 @@ +import numpy as np +from math import sqrt + +def cubic_interp1d(x0, x, y): + """ + Interpolate a 1-D function using cubic splines. + x0 : a float or an 1d-array + x : (N,) array_like + A 1-D array of real/complex values. + y : (N,) array_like + A 1-D array of real values. The length of y along the + interpolation axis must be equal to the length of x. + + Implement a trick to generate at first step the cholesky matrice L of + the tridiagonal matrice A (thus L is a bidiagonal matrice that + can be solved in two distinct loops). + + additional ref: www.math.uh.edu/~jingqiu/math4364/spline.pdf + """ + x = np.asfarray(x) + y = np.asfarray(y) + + # remove non finite values + # indexes = np.isfinite(x) + # x = x[indexes] + # y = y[indexes] + + # check if sorted + if np.any(np.diff(x) < 0): + indexes = np.argsort(x) + x = x[indexes] + y = y[indexes] + + size = len(x) + + xdiff = np.diff(x) + ydiff = np.diff(y) + + # allocate buffer matrices + Li = np.empty(size) + Li_1 = np.empty(size-1) + z = np.empty(size) + + # fill diagonals Li and Li-1 and solve [L][y] = [B] + Li[0] = sqrt(2*xdiff[0]) + Li_1[0] = 0.0 + B0 = 0.0 # natural boundary + z[0] = B0 / Li[0] + + for i in range(1, size-1, 1): + Li_1[i] = xdiff[i-1] / Li[i-1] + Li[i] = sqrt(2*(xdiff[i-1]+xdiff[i]) - Li_1[i-1] * Li_1[i-1]) + Bi = 6*(ydiff[i]/xdiff[i] - ydiff[i-1]/xdiff[i-1]) + z[i] = (Bi - Li_1[i-1]*z[i-1])/Li[i] + + i = size - 1 + Li_1[i-1] = xdiff[-1] / Li[i-1] + Li[i] = sqrt(2*xdiff[-1] - Li_1[i-1] * Li_1[i-1]) + Bi = 0.0 # natural boundary + z[i] = (Bi - Li_1[i-1]*z[i-1])/Li[i] + + # solve [L.T][x] = [y] + i = size-1 + z[i] = z[i] / Li[i] + for i in range(size-2, -1, -1): + z[i] = (z[i] - Li_1[i-1]*z[i+1])/Li[i] + + # find index + index = x.searchsorted(x0) + np.clip(index, 1, size-1, index) + + xi1, xi0 = x[index], x[index-1] + yi1, yi0 = y[index], y[index-1] + zi1, zi0 = z[index], z[index-1] + hi1 = xi1 - xi0 + + # calculate cubic + f0 = zi0/(6*hi1)*(xi1-x0)**3 + \ + zi1/(6*hi1)*(x0-xi0)**3 + \ + (yi1/hi1 - zi1*hi1/6)*(x0-xi0) + \ + (yi0/hi1 - zi0*hi1/6)*(xi1-x0) + return f0 + +if __name__ == '__main__': + #import matplotlib.pyplot as plt + x = np.linspace(0, 10, 11) + y = np.sin(x) + #plt.scatter(x, y) + + x_new = np.linspace(0, 10, 201) + print(cubic_interp1d(x_new, x, y)) + #plt.plot(x_new, cubic_interp1d(x_new, x, y)) + + #plt.show() \ No newline at end of file diff --git a/atelier/utils/others/cubic_hermite_spline.py b/atelier/utils/others/cubic_hermite_spline.py new file mode 100644 index 0000000..25df9d6 --- /dev/null +++ b/atelier/utils/others/cubic_hermite_spline.py @@ -0,0 +1,153 @@ + +#!/usr/bin/python +#Cubic Hermite Spline +#Akihiko Yamaguchi, info@akihikoy.net +#2014 + +# Matlab-like mod function that returns always positive +import math +def Mod(x, y): + if y==0: return x + return x-y*math.floor(x/y) + +#Generate a cubic Hermite spline from a key points. +#Key points: [[t0,x0],[t1,x1],[t2,x2],...]. +class TCubicHermiteSpline: + class TKeyPoint: + T= 0.0 #Input + X= 0.0 #Output + M= 0.0 #Gradient + def __str__(self): + return '['+str(self.T)+', '+str(self.X)+', '+str(self.M)+']' + + class TParam: pass + + def __init__(self): + self.idx_prev= 0 + self.Param= self.TParam() + + def FindIdx(self, t, idx_prev=0): + idx= idx_prev + if idx>=len(self.KeyPts): idx= len(self.KeyPts)-1 + while idx+1self.KeyPts[idx+1].T: idx+=1 + while idx>=0 and t=len(self.KeyPts)-1: + if idx<0: + idx= 0 + t= self.KeyPts[0].T + else: + idx= len(self.KeyPts)-2 + t= self.KeyPts[-1].T + + h00= lambda t: t*t*(2.0*t-3.0)+1.0 + h10= lambda t: t*(t*(t-2.0)+1.0) + h01= lambda t: t*t*(-2.0*t+3.0) + h11= lambda t: t*t*(t-1.0) + + self.idx_prev= idx + p0= self.KeyPts[idx] + p1= self.KeyPts[idx+1] + tr= (t-p0.T) / (p1.T-p0.T) + return h00(tr)*p0.X + h10(tr)*(p1.T-p0.T)*p0.M + h01(tr)*p1.X + h11(tr)*(p1.T-p0.T)*p1.M + + #Compute a phase information (n, tp) for a cyclic spline curve. + #n: n-th occurrence of the base wave + #tp: phase (time in the base wave) + def PhaseInfo(self, t): + t0= self.KeyPts[0].T + te= self.KeyPts[-1].T + T= te-t0 + mod= Mod(t-t0,T) + tp= t0+mod #Phase + n= (t-t0-mod)/T + return n, tp + + #Return interpolated value at t (cyclic version). + #pi: Phase information. + def EvaluateC(self, t, pi=None): + if pi==None: + n, tp= self.PhaseInfo(t) + else: + n, tp= pi + return self.Evaluate(tp) + n*(self.KeyPts[-1].X - self.KeyPts[0].X) + + #data= [[t0,x0],[t1,x1],[t2,x2],...] + FINITE_DIFF=0 #Tangent method: finite difference method + CARDINAL=1 #Tangent method: Cardinal spline (c is used) + ZERO= 0 #End tangent: zero + GRAD= 1 #End tangent: gradient (m is used) + CYCLIC= 2 #End tangent: treating data as cyclic (KeyPts[-1] and KeyPts[0] are considered as an identical point) + def Initialize(self, data, tan_method=CARDINAL, end_tan=GRAD, c=0.0, m=1.0): + if data != None: + self.KeyPts= [self.TKeyPoint() for i in range(len(data))] + for idx in range(len(data)): + self.KeyPts[idx].T= data[idx][0] + self.KeyPts[idx].X= data[idx][1] + + #Store parameters for future use / remind parameters if not given + if tan_method==None: tan_method= self.Param.TanMethod + else: self.Param.TanMethod= tan_method + if end_tan==None: end_tan= self.Param.EndTan + else: self.Param.EndTan= end_tan + if c==None: c= self.Param.C + else: self.Param.C= c + if m==None: c= self.Param.M + else: self.Param.M= m + + grad= lambda idx1,idx2: (self.KeyPts[idx2].X-self.KeyPts[idx1].X)/(self.KeyPts[idx2].T-self.KeyPts[idx1].T) + + if tan_method == self.FINITE_DIFF: + for idx in range(1,len(self.KeyPts)-1): + self.KeyPts[idx].M= 0.5*grad(idx,idx+1) + 0.5*grad(idx-1,idx) + elif tan_method == self.CARDINAL: + for idx in range(1,len(self.KeyPts)-1): + self.KeyPts[idx].M= (1.0-c)*grad(idx-1,idx+1) + + if end_tan == self.ZERO: + self.KeyPts[0].M= 0.0 + self.KeyPts[-1].M= 0.0 + elif end_tan == self.GRAD: + self.KeyPts[0].M= m*grad(0,1) + self.KeyPts[-1].M= m*grad(-2,-1) + elif end_tan == self.CYCLIC: + if tan_method == self.FINITE_DIFF: + grad_p1= grad(0,1) + grad_n1= grad(-2,-1) + M= 0.5*grad_p1 + 0.5*grad_n1 + self.KeyPts[0].M= M + self.KeyPts[-1].M= M + elif tan_method == self.CARDINAL: + T= self.KeyPts[-1].T - self.KeyPts[0].T + X= self.KeyPts[-1].X - self.KeyPts[0].X + grad_2= (X+self.KeyPts[1].X-self.KeyPts[-2].X)/(T+self.KeyPts[1].T-self.KeyPts[-2].T) + M= (1.0-c)*grad_2 + self.KeyPts[0].M= M + self.KeyPts[-1].M= M + + def Update(self): + self.Initialize(data=None, tan_method=None, end_tan=None, c=None, m=None) + + +if __name__=="__main__": + import numpy as np + import random + + key_points= [[random.random()*3.0,random.random()*2.0-1.0] for i in range(5)] + key_points.sort() + + spline= TCubicHermiteSpline() + spline.Initialize(key_points, tan_method=spline.CARDINAL, c=0.0) + + X= [] + Y= [] + for t in np.arange(key_points[0][0], key_points[-1][0], 0.001): + x= spline.Evaluate(t) + X.append(t) + Y.append(x) + print(x) diff --git a/atelier/utils/package_installer.py b/atelier/utils/package_installer.py new file mode 100644 index 0000000..5c62cd5 --- /dev/null +++ b/atelier/utils/package_installer.py @@ -0,0 +1,17 @@ +import bpy + + +def install_package(package_name): + import subprocess + try: + output = subprocess.check_output([bpy.app.binary_path_python, '-m', 'pip', 'install', package_name]) + print(output) + except subprocess.CalledProcessError as e: + print(e.output) + +def admin_install_package(package_name): + from pathlib import Path + import subprocess + PYTHON_PATH = Path(bpy.app.binary_path_python) + BLENDER_SITE_PACKAGE = PYTHON_PATH.parents[1] / 'lib' / 'site-packages' + subprocess.check_call([str(PYTHON_PATH), "-m", "pip", "install", f'--target={BLENDER_SITE_PACKAGE}', package_name]) diff --git a/atelier/utils/space_conversion.py b/atelier/utils/space_conversion.py new file mode 100644 index 0000000..88c0a8a --- /dev/null +++ b/atelier/utils/space_conversion.py @@ -0,0 +1,28 @@ +from bpy_extras import view3d_utils +from . others import blender_version + + +def get_regiondata_view3d(_context): + # Get region and 3d region space data + return _context.region, _context.space_data.region_3d + +def convert_3d_spaceCoords_to_2d_screenCoords(_context, _3dPos): + region, regionData = get_regiondata_view3d(_context) + # Returns mathutils.Vector // None if coord is behind the origin of a perspective view. + return view3d_utils.location_3d_to_region_2d(region, regionData, _3dPos, default = None) # Vector or None + +def convert_2d_screenCoords_to_3d_spaceCoords(_context, _2dPos): + region, regionData = get_regiondata_view3d(_context) + # normalized 3d vector from 2d region coords to 3d space # length of 1 + vNormalized = view3d_utils.region_2d_to_vector_3d(region, regionData, _2dPos) # this returns a 3d vector (mathutils.Vector) + # Get 3D location from relative 2d coords aligned with depth location + using the normalized vector we got + return view3d_utils.region_2d_to_location_3d(region, regionData, _2dPos, vNormalized) + +def raycast_2d_3d(_ctx, _2dPos): + region, regionData = get_regiondata_view3d(_ctx) # get region data + # normalized 3d vector from 2d region coords to 3d space # length of 1 + vNormalized = view3d_utils.region_2d_to_vector_3d(region, regionData, _2dPos) # this returns a 3d vector (mathutils.Vector) + # Get view origin from relative 2d coords of the region to the 3d spcace + origin = view3d_utils.region_2d_to_origin_3d(region, regionData, _2dPos) + # hit, pos, normal, index, obj, matrix + return _ctx.scene.ray_cast(_ctx.view_layer if blender_version()[1] < 91 else _ctx.view_layer.depsgraph, origin, vNormalized, distance=1e+10) # distance can be higher