salabim 24.0.18__tar.gz → 25.0.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (26) hide show
  1. {salabim-24.0.18 → salabim-25.0.0}/PKG-INFO +2 -2
  2. {salabim-24.0.18 → salabim-25.0.0}/pyproject.toml +36 -30
  3. {salabim-24.0.18 → salabim-25.0.0}/salabim/LICENSE.txt +1 -1
  4. {salabim-24.0.18 → salabim-25.0.0}/salabim/salabim.py +195 -114
  5. {salabim-24.0.18 → salabim-25.0.0}/salabim.egg-info/PKG-INFO +2 -2
  6. {salabim-24.0.18 → salabim-25.0.0}/README.md +0 -0
  7. {salabim-24.0.18 → salabim-25.0.0}/salabim/DejaVuSansMono.ttf +0 -0
  8. {salabim-24.0.18 → salabim-25.0.0}/salabim/__init__.py +0 -0
  9. {salabim-24.0.18 → salabim-25.0.0}/salabim/calibri.ttf +0 -0
  10. {salabim-24.0.18 → salabim-25.0.0}/salabim/mplus-1m-regular.ttf +0 -0
  11. {salabim-24.0.18 → salabim-25.0.0}/salabim.egg-info/SOURCES.txt +0 -0
  12. {salabim-24.0.18 → salabim-25.0.0}/salabim.egg-info/dependency_links.txt +0 -0
  13. {salabim-24.0.18 → salabim-25.0.0}/salabim.egg-info/top_level.txt +0 -0
  14. {salabim-24.0.18 → salabim-25.0.0}/setup.cfg +0 -0
  15. {salabim-24.0.18 → salabim-25.0.0}/tests/test salabim.py +0 -0
  16. {salabim-24.0.18 → salabim-25.0.0}/tests/test_cap_now.py +0 -0
  17. {salabim-24.0.18 → salabim-25.0.0}/tests/test_componentgenerator.py +0 -0
  18. {salabim-24.0.18 → salabim-25.0.0}/tests/test_datetime.py +0 -0
  19. {salabim-24.0.18 → salabim-25.0.0}/tests/test_distributions.py +0 -0
  20. {salabim-24.0.18 → salabim-25.0.0}/tests/test_misc.py +0 -0
  21. {salabim-24.0.18 → salabim-25.0.0}/tests/test_monitor.py +0 -0
  22. {salabim-24.0.18 → salabim-25.0.0}/tests/test_process.py +0 -0
  23. {salabim-24.0.18 → salabim-25.0.0}/tests/test_queue.py +0 -0
  24. {salabim-24.0.18 → salabim-25.0.0}/tests/test_state.py +0 -0
  25. {salabim-24.0.18 → salabim-25.0.0}/tests/test_store.py +0 -0
  26. {salabim-24.0.18 → salabim-25.0.0}/tests/test_timeunit.py +0 -0
@@ -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
@@ -1,30 +1,36 @@
1
- [build-system]
2
- requires = ["setuptools"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "salabim"
7
- authors = [
8
- {name = "Ruud van der Ham", email = "rt.van.der.ham@gmail.com"}
9
- ]
10
- description = "salabim - discrete event simulation in Python"
11
- version = "24.0.18"
12
- readme = "README.md"
13
- requires-python = ">=3.7"
14
- dependencies = [
15
- ]
16
- classifiers = [
17
- "Development Status :: 5 - Production/Stable",
18
- "License :: OSI Approved :: MIT License",
19
- "Programming Language :: Python :: 3 :: Only"
20
- ]
21
- [project.urls]
22
- Homepage = "https://salabim.org"
23
- Repository = "https://github.com/salabim/salabim"
24
-
25
-
26
- [tool.setuptools]
27
- packages = ["salabim"]
28
-
29
- [tool.setuptools.package-data]
30
- "*" = ["*.ttf", "*.txt"]
1
+ [build-system]
2
+ requires = [
3
+ "setuptools",
4
+ ]
5
+ build-backend = "setuptools.build_meta"
6
+
7
+ [project]
8
+ name = "salabim"
9
+ authors = [
10
+ { name = "Ruud van der Ham", email = "rt.van.der.ham@gmail.com" },
11
+ ]
12
+ description = "salabim - discrete event simulation in Python"
13
+ version = "25.0.0"
14
+ readme = "README.md"
15
+ requires-python = ">=3.7"
16
+ dependencies = []
17
+ classifiers = [
18
+ "Development Status :: 5 - Production/Stable",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://salabim.org"
25
+ Repository = "https://github.com/salabim/salabim"
26
+
27
+ [tool.setuptools]
28
+ packages = [
29
+ "salabim",
30
+ ]
31
+
32
+ [tool.setuptools.package-data]
33
+ "*" = [
34
+ "*.ttf",
35
+ "*.txt",
36
+ ]
@@ -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
@@ -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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes