diff options
| author | Lex Neva <lexelby@users.noreply.github.com> | 2025-01-29 12:04:07 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-01-29 12:04:07 -0500 |
| commit | 913c2700d1486284dba0583ae1b280b1aa237570 (patch) | |
| tree | c165b29d0794981b5e44ab46f9838baab16b06a4 /lib/sew_stack/stitch_layers/mixins/randomization.py | |
| parent | efe3b27f17686094f74462bd81763a8197b54c6e (diff) | |
Sew Stack first steps (#3133)
* handle more recursive cases
* scaffolding for stitch layers
* scaffolding for SewStack
* always use DotDict when parsing json params
* add DefaultDotDict + DotDict fixes
* first working SewStack (no UI yet)
* ignore inkstitch_debug.log and .svg
* refactor
* early WIP: property grid display temporarily in stitch plan preview
* start of sew stack editor extension
* add layer properties panel and splitter
* spacing and better icon
* handle checkbox
* add layer action buttons
* show selected property help text in an HtmlWindow
* rename
* rephrase help text for tolerance
* refactor into separate file
* simplify structure
* better property type handling
* add randomization button
* add random seed re-roll button
* simulator preview
* update preview in a few more cases
* always DotDict
* avoid ridiculously slow simulations
* preview selected layer or all layers
* edit multiple objects and save only modified properties into the SVG
* better preview handling
* add reverse and jitter
* add stitch path jitter
* fix types
* fix random shuffle button
* fixes
* fix repeats
* type hinting to please pycharm
* show layer description
* avoid exception in properties with multiple values
* fix typing
* fix new layer
* draw a box around property grid and help box
* confirm before closing
* rename properties and fix seed
* fix close/cancel logic
* add buttons to undo changes and reset to default value
* set not modified if default is original setting
* fix invisible icon
* more space for properties
* fix random properties
* better regulation of simulator rendering speed
* Fixed timer being passed a float
* fix get_json_param() default handling
* fix tests
* add checkbox for sew stack only
* fix property help
* adjustable stitch layer editor help box size, with persistence
* repeat exact stitches
* "fix" style
* adjust for new next_element stuff
---------
Co-authored-by: CapellanCitizen <thecapellancitizen@gmail.com>
Diffstat (limited to 'lib/sew_stack/stitch_layers/mixins/randomization.py')
| -rw-r--r-- | lib/sew_stack/stitch_layers/mixins/randomization.py | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/lib/sew_stack/stitch_layers/mixins/randomization.py b/lib/sew_stack/stitch_layers/mixins/randomization.py new file mode 100644 index 00000000..5414731c --- /dev/null +++ b/lib/sew_stack/stitch_layers/mixins/randomization.py @@ -0,0 +1,121 @@ +import os +from secrets import randbelow +from typing import TYPE_CHECKING + +import wx.propgrid + +from ..stitch_layer_editor import Category, Property +from ....i18n import _ +from ....svg import PIXELS_PER_MM +from ....utils import DotDict, get_resource_dir, prng + +if TYPE_CHECKING: + from ... import SewStack + + +editor_instance = None + + +class RandomSeedEditor(wx.propgrid.PGTextCtrlAndButtonEditor): + def CreateControls(self, property_grid, property, position, size): + if wx.SystemSettings().GetAppearance().IsDark(): + randomize_icon_file = 'randomize_20x20_dark.png' + else: + randomize_icon_file = 'randomize_20x20.png' + randomize_icon = wx.Image(os.path.join(get_resource_dir("icons"), randomize_icon_file)).ConvertToBitmap() + # button = wx.Button(property_grid, wx.ID_ANY, _("Re-roll")) + # button.SetBitmap(randomize_icon) + + window_list = super().CreateControls(property_grid, property, position, size) + button = window_list.GetSecondary() + button.SetBitmap(randomize_icon) + button.SetLabel("") + button.SetToolTip(_("Re-roll")) + return window_list + + +class RandomSeedProperty(wx.propgrid.IntProperty): + def DoGetEditorClass(self): + return wx.propgrid.PropertyGridInterface.GetEditorByName("RandomSeedEditor") + + def OnEvent(self, propgrid, primaryEditor, event): + if event.GetEventType() == wx.wxEVT_COMMAND_BUTTON_CLICKED: + self.SetValueInEvent(randbelow(int(1e8))) + return True + return False + + +class RandomizationPropertiesMixin: + @classmethod + def randomization_properties(cls): + # We have to register the editor class once. We have to save a reference + # to the editor to avoid letting it get garbage collected. + global editor_instance + if editor_instance is None: + editor_instance = RandomSeedEditor() + wx.propgrid.PropertyGrid.DoRegisterEditorClass(editor_instance, "RandomSeedEditor") + + return Category(_("Randomization")).children( + Property("random_seed", _("Random seed"), type=RandomSeedProperty, + # Wow, it's really hard to explain the concept of a random seed to non-programmers... + help=_("The random seed is used when handling randomization settings. " + + "Click the button to choose a new random seed, which will generate random features differently. " + + "Alternatively, you can enter your own random seed. If you reuse a random seed, random features " + + "will look the same.")), + Property("random_stitch_offset", _("Offset stitches"), unit="mm", prefix="±", + help=_("Move stitches randomly by up to this many millimeters in any direction.")), + Property("random_stitch_path_offset", _("Offset stitch path"), unit="mm", prefix="±", + help=_("Move stitches randomly by up to this many millimeters perpendicular to the stitch path.\n\n" + + "If <b>Offset stitches</b> is also specified, then this one is processed first.")), + ) + + +class RandomizationMixin: + config: DotDict + element: "SewStack" + + @classmethod + def randomization_defaults(cls): + return dict( + random_seed=None, + random_stitch_offset=0.0, + random_stitch_path_offset=0.0, + ) + + def get_random_seed(self): + seed = self.config.get('random_seed') + if seed is None: + return self.element.get_default_random_seed() + else: + return seed + + def offset_stitches(self, stitches): + """Randomly move stitches by modifying a list of stitches in-place.""" + + if not stitches: + return + + if 'random_stitch_path_offset' in self.config and self.config.random_stitch_path_offset: + offset = self.config.random_stitch_path_offset * PIXELS_PER_MM + rand_iter = iter(prng.iter_uniform_floats(self.get_random_seed(), "random_stitch_path_offset")) + + last_stitch = stitches[0] + for stitch in stitches[1:]: + try: + direction = (stitch - last_stitch).unit().rotate_left() + except ZeroDivisionError: + continue + + last_stitch = stitch + distance = next(rand_iter) * 2 * offset - offset + result = stitch + direction * distance + stitch.x = result.x + stitch.y = result.y + + if 'random_stitch_offset' in self.config and self.config.random_stitch_offset: + offset = self.config.random_stitch_offset * PIXELS_PER_MM + rand_iter = iter(prng.iter_uniform_floats(self.get_random_seed(), "random_stitch_offset")) + + for stitch in stitches: + stitch.x += next(rand_iter) * 2 * offset - offset + stitch.y += next(rand_iter) * 2 * offset - offset |
