salabim 25.0.9.post4__tar.gz → 25.0.11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {salabim-25.0.9.post4 → salabim-25.0.11}/PKG-INFO +1 -1
  2. {salabim-25.0.9.post4 → salabim-25.0.11}/pyproject.toml +1 -1
  3. {salabim-25.0.9.post4 → salabim-25.0.11}/salabim/salabim.py +155 -32
  4. {salabim-25.0.9.post4 → salabim-25.0.11}/salabim.egg-info/PKG-INFO +1 -1
  5. {salabim-25.0.9.post4 → salabim-25.0.11}/README.md +0 -0
  6. {salabim-25.0.9.post4 → salabim-25.0.11}/salabim/DejaVuSansMono.ttf +0 -0
  7. {salabim-25.0.9.post4 → salabim-25.0.11}/salabim/LICENSE.txt +0 -0
  8. {salabim-25.0.9.post4 → salabim-25.0.11}/salabim/__init__.py +0 -0
  9. {salabim-25.0.9.post4 → salabim-25.0.11}/salabim/calibri.ttf +0 -0
  10. {salabim-25.0.9.post4 → salabim-25.0.11}/salabim/mplus-1m-regular.ttf +0 -0
  11. {salabim-25.0.9.post4 → salabim-25.0.11}/salabim.egg-info/SOURCES.txt +0 -0
  12. {salabim-25.0.9.post4 → salabim-25.0.11}/salabim.egg-info/dependency_links.txt +0 -0
  13. {salabim-25.0.9.post4 → salabim-25.0.11}/salabim.egg-info/top_level.txt +0 -0
  14. {salabim-25.0.9.post4 → salabim-25.0.11}/setup.cfg +0 -0
  15. {salabim-25.0.9.post4 → salabim-25.0.11}/tests/test salabim.py +0 -0
  16. {salabim-25.0.9.post4 → salabim-25.0.11}/tests/test_cap_now.py +0 -0
  17. {salabim-25.0.9.post4 → salabim-25.0.11}/tests/test_componentgenerator.py +0 -0
  18. {salabim-25.0.9.post4 → salabim-25.0.11}/tests/test_datetime.py +0 -0
  19. {salabim-25.0.9.post4 → salabim-25.0.11}/tests/test_distributions.py +0 -0
  20. {salabim-25.0.9.post4 → salabim-25.0.11}/tests/test_misc.py +0 -0
  21. {salabim-25.0.9.post4 → salabim-25.0.11}/tests/test_monitor.py +0 -0
  22. {salabim-25.0.9.post4 → salabim-25.0.11}/tests/test_process.py +0 -0
  23. {salabim-25.0.9.post4 → salabim-25.0.11}/tests/test_queue.py +0 -0
  24. {salabim-25.0.9.post4 → salabim-25.0.11}/tests/test_state.py +0 -0
  25. {salabim-25.0.9.post4 → salabim-25.0.11}/tests/test_store.py +0 -0
  26. {salabim-25.0.9.post4 → salabim-25.0.11}/tests/test_timeunit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: salabim
3
- Version: 25.0.9.post4
3
+ Version: 25.0.11
4
4
  Summary: salabim - discrete event simulation in Python
5
5
  Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
6
6
  Project-URL: Homepage, https://salabim.org
@@ -10,7 +10,7 @@ authors = [
10
10
  { name = "Ruud van der Ham", email = "rt.van.der.ham@gmail.com" },
11
11
  ]
12
12
  description = "salabim - discrete event simulation in Python"
13
- version = "25.0.9.post4"
13
+ version = "25.0.11"
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.7"
16
16
  dependencies = []
@@ -1,13 +1,13 @@
1
- # _ _ _ ____ ____ ___ ___
2
- # ___ __ _ | | __ _ | |__ (_) _ __ ___ |___ \ | ___| / _ \ / _ \
3
- # / __| / _` || | / _` || '_ \ | || '_ ` _ \ __) ||___ \ | | | | | (_) |
4
- # \__ \| (_| || || (_| || |_) || || | | | | | / __/ ___) | _ | |_| | _ \__, |
5
- # |___/ \__,_||_| \__,_||_.__/ |_||_| |_| |_| |_____||____/ (_) \___/ (_) /_/
1
+ # _ _ _ ____ ____ ___ _ _
2
+ # ___ __ _ | | __ _ | |__ (_) _ __ ___ |___ \ | ___| / _ \ / |/ |
3
+ # / __| / _` || | / _` || '_ \ | || '_ ` _ \ __) ||___ \ | | | | | || |
4
+ # \__ \| (_| || || (_| || |_) || || | | | | | / __/ ___) | _ | |_| | _ | || |
5
+ # |___/ \__,_||_| \__,_||_.__/ |_||_| |_| |_| |_____||____/ (_) \___/ (_)|_||_|
6
6
  # discrete event simulation
7
7
  #
8
8
  # see www.salabim.org for more information, the documentation and license information
9
9
 
10
- __version__ = "25.0.9"
10
+ __version__ = "25.0.11"
11
11
  import heapq
12
12
  import random
13
13
  import time
@@ -43,7 +43,6 @@ import base64
43
43
  import zipfile
44
44
  from pathlib import Path
45
45
 
46
-
47
46
  from typing import Any, Union, Iterable, Tuple, List, Callable, TextIO, Dict, Set, Type, Hashable, Optional
48
47
 
49
48
  dataframe = None # to please PyLance
@@ -52,6 +51,7 @@ ColorType = Union[str, Iterable[float]]
52
51
 
53
52
  Pythonista = sys.platform == "ios"
54
53
  Windows = sys.platform.startswith("win")
54
+ MacOS = platform.system == "Darwin"
55
55
  PyPy = platform.python_implementation() == "PyPy"
56
56
  Chromebook = "penguin" in platform.uname()
57
57
  Xlwings = "xlwings" in sys.modules
@@ -110,6 +110,7 @@ def a_log(*args):
110
110
 
111
111
  class g: ...
112
112
 
113
+
113
114
  if Pythonista:
114
115
  try:
115
116
  import scene # type: ignore
@@ -2889,6 +2890,14 @@ class Monitor:
2889
2890
 
2890
2891
  if False (default), the normal 2D plane will be used.
2891
2892
 
2893
+ screen_coordinates : bool
2894
+ use screen_coordinates
2895
+
2896
+ if True (default), screen_coordinates will be used instead.
2897
+
2898
+ if False, all parameters are scaled for positioning and scaling
2899
+ objects.
2900
+
2892
2901
  Returns
2893
2902
  -------
2894
2903
  reference to AnimateMonitor object : AnimateMonitor
@@ -3665,11 +3674,11 @@ class AnimateMonitor(DynamicClass):
3665
3674
  screen_coordinates : bool
3666
3675
  use screen_coordinates
3667
3676
 
3668
- if False, the scale parameters are use for positioning and scaling
3669
- objects.
3670
-
3671
- if True (default), screen_coordinates will be used.
3677
+ if True (default), screen_coordinates will be used instead.
3672
3678
 
3679
+ if False, all parameters are scaled for positioning and scaling
3680
+ objects.
3681
+
3673
3682
  Note
3674
3683
  ----
3675
3684
  All measures are in screen coordinates
@@ -3714,9 +3723,9 @@ class AnimateMonitor(DynamicClass):
3714
3723
  as_points: bool = None,
3715
3724
  over3d: bool = None,
3716
3725
  layer: Union[float, Callable] = 0,
3726
+ screen_coordinates=True,
3717
3727
  visible: Union[bool, Callable] = True,
3718
3728
  keep: Union[bool, Callable] = True,
3719
- screen_coordinates: bool = True,
3720
3729
  arg: Any = None,
3721
3730
  ):
3722
3731
  super().__init__()
@@ -4396,6 +4405,14 @@ class Queue:
4396
4405
 
4397
4406
  default: self (instance itself)
4398
4407
 
4408
+ screen_coordinates : bool
4409
+ use screen_coordinates
4410
+
4411
+ if True (default), screen_coordinates will be used instead.
4412
+
4413
+ if False, all parameters are scaled for positioning and scaling
4414
+ objects.
4415
+
4399
4416
  Returns
4400
4417
  -------
4401
4418
  reference to AnimationQueue object : AnimationQueue
@@ -7204,7 +7221,7 @@ by adding at the end:
7204
7221
 
7205
7222
  overridden_lineno = None
7206
7223
 
7207
- def animation_objects(self, id: Any) -> Tuple:
7224
+ def animation_objects(self, id: Any, screen_coordinates: bool = True) -> Tuple:
7208
7225
  """
7209
7226
  defines how to display a component in AnimateQueue
7210
7227
 
@@ -7234,7 +7251,9 @@ by adding at the end:
7234
7251
  """
7235
7252
  size_x = 50
7236
7253
  size_y = 50
7237
- ao0 = AnimateRectangle(text=str(self.sequence_number()), textcolor="bg", spec=(-20, -20, 20, 20), linewidth=0, fillcolor="fg")
7254
+ ao0 = AnimateRectangle(
7255
+ text=str(self.sequence_number()), textcolor="bg", spec=(-20, -20, 20, 20), linewidth=0, fillcolor="fg", screen_coordinates=screen_coordinates
7256
+ )
7238
7257
  return (size_x, size_y, ao0)
7239
7258
 
7240
7259
  def animation3d_objects(self, id: Any) -> Tuple:
@@ -10583,6 +10602,8 @@ class Environment:
10583
10602
  self._step_pressed = False
10584
10603
  self.stopped = False
10585
10604
  self._paused = False
10605
+ self._zoom_factor = 1.1
10606
+
10586
10607
  self.last_s0 = ""
10587
10608
  if Xlwings:
10588
10609
  if blind_animation is None:
@@ -10711,6 +10732,23 @@ class Environment:
10711
10732
  """
10712
10733
  if self._ui:
10713
10734
  self._handle_ui_event()
10735
+ self._x0_org = self._x0
10736
+ self._x1_org = self._x1
10737
+ self._y0_org = self._y0
10738
+ self._y1_org = self._y1
10739
+ self._scale_org = self._scale
10740
+ self._x0 = self._x0z
10741
+ self._y0 = self._y0z
10742
+ self._x1 = self._x1z # 0+self._width/self._scale
10743
+ self._y1 = self._y1z # +self._height/self._scale
10744
+ self._scale = self._scalez
10745
+
10746
+ # midx=self._x0+self._panx*(self._x1-self._x0)
10747
+ # self._x0, self._x1= midx-(1/self._zoom)*(self._x1-self._x0)/2,midx+(1/self._zoom)*(self._x1-self._x0)/2
10748
+
10749
+ # midy=self._y0+self._pany*(self._y1-self._y0)
10750
+ # self._y0, self._y1 = midy-(1/self._zoom)*(self._y1-self._y0)/2, midy+(1/self._zoom)*(self._y1-self._y0)/2
10751
+ # self._scale = self._width / (self._x1 - self._x0)
10714
10752
 
10715
10753
  def animation_post_tick(self, t: float) -> None:
10716
10754
  """
@@ -10723,7 +10761,12 @@ class Environment:
10723
10761
  t : float
10724
10762
  Current (animation) time.
10725
10763
  """
10726
- ...
10764
+ self._x0 = self._x0_org
10765
+ self._x1 = self._x1_org
10766
+ self._y0 = self._y0_org
10767
+ self._y1 = self._y1_org
10768
+ self._scale = self._scale_org
10769
+ self._last_scalez = self._scalez
10727
10770
 
10728
10771
  def animation_pre_tick_sys(self, t: float) -> None:
10729
10772
  for ao in self.sys_objects.copy(): # copy required as ao's may be removed due to keep
@@ -11248,6 +11291,61 @@ class Environment:
11248
11291
  def on_closing(self):
11249
11292
  self.an_quit()
11250
11293
 
11294
+ def on_mousewheel(self, event):
11295
+ x_mouse = self.root.winfo_pointerx() - self.root.winfo_rootx()
11296
+ y_mouse = self.height() - self.root.winfo_pointery() + self.root.winfo_rooty()
11297
+ x = (x_mouse / self._scale) + self._x0z
11298
+ y = (y_mouse / self._scale) + self._y0z
11299
+
11300
+ if Windows:
11301
+ delta = int(event.delta / 120) # normalize to ticks
11302
+ elif MacOS:
11303
+ delta = int(event.delta) # already small, usually ±1
11304
+ else:
11305
+ delta = 0 # fallback
11306
+ for _ in range(abs(delta)):
11307
+ if delta < 0:
11308
+ zoom_factor = self._zoom_factor
11309
+
11310
+ else:
11311
+ zoom_factor = 1 / self._zoom_factor
11312
+
11313
+ # min_zoom = min(
11314
+ # (self._x0 - x) / (self._x0z - x),
11315
+ # (self._x0 - x) / (self._x0z - x),
11316
+ # (self._x1 - x) / (self._x1z - x),
11317
+ # (self._y0 - y) / (self._y0z - y),
11318
+ # (self._y1 - y) / (self._y1z - y),
11319
+ # )
11320
+
11321
+ # zoom_factor = min(zoom_factor, min_zoom)
11322
+
11323
+ self._scalez /= zoom_factor
11324
+ self._x0z = x - (x - self._x0z) * zoom_factor
11325
+ self._y0z = y - (y - self._y0z) * zoom_factor
11326
+ self._x1z = x - (x - self._x1z) * zoom_factor
11327
+ self._y1z = y - (y - self._y1z) * zoom_factor
11328
+
11329
+ def start_pan(self, event):
11330
+ g.canvas.config(cursor="fleur") # Change cursor to "move" style
11331
+ self.lastx = event.x
11332
+ self.lasty = event.y
11333
+ self.lastx0 = self._x0z
11334
+ self.lasty0 = self._y0z
11335
+
11336
+ def do_pan(self, event):
11337
+ dx = -((event.x - self.lastx) / self._scale)
11338
+ dy = (event.y - self.lasty) / self._scale
11339
+ # self._x0z = max(self._x0,self.lastx0 + dx)
11340
+ # self._y0z = max(self._y0,self.lasty0 + dy)
11341
+ self._x0z = self.lastx0 + dx
11342
+ self._y0z = self.lasty0 + dy
11343
+ self._x1z = self._x0z + (self._x1 - self._x0)
11344
+ self._y1z = self._y0z + (self._y1 - self._y0)
11345
+
11346
+ def end_pan(self, event):
11347
+ g.canvas.config(cursor="") # Reset to default
11348
+
11251
11349
  def animation_parameters(
11252
11350
  self,
11253
11351
  animate: Union[bool, str] = None,
@@ -11850,6 +11948,10 @@ class Environment:
11850
11948
  self.root.bind("<space>", lambda self: g.animation_env.an_menu_go())
11851
11949
  self.root.bind("s", lambda self: g.animation_env.an_single_step())
11852
11950
  self.root.bind("<Control-c>", lambda self: g.animation_env.an_quit())
11951
+ self.root.bind("<MouseWheel>", self.on_mousewheel)
11952
+ self.root.bind("<ButtonPress-1>", self.start_pan)
11953
+ self.root.bind("<B1-Motion>", self.do_pan)
11954
+ self.root.bind("<ButtonRelease-1>", self.end_pan)
11853
11955
 
11854
11956
  g.canvas = tkinter.Canvas(self.root, width=self._width, height=self._height)
11855
11957
  g.canvas.configure(background=self.colorspec_to_hex("bg", False))
@@ -11857,6 +11959,11 @@ class Environment:
11857
11959
  g.canvas_objects = []
11858
11960
  g.canvas_object_overflow_image = None
11859
11961
 
11962
+ # g.canvas.move("all", 1, 1)
11963
+ # g.canvas.update()
11964
+ # g.canvas.move("all", -1, -1)
11965
+ # g.canvas.update()
11966
+
11860
11967
  self.uninstall_uios() # this causes all ui objects to be (re)installed
11861
11968
 
11862
11969
  if self._show_menu_buttons and not self._ui:
@@ -13313,6 +13420,12 @@ class Environment:
13313
13420
  raise SimulationStopped
13314
13421
  else:
13315
13422
  self.root.after(0, self.simulate_and_animate_loop)
13423
+ self._x0z = self._x0
13424
+ self._y0z = self._y0
13425
+ self._x1z = self._x1
13426
+ self._y1z = self._y1
13427
+
13428
+ self._scalez = self._last_scalez = self._scale
13316
13429
  self.root.mainloop()
13317
13430
  if self._animate and self.running:
13318
13431
  if self._video:
@@ -13822,19 +13935,19 @@ class Environment:
13822
13935
  if screen_coordinates:
13823
13936
  return 0
13824
13937
  else:
13825
- return self._x0 / scale
13938
+ return self._x0_org / scale
13826
13939
 
13827
13940
  if xy_anchor in ("n", "c", "center", "s"):
13828
13941
  if screen_coordinates:
13829
13942
  return (width / 2) / scale
13830
13943
  else:
13831
- return ((self._x0 + self._x1) / 2) / scale
13944
+ return ((self._x0_org + self._x1_org) / 2) / scale
13832
13945
 
13833
13946
  if xy_anchor in ("ne", "e", "se", ""):
13834
13947
  if screen_coordinates:
13835
13948
  return width / scale
13836
13949
  else:
13837
- return self._x1 / scale
13950
+ return self._x1_org / scale
13838
13951
 
13839
13952
  raise ValueError("incorrect xy_anchor", xy_anchor)
13840
13953
 
@@ -13849,19 +13962,19 @@ class Environment:
13849
13962
  if screen_coordinates:
13850
13963
  return height / scale
13851
13964
  else:
13852
- return self._y1 / scale
13965
+ return self._y1_org / scale
13853
13966
 
13854
13967
  if xy_anchor in ("w", "c", "center", "e"):
13855
13968
  if screen_coordinates:
13856
13969
  return (height / 2) / scale
13857
13970
  else:
13858
- return ((self._y0 + self._y1) / 2) / scale
13971
+ return ((self._y0_org + self._y1_org) / 2) / scale
13859
13972
 
13860
13973
  if xy_anchor in ("sw", "s", "se", ""):
13861
13974
  if screen_coordinates:
13862
13975
  return 0
13863
13976
  else:
13864
- return self._y0 / scale
13977
+ return self._y0_org / scale
13865
13978
 
13866
13979
  raise ValueError("incorrect xy_anchor", xy_anchor)
13867
13980
 
@@ -18180,6 +18293,7 @@ class AnimateQueue(DynamicClass):
18180
18293
  if not self.keep(t):
18181
18294
  self.remove()
18182
18295
  return
18296
+ screen_coordinates = self.screen_coordinates
18183
18297
  prev_aos = self.current_aos
18184
18298
  self.current_aos = {}
18185
18299
  xy_anchor = self.xy_anchor(t)
@@ -18201,8 +18315,8 @@ class AnimateQueue(DynamicClass):
18201
18315
  titleoffsetx = self.titleoffsetx(t)
18202
18316
  titleoffsety = self.titleoffsety(t)
18203
18317
 
18204
- x += self._queue.env.xy_anchor_to_x(xy_anchor, screen_coordinates=True, over3d=self.over3d)
18205
- y += self._queue.env.xy_anchor_to_y(xy_anchor, screen_coordinates=True, over3d=self.over3d)
18318
+ x += self._queue.env.xy_anchor_to_x(xy_anchor, screen_coordinates=screen_coordinates, over3d=self.over3d)
18319
+ y += self._queue.env.xy_anchor_to_y(xy_anchor, screen_coordinates=screen_coordinates, over3d=self.over3d)
18206
18320
 
18207
18321
  if direction == "e":
18208
18322
  self.x_t = x + (-25 if titleoffsetx is None else titleoffsetx)
@@ -18234,20 +18348,21 @@ class AnimateQueue(DynamicClass):
18234
18348
  if ((max_length is not None) and n >= max_length) or not self.visible_t:
18235
18349
  break
18236
18350
 
18237
- if c not in prev_aos:
18238
- nargs = c.animation_objects.__code__.co_argcount
18239
- if nargs == 1:
18240
- animation_objects = self.current_aos[c] = c.animation_objects()
18241
- else:
18242
- animation_objects = self.current_aos[c] = c.animation_objects(self.id(t))
18243
- else:
18351
+ if c in prev_aos and self.env._scalez != self.env._last_scalez: # if scale changed (due to zooming), rerender the animation_objects
18244
18352
  animation_objects = self.current_aos[c] = prev_aos[c]
18245
18353
  del prev_aos[c]
18354
+ else:
18355
+ parameters = inspect.signature(c.animation_objects).parameters
18356
+ kwargs = {}
18357
+ if "id" in parameters:
18358
+ kwargs["id"] = self.id(t)
18359
+ if "screen_coordinates" in parameters:
18360
+ kwargs["screen_coordinates"] = self.screen_coordinates
18361
+ animation_objects = self.current_aos[c] = c.animation_objects(**kwargs)
18246
18362
 
18247
18363
  dimx = _call(animation_objects[0], t, c)
18248
18364
  dimy = _call(animation_objects[1], t, c)
18249
18365
  for ao in animation_objects[2:]:
18250
- ao.screen_coordinates = self.screen_coordinates
18251
18366
  if isinstance(ao, AnimateClassic):
18252
18367
  if direction == "t":
18253
18368
  ao.x0 = xt + trajectory.x(t=x * 1.00, _t0=0)
@@ -18373,9 +18488,17 @@ class Animate3dQueue(DynamicClass):
18373
18488
  if given, the animation object will be removed
18374
18489
  automatically when the parent component is no longer accessible
18375
18490
 
18491
+ screen_coordinates : bool
18492
+ use screen_coordinates
18493
+
18494
+ if True (default), screen_coordinates will be used instead.
18495
+
18496
+ if False, all parameters are scaled for positioning and scaling
18497
+ objects.
18498
+
18376
18499
  Note
18377
18500
  ----
18378
- All parameters, apart from queue, id, arg and parent can be specified as:
18501
+ All parameters, apart from queue, id, arg, screen_coordinates and parent can be specified as:
18379
18502
 
18380
18503
  - a scalar, like 10
18381
18504
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: salabim
3
- Version: 25.0.9.post4
3
+ Version: 25.0.11
4
4
  Summary: salabim - discrete event simulation in Python
5
5
  Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
6
6
  Project-URL: Homepage, https://salabim.org
File without changes
File without changes