salabim 24.0.18__py3-none-any.whl → 25.0.0__py3-none-any.whl

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.
salabim/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (C) 2017-2024 Ruud van der Ham, ruud@salabim.org
3
+ Copyright (C) 2017-2025 Ruud van der Ham, ruud@salabim.org
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
salabim/salabim.py CHANGED
@@ -1,14 +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__ = "24.0.18"
11
-
10
+ __version__ = "25.0.0"
12
11
  import heapq
13
12
  import random
14
13
  import time
@@ -42,8 +41,12 @@ import urllib.request
42
41
  import urllib.error
43
42
  import base64
44
43
  import zipfile
45
-
46
44
  from pathlib import Path
45
+ try:
46
+ import peek
47
+ except ModuleNotFoundError:
48
+ ...
49
+
47
50
 
48
51
  from typing import Any, Union, Iterable, Tuple, List, Callable, TextIO, Dict, Set, Type, Hashable, Optional
49
52
 
@@ -61,6 +64,46 @@ Chromebook = "penguin" in platform.uname()
61
64
  PythonInExcel = not ("__file__" in globals())
62
65
  AnacondaCode = sys.platform == "emscripten"
63
66
 
67
+ _color_name_to_ANSI = dict(
68
+ dark_black="\033[0;30m",
69
+ dark_red="\033[0;31m",
70
+ dark_green="\033[0;32m",
71
+ dark_yellow="\033[0;33m",
72
+ dark_blue="\033[0;34m",
73
+ dark_magenta="\033[0;35m",
74
+ dark_cyan="\033[0;36m",
75
+ dark_white="\033[0;37m",
76
+ black="\033[1;30m",
77
+ red="\033[1;31m",
78
+ green="\033[1;32m",
79
+ yellow="\033[1;33m",
80
+ blue="\033[1;34m",
81
+ magenta="\033[1;35m",
82
+ cyan="\033[1;36m",
83
+ white="\033[1;37m",
84
+ reset="\033[0m",
85
+ )
86
+ _ANSI_to_rgb = {
87
+ "\033[1;30m": (51, 51, 51),
88
+ "\033[1;31m": (255, 0, 0),
89
+ "\033[1;32m": (0, 255, 0),
90
+ "\033[1;33m": (255, 255, 0),
91
+ "\033[1;34m": (0, 178, 255),
92
+ "\033[1;35m": (255, 0, 255),
93
+ "\033[1;36m": (0, 255, 255),
94
+ "\033[1;37m": (255, 255, 255),
95
+ "\033[0;30m": (76, 76, 76),
96
+ "\033[0;31m": (178, 0, 0),
97
+ "\033[0;32m": (0, 178, 0),
98
+ "\033[0;33m": (178, 178, 0),
99
+ "\033[0;34m": (0, 89, 255),
100
+ "\033[0;35m": (178, 0, 178),
101
+ "\033[0;36m": (0, 178, 178),
102
+ "\033[0;37m": (178, 178, 178),
103
+ "\033[0m": (),
104
+ }
105
+
106
+ ANSI=types.SimpleNamespace(_color_name_to_ANSI.items())
64
107
 
65
108
  def a_log(*args):
66
109
  if not hasattr(a_log, "a_logfile_name"):
@@ -4866,7 +4909,7 @@ class Queue:
4866
4909
  """
4867
4910
  return getattr(self, "_sequence_number", 1)
4868
4911
 
4869
- def add(self, component: "Component") -> "Queue":
4912
+ def add(self, component: "Component", priority: float = None) -> "Queue":
4870
4913
  """
4871
4914
  adds a component to the tail of a queue
4872
4915
 
@@ -4877,18 +4920,23 @@ class Queue:
4877
4920
 
4878
4921
  may not be member of the queue yet
4879
4922
 
4923
+ priority : float
4924
+ if None (default), add to the tail of the queue
4925
+
4926
+ otherwise, put after the last component with the same priority
4927
+
4880
4928
  Note
4881
4929
  ----
4882
- the priority will be set to
4930
+ if prioority is None, the priority will be set to
4883
4931
  the priority of the tail of the queue, if any
4884
4932
  or 0 if queue is empty
4885
4933
 
4886
4934
  This method is equivalent to append()
4887
4935
  """
4888
- component.enter(self)
4936
+ component.enter(self, priority)
4889
4937
  return self
4890
4938
 
4891
- def append(self, component: "Component") -> "Queue":
4939
+ def append(self, component: "Component", priority: float = None) -> "Queue":
4892
4940
  """
4893
4941
  appends a component to the tail of a queue
4894
4942
 
@@ -4899,9 +4947,14 @@ class Queue:
4899
4947
 
4900
4948
  may not be member of the queue yet
4901
4949
 
4950
+ priority : float
4951
+ if None (default), add to the tail of the queue
4952
+
4953
+ otherwise, put after the last component with the same priority
4954
+
4902
4955
  Note
4903
4956
  ----
4904
- the priority will be set to
4957
+ if priority is None, the priority will be set to
4905
4958
  the priority of the tail of the queue, if any
4906
4959
  or 0 if queue is empty
4907
4960
 
@@ -5013,7 +5066,7 @@ class Queue:
5013
5066
  component.enter_behind(self, poscomponent)
5014
5067
  return self
5015
5068
 
5016
- def add_sorted(self, component: "Component", priority: Any) -> "Queue":
5069
+ def add_sorted(self, component: "Component", priority: float) -> "Queue":
5017
5070
  """
5018
5071
  adds a component to a queue, according to the priority
5019
5072
 
@@ -5024,7 +5077,7 @@ class Queue:
5024
5077
 
5025
5078
  may not be member of the queue yet
5026
5079
 
5027
- priority: type that can be compared with other priorities in the queue
5080
+ priority: float
5028
5081
  priority in the queue
5029
5082
 
5030
5083
  Note
@@ -7339,7 +7392,6 @@ by adding at the end:
7339
7392
  self.status.reset(monitor=monitor, stats_only=stats_only)
7340
7393
  self.mode.reset(monitor=monitor, stats_only=stats_only)
7341
7394
 
7342
-
7343
7395
  def register(self, registry: List) -> "Component":
7344
7396
  """
7345
7397
  registers the component in the registry
@@ -8073,6 +8125,7 @@ by adding:
8073
8125
  self,
8074
8126
  store: Union["Store", Iterable],
8075
8127
  filter: Callable = lambda c: True,
8128
+ request_priority: Any = 0,
8076
8129
  fail_priority: float = 0,
8077
8130
  urgent: bool = True,
8078
8131
  fail_at: float = None,
@@ -8096,6 +8149,9 @@ by adding:
8096
8149
 
8097
8150
  default: lambda c: True (i.e. always return True)
8098
8151
 
8152
+ request_priority: float
8153
+ put component in to_store_requesters according to the given priority (default 0)
8154
+
8099
8155
  fail_priority : float
8100
8156
  priority of the fail event
8101
8157
 
@@ -8236,7 +8292,7 @@ by adding:
8236
8292
 
8237
8293
  self._from_stores = from_stores
8238
8294
  for store in from_stores:
8239
- self.enter(store._from_store_requesters)
8295
+ self.enter_sorted(store._from_store_requesters, priority=request_priority)
8240
8296
  self.status._value = requesting
8241
8297
  self._from_store_item = None
8242
8298
  self._from_store_filter = filter
@@ -8249,6 +8305,7 @@ by adding:
8249
8305
  self,
8250
8306
  store: Union["Store", Iterable],
8251
8307
  item: "Component",
8308
+ request_priority: float = None,
8252
8309
  priority: float = 0,
8253
8310
  fail_priority: float = 0,
8254
8311
  urgent: bool = True,
@@ -8268,6 +8325,9 @@ by adding:
8268
8325
  item: Component
8269
8326
  component to put to store
8270
8327
 
8328
+ request_priority: float
8329
+ put component in to_store_requesters according to the given priority (default 0)
8330
+
8271
8331
  fail_priority : float
8272
8332
  priority of the fail event
8273
8333
 
@@ -8279,11 +8339,11 @@ by adding:
8279
8339
  urgent : bool
8280
8340
  urgency indicator
8281
8341
 
8282
- if False (default), the component will be scheduled
8342
+ if False (default), the fail event will be scheduled
8283
8343
  behind all other components scheduled
8284
8344
  for the same time and priority
8285
8345
 
8286
- if True, the component will be scheduled
8346
+ if True, the fail event will be scheduled
8287
8347
  in front of all components scheduled
8288
8348
  for the same time and priority
8289
8349
 
@@ -8381,7 +8441,7 @@ by adding:
8381
8441
  return
8382
8442
 
8383
8443
  for store in to_stores:
8384
- self.enter(store._to_store_requesters)
8444
+ self.enter_sorted(store._to_store_requesters, priority=request_priority)
8385
8445
  self.status._value = requesting
8386
8446
  self._to_store_item = item
8387
8447
  self._to_store_priority = priority
@@ -8407,7 +8467,19 @@ by adding:
8407
8467
  for store in self._from_stores:
8408
8468
  store.rescan()
8409
8469
 
8410
- def request(self, *args, **kwargs) -> None:
8470
+ def request(
8471
+ self,
8472
+ *args,
8473
+ fail_at: float = None,
8474
+ fail_delay: float = None,
8475
+ mode: Any = None,
8476
+ urgent: bool = False,
8477
+ request_priority: float = 0,
8478
+ schedule_priority: float = 0,
8479
+ cap_now: bool = None,
8480
+ oneof: bool = False,
8481
+ called_from: str = "request",
8482
+ ) -> None:
8411
8483
  """
8412
8484
  request from a resource or resources
8413
8485
 
@@ -8417,9 +8489,12 @@ by adding:
8417
8489
  - resource, where quantity=1, priority=tail of requesters queue
8418
8490
  - tuples/list containing a resource, a quantity and optionally a priority.
8419
8491
  if the priority is not specified, the request
8420
- for the resource be added to the tail of
8421
- the requesters queue
8492
+ for the resource will be added according to the request_priority parameter
8493
+
8494
+ request_priority: float
8495
+ (may be overridden by the priority parameter in the arg sequence)
8422
8496
 
8497
+ put component requesters according to the given priority (default 0)
8423
8498
 
8424
8499
  priority : float
8425
8500
  priority of the fail event
@@ -8522,17 +8597,7 @@ by adding:
8522
8597
  --> requests 1 from r1, r2 or r3
8523
8598
 
8524
8599
  """
8525
- fail_at = kwargs.pop("fail_at", None)
8526
- fail_delay = kwargs.pop("fail_delay", None)
8527
- mode = kwargs.pop("mode", None)
8528
- urgent = kwargs.pop("urgent", False)
8529
- schedule_priority = kwargs.pop("priority", 0)
8530
- cap_now = kwargs.pop("cap_now", None)
8531
- oneof = kwargs.pop("oneof", False)
8532
- called_from = kwargs.pop("called_from", "request")
8533
8600
  self.oneof_request = oneof
8534
- if kwargs:
8535
- raise TypeError(called_from + "() got an unexpected keyword argument '" + tuple(kwargs)[0] + "'")
8536
8601
 
8537
8602
  if self.status.value != current:
8538
8603
  self._checkisnotdata()
@@ -8565,7 +8630,7 @@ by adding:
8565
8630
  return
8566
8631
  for arg in args:
8567
8632
  q = 1
8568
- priority = inf
8633
+ priority = request_priority
8569
8634
  if isinstance(arg, Resource):
8570
8635
  r = arg
8571
8636
  elif isinstance(arg, (tuple, list)):
@@ -8603,7 +8668,7 @@ by adding:
8603
8668
  if self.oneof_request:
8604
8669
  addstring += " (oneof)"
8605
8670
 
8606
- self.enter_sorted(r._requesters, priority)
8671
+ self.enter(r._requesters, priority)
8607
8672
  if self.env._trace:
8608
8673
  self.env.print_trace("", "", self.name(), req_text + r.name() + addstring)
8609
8674
 
@@ -8852,7 +8917,7 @@ by adding:
8852
8917
  for r in list(self._claims):
8853
8918
  self._release(r)
8854
8919
 
8855
- def wait_for(self,cond, states, priority=0, urgent=False, mode=None,fail_delay=None, fail_at=None, cap_now=None):
8920
+ def wait_for(self, cond, states, request_priority=0, priority=0, urgent=False, mode=None, fail_delay=None, fail_at=None, cap_now=None):
8856
8921
  schedule_priority = priority
8857
8922
  """
8858
8923
  wait for any or all of the given state values are met
@@ -8865,6 +8930,9 @@ by adding:
8865
8930
  states : iterable
8866
8931
  specicies which states should trigger the cond to be checked
8867
8932
 
8933
+ request_priority : float
8934
+ put component in waiters queue according to the given priority (deafult 0)
8935
+
8868
8936
  priority : float
8869
8937
  priority of the fail event
8870
8938
 
@@ -8954,9 +9022,9 @@ by adding:
8954
9022
 
8955
9023
  self.set_mode(mode)
8956
9024
 
8957
- self._cond=cond # add test ***
9025
+ self._cond = cond # add test ***
8958
9026
  for state in states:
8959
- self._waits.append((state, None,None))
9027
+ self._waits.append((state, None, None))
8960
9028
  if priority is None:
8961
9029
  self.enter(state._waiters)
8962
9030
  else:
@@ -8975,9 +9043,18 @@ by adding:
8975
9043
  else:
8976
9044
  return
8977
9045
 
8978
-
8979
-
8980
- def wait(self, *args, **kwargs) -> None:
9046
+ def wait(
9047
+ self,
9048
+ *args,
9049
+ fail_at: float = None,
9050
+ fail_delay: float = None,
9051
+ all: bool = False,
9052
+ mode: Any = None,
9053
+ urgent: bool = False,
9054
+ request_priority: float = 0,
9055
+ schedule_priority: float = 0,
9056
+ cap_now: bool = None,
9057
+ ) -> None:
8981
9058
  """
8982
9059
  wait for any or all of the given state values are met
8983
9060
 
@@ -8993,6 +9070,8 @@ by adding:
8993
9070
  be added to the tail of
8994
9071
  the waiters queue
8995
9072
 
9073
+ request_priority : float
9074
+ put component in waiters queue according to the given priority (default 0)
8996
9075
 
8997
9076
  priority : float
8998
9077
  priority of the fail event
@@ -9118,18 +9197,7 @@ by adding:
9118
9197
  --> waits for s1.value()==True and s2.value==True
9119
9198
 
9120
9199
  """
9121
- fail_at = kwargs.pop("fail_at", None)
9122
- fail_delay = kwargs.pop("fail_delay", None)
9123
- all = kwargs.pop("all", False)
9124
- mode = kwargs.pop("mode", None)
9125
- urgent = kwargs.pop("urgent", False)
9126
- schedule_priority = kwargs.pop("priority", 0)
9127
- cap_now = kwargs.pop("cap_now", None)
9128
-
9129
- self._cond=None
9130
-
9131
- if kwargs:
9132
- raise TypeError("wait() got an unexpected keyword argument '" + tuple(kwargs)[0] + "'")
9200
+ self._cond = None
9133
9201
 
9134
9202
  if self.status.value != current:
9135
9203
  self._checkisnotdata()
@@ -9160,7 +9228,7 @@ by adding:
9160
9228
 
9161
9229
  for arg in args:
9162
9230
  value = True
9163
- priority = None
9231
+ priority = request_priority
9164
9232
  if isinstance(arg, State):
9165
9233
  state = arg
9166
9234
  elif isinstance(arg, (tuple, list)):
@@ -9168,10 +9236,8 @@ by adding:
9168
9236
  if not isinstance(state, State):
9169
9237
  raise TypeError("incorrect specifier", arg)
9170
9238
  if len(arg) >= 2:
9171
- value = arg[1]
9239
+ priority = arg[1]
9172
9240
  if len(arg) >= 3:
9173
- priority = arg[2]
9174
- if len(arg) >= 4:
9175
9241
  raise TypeError("incorrect specifier", arg)
9176
9242
  else:
9177
9243
  raise TypeError("incorrect specifier", arg)
@@ -9205,7 +9271,6 @@ by adding:
9205
9271
  return
9206
9272
 
9207
9273
  def _trywait(self):
9208
-
9209
9274
  if self.status.value == interrupted:
9210
9275
  return False
9211
9276
  if self._cond:
@@ -9600,7 +9665,7 @@ by adding:
9600
9665
  index += 1
9601
9666
  return index
9602
9667
 
9603
- def enter(self, q: "Queue") -> "Component":
9668
+ def enter(self, q: "Queue", priority: float = None) -> "Component":
9604
9669
  """
9605
9670
  enters a queue at the tail
9606
9671
 
@@ -9616,8 +9681,17 @@ by adding:
9616
9681
  or 0 if queue is empty
9617
9682
  """
9618
9683
  self._checknotinqueue(q)
9619
- priority = q._tail.predecessor.priority
9620
- Qmember().insert_in_front_of(q._tail, self, q, priority)
9684
+ if priority is None:
9685
+ priority = q._tail.predecessor.priority
9686
+ Qmember().insert_in_front_of(q._tail, self, q, priority)
9687
+ else:
9688
+ if q._length >= 1 and priority < q._head.successor.priority: # direct enter component that's smaller than the rest
9689
+ m2 = q._head.successor
9690
+ else:
9691
+ m2 = q._tail
9692
+ while (m2.predecessor != q._head) and (m2.predecessor.priority > priority):
9693
+ m2 = m2.predecessor
9694
+ Qmember().insert_in_front_of(m2, self, q, priority)
9621
9695
  return self
9622
9696
 
9623
9697
  def enter_at_head(self, q: "Queue") -> "Component":
@@ -9696,22 +9770,14 @@ by adding:
9696
9770
  q : Queue
9697
9771
  queue to enter
9698
9772
 
9699
- priority: type that can be compared with other priorities in the queue
9773
+ priority: float
9700
9774
  priority in the queue
9701
9775
 
9702
9776
  Note
9703
9777
  ----
9704
9778
  The component is placed just before the first component with a priority > given priority
9705
9779
  """
9706
- self._checknotinqueue(q)
9707
- if q._length >= 1 and priority < q._head.successor.priority: # direct enter component that's smaller than the rest
9708
- m2 = q._head.successor
9709
- else:
9710
- m2 = q._tail
9711
- while (m2.predecessor != q._head) and (m2.predecessor.priority > priority):
9712
- m2 = m2.predecessor
9713
- Qmember().insert_in_front_of(m2, self, q, priority)
9714
- return self
9780
+ return self.enter(q, priority)
9715
9781
 
9716
9782
  def leave(self, q: "Queue" = None) -> "Component":
9717
9783
  """
@@ -9777,7 +9843,7 @@ by adding:
9777
9843
  q : Queue
9778
9844
  queue where the component belongs to
9779
9845
 
9780
- priority : type that can be compared with other priorities in the queue
9846
+ priority : float
9781
9847
  priority in queue
9782
9848
 
9783
9849
  if omitted, no change
@@ -10919,8 +10985,8 @@ class Environment:
10919
10985
  s = "view("
10920
10986
  items = []
10921
10987
  for prop in props.split():
10922
- items.append(f"{getattr(self.view,prop)(t):.4f}")
10923
- print("view(" + (",".join(f"{prop}={getattr(self.view,prop)(t):.4f}" for prop in props.split())) + f") # t={t:.4f}")
10988
+ items.append(f"{getattr(self.view, prop)(t):.4f}")
10989
+ print("view(" + (",".join(f"{prop}={getattr(self.view, prop)(t):.4f}" for prop in props.split())) + f") # t={t:.4f}")
10924
10990
 
10925
10991
  def _bind(self, tkinter_event, func):
10926
10992
  self.root.bind(tkinter_event, func)
@@ -11036,7 +11102,7 @@ class Environment:
11036
11102
  ao.label = "fovy" if prop == "field_of_view_y" else prop
11037
11103
 
11038
11104
  ao = AnimateText(
11039
- text=lambda arg, t: f"{getattr(self.view,arg.prop)(t):11.3f}",
11105
+ text=lambda arg, t: f"{getattr(self.view, arg.prop)(t):11.3f}",
11040
11106
  x=5 + i * 80 + 70,
11041
11107
  y=top,
11042
11108
  font="calibri",
@@ -11878,7 +11944,7 @@ class Environment:
11878
11944
  format="GIF",
11879
11945
  )
11880
11946
  else:
11881
- for _ in range(2): # normally runs only once
11947
+ for _ in range(2): # normally runs only once
11882
11948
  try:
11883
11949
  self._images[0].save(
11884
11950
  self._video_name,
@@ -11890,7 +11956,7 @@ class Environment:
11890
11956
  )
11891
11957
  break
11892
11958
  except ValueError: # prevent bug in Python 3.13
11893
- self._images=[image.convert("RGB") for image in self._images]
11959
+ self._images = [image.convert("RGB") for image in self._images]
11894
11960
 
11895
11961
  else:
11896
11962
  if PythonInExcel or AnacondaCode:
@@ -11906,9 +11972,8 @@ class Environment:
11906
11972
  format="GIF",
11907
11973
  )
11908
11974
  else:
11909
- for _ in range(2): # normally runs only once
11975
+ for _ in range(2): # normally runs only once
11910
11976
  try:
11911
-
11912
11977
  self._images[0].save(
11913
11978
  self._video_name,
11914
11979
  disposal=2,
@@ -11919,7 +11984,7 @@ class Environment:
11919
11984
  optimize=False,
11920
11985
  )
11921
11986
  except ValueError: # prevent bug in Python 3.13
11922
- self._images=[image.convert("RGB") for image in self._images]
11987
+ self._images = [image.convert("RGB") for image in self._images]
11923
11988
 
11924
11989
  del self._images
11925
11990
  elif self._video_out == "png":
@@ -11983,7 +12048,6 @@ class Environment:
11983
12048
  Number of 1/30 second long frames to be inserted
11984
12049
  """
11985
12050
 
11986
-
11987
12051
  if self._video_out is None:
11988
12052
  raise ValueError("video not set")
11989
12053
  if isinstance(image, (Path, str)):
@@ -12009,7 +12073,6 @@ class Environment:
12009
12073
  open_cv_image = cv2.cvtColor(numpy.array(image), cv2.COLOR_RGB2BGR)
12010
12074
  self._video_out.write(open_cv_image)
12011
12075
 
12012
-
12013
12076
  def _save_frame(self):
12014
12077
  self._exclude_from_animation = "not in video"
12015
12078
  image = self._capture_image("RGBA", self._video_mode)
@@ -12944,7 +13007,7 @@ class Environment:
12944
13007
  self.animation_parameters(synced=value, animate=None)
12945
13008
  return self._synced
12946
13009
 
12947
- def minimized(self, value: bool=None)-> bool:
13010
+ def minimized(self, value: bool = None) -> bool:
12948
13011
  """
12949
13012
  minimized
12950
13013
 
@@ -13212,7 +13275,7 @@ class Environment:
13212
13275
  """
13213
13276
  return self._current_component
13214
13277
 
13215
- def run(self, duration: float = None, till: float = None, priority: Any = inf, urgent: bool = False, cap_now: bool = None):
13278
+ def run(self, duration: float = None, till: float = None, priority: float = inf, urgent: bool = False, cap_now: bool = None):
13216
13279
  """
13217
13280
  start execution of the simulation
13218
13281
 
@@ -13352,7 +13415,6 @@ class Environment:
13352
13415
  self._t = self.animation_start_time
13353
13416
  else:
13354
13417
  self._t = self.animation_start_time + ((time.time() - self.animation_start_clocktime) * self._speed)
13355
-
13356
13418
  while self.peek() < self._t:
13357
13419
  self.step()
13358
13420
  if not (self.running and self._animate):
@@ -15780,29 +15842,49 @@ class Animate2dBase(DynamicClass):
15780
15842
  im = Image.new("RGBA", (int(totwidth + 0.1 * fontsize), int(totheight)), (0, 0, 0, 0))
15781
15843
  imwidth, imheight = im.size
15782
15844
  draw = ImageDraw.Draw(im)
15783
- pos = 0
15845
+ ypos = 0
15846
+ now_color = textcolor
15784
15847
  for line, width in zip(lines, widths):
15785
15848
  if line:
15786
- draw.text(xy=(0.1 * fontsize, pos), text=line, font=font, fill=textcolor)
15787
-
15788
- pos += lineheight
15789
- # this code is to correct a bug in the rendering of text,
15790
- # leaving a kind of shadow around the text
15791
- del draw
15792
- if textcolor[:3] != (0, 0, 0): # black is ok
15793
- if False and has_numpy():
15794
- arr = numpy.asarray(im).copy()
15795
- arr[:, :, 0] = textcolor[0]
15796
- arr[:, :, 1] = textcolor[1]
15797
- arr[:, :, 2] = textcolor[2]
15798
- im = Image.fromarray(numpy.uint8(arr))
15799
- else:
15800
- pix = im.load()
15801
- for y in range(imheight):
15802
- for x in range(imwidth):
15803
- pix[x, y] = (textcolor[0], textcolor[1], textcolor[2], pix[x, y][3])
15849
+ if "\033[" in line: # ANSI
15850
+ xpos = 0.1 * fontsize
15851
+ while line:
15852
+ for ansi, rgb in _ANSI_to_rgb.items():
15853
+ if line.startswith(ansi):
15854
+ if rgb:
15855
+ now_color = rgb
15856
+ else:
15857
+ now_color = textcolor
15858
+ line = line[len(ansi) :]
15859
+ break
15860
+ else:
15861
+ c = line[0]
15862
+ draw.text(xy=(xpos, ypos), text=c, font=font, fill=now_color)
15863
+ charwidth = font.getbbox(c)[2]
15864
+ xpos += charwidth
15865
+ line = line[1:]
15804
15866
 
15805
- # end of code to correct bug
15867
+ else:
15868
+ draw.text(xy=(0.1 * fontsize, ypos), text=line, font=font, fill=now_color)
15869
+
15870
+ ypos += lineheight
15871
+ # # this code is to correct a bug in the rendering of text,
15872
+ # # leaving a kind of shadow around the text
15873
+ # del draw
15874
+ # if textcolor[:3] != (0, 0, 0): # black is ok
15875
+ # if False and has_numpy():
15876
+ # arr = numpy.asarray(im).copy()
15877
+ # arr[:, :, 0] = textcolor[0]
15878
+ # arr[:, :, 1] = textcolor[1]
15879
+ # arr[:, :, 2] = textcolor[2]
15880
+ # im = Image.fromarray(numpy.uint8(arr))
15881
+ # else:
15882
+ # pix = im.load()
15883
+ # for y in range(imheight):
15884
+ # for x in range(imwidth):
15885
+ # pix[x, y] = (textcolor[0], textcolor[1], textcolor[2], pix[x, y][3])
15886
+
15887
+ # # end of code to correct bug
15806
15888
 
15807
15889
  self.imwidth, self.imheight = im.size
15808
15890
  self.heightA = heightA
@@ -23662,7 +23744,7 @@ class State:
23662
23744
  self.value.tally(value_after)
23663
23745
  self._trywait()
23664
23746
 
23665
- def _trywait(self, max=inf): # this _trywait of a state
23747
+ def _trywait(self, max=inf): # this _trywait of a state
23666
23748
  mx = self._waiters._head.successor
23667
23749
  while mx != self._waiters._tail:
23668
23750
  c = mx.component
@@ -25580,7 +25662,6 @@ class Animate3dObj(Animate3dBase):
25580
25662
  global visualization
25581
25663
  global pyglet
25582
25664
 
25583
-
25584
25665
  self.x = x
25585
25666
  self.y = y
25586
25667
  self.z = z
@@ -25605,12 +25686,12 @@ class Animate3dObj(Animate3dBase):
25605
25686
  try:
25606
25687
  import pywavefront
25607
25688
  except ImportError:
25608
- pywavefront=None
25609
-
25689
+ pywavefront = None
25690
+
25610
25691
  try:
25611
- import pyglet # this is a requirement for visualization!
25692
+ import pyglet # this is a requirement for visualization!
25612
25693
  except ImportError:
25613
- pyglet=None
25694
+ pyglet = None
25614
25695
 
25615
25696
  from pywavefront import visualization
25616
25697
 
@@ -25620,7 +25701,7 @@ class Animate3dObj(Animate3dBase):
25620
25701
  global pyglet
25621
25702
  if pywavefront is None:
25622
25703
  raise ImportError("Animate3dObj requires pywavefront. Not found")
25623
- if pyglet is None:
25704
+ if pyglet is None:
25624
25705
  raise ImportError("Animate3dObj requires pyglet. Not found")
25625
25706
 
25626
25707
  obj_filename = Path(self.filename(t))
@@ -27374,7 +27455,7 @@ def can_animate3d(try_only: bool = True) -> bool:
27374
27455
  glut.glutInit()
27375
27456
  except OpenGL.error.NullFunctionError:
27376
27457
  raise ImportError("Installed OpenGL does not support glut. Try 'pip install OpenGL-glut' or see the salabim documentation")
27377
-
27458
+
27378
27459
  return True
27379
27460
  else:
27380
27461
  if try_only:
@@ -27737,7 +27818,7 @@ def set_environment_aliases():
27737
27818
  return # do not set when using Sphinx build!
27738
27819
 
27739
27820
  for name, obj in list(globals().items()):
27740
- if (not name.startswith("_") or name in ("_Trajectory", "_Distribution")) and name != "yieldless" and name != "Environment":
27821
+ if (not name.startswith("_") or name in ("_Trajectory", "_Distribution")) and name != "yieldless" and name != "Environment" and not hasattr(Environment,name):
27741
27822
  if inspect.isclass(obj) and obj.__module__ == Environment.__module__:
27742
27823
  if issubclass(obj, Exception):
27743
27824
  exec(f"Environment.{name}={name}")
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: salabim
3
- Version: 24.0.18
3
+ Version: 25.0.0
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
@@ -0,0 +1,10 @@
1
+ salabim/DejaVuSansMono.ttf,sha256=Z_oIXp5yp1Zaw2y2p3vaxwHhjHpG0MFbmwhxSh4aIEI,335068
2
+ salabim/LICENSE.txt,sha256=eTPlcDJz4G0096Qv-wfMjm1Wxbd4ilDlsYg5rN4HjWQ,1106
3
+ salabim/__init__.py,sha256=r7qPLvlmX0dkZDyjuTo8Jo3ex3sD1L4pmK6K5ib9vyw,56
4
+ salabim/calibri.ttf,sha256=RWpf8Uo31RfvGGNaSt9-2sXSuN87AVE_NFMRsV3LhBk,1330156
5
+ salabim/mplus-1m-regular.ttf,sha256=EuFHr90BJjuAn_r5MleJFN-WfkeWJ4tf7DweI5zr8tU,289812
6
+ salabim/salabim.py,sha256=G7w62bzTMLNDN8paZkvYUA3MmvfPhwouL2nc7SLNO2A,1123563
7
+ salabim-25.0.0.dist-info/METADATA,sha256=m_Yl7oewus-EsdjjsC1OpAwGJy7UE5UCNFBgOa4jfO4,3457
8
+ salabim-25.0.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
9
+ salabim-25.0.0.dist-info/top_level.txt,sha256=UE6zVlbi3F6T5ma1a_5TrojMaF21GYKDt9svvm0U4cQ,8
10
+ salabim-25.0.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,10 +0,0 @@
1
- salabim/DejaVuSansMono.ttf,sha256=Z_oIXp5yp1Zaw2y2p3vaxwHhjHpG0MFbmwhxSh4aIEI,335068
2
- salabim/LICENSE.txt,sha256=qHlBa-POyexatCxDTjSKMlYtkBFQDn9lu-YV_1L6V0U,1106
3
- salabim/__init__.py,sha256=r7qPLvlmX0dkZDyjuTo8Jo3ex3sD1L4pmK6K5ib9vyw,56
4
- salabim/calibri.ttf,sha256=RWpf8Uo31RfvGGNaSt9-2sXSuN87AVE_NFMRsV3LhBk,1330156
5
- salabim/mplus-1m-regular.ttf,sha256=EuFHr90BJjuAn_r5MleJFN-WfkeWJ4tf7DweI5zr8tU,289812
6
- salabim/salabim.py,sha256=Ic3A6isSW-Ybu3nvB8judAXxfqp9mh1iCYBx-ACKBPY,1120439
7
- salabim-24.0.18.dist-info/METADATA,sha256=oVc1cFiKv3eqzVQbIH78pnnMu1afHe1S2K-glVtqnyU,3458
8
- salabim-24.0.18.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
9
- salabim-24.0.18.dist-info/top_level.txt,sha256=UE6zVlbi3F6T5ma1a_5TrojMaF21GYKDt9svvm0U4cQ,8
10
- salabim-24.0.18.dist-info/RECORD,,