CreativePython 1.1.1__tar.gz → 1.1.2__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.
- {creativepython-1.1.1/src/CreativePython.egg-info → creativepython-1.1.2}/PKG-INFO +1 -1
- {creativepython-1.1.1 → creativepython-1.1.2}/pyproject.toml +1 -1
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/GuiHandler.py +38 -25
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/GuiRenderer.py +69 -89
- {creativepython-1.1.1 → creativepython-1.1.2/src/CreativePython.egg-info}/PKG-INFO +1 -1
- {creativepython-1.1.1 → creativepython-1.1.2}/src/gui.py +8 -8
- creativepython-1.1.2/src/image.py +123 -0
- creativepython-1.1.1/src/image.py +0 -70
- {creativepython-1.1.1 → creativepython-1.1.2}/LICENSE +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/LICENSE-PSF +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/MANIFEST.in +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/README.md +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/setup.cfg +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/RealtimeAudioPlayer.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/__init__.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/Java-Comparison-Tests/advMetricRunner.pythonSurvey.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/Java-Comparison-Tests/compareMetrics_Java-Vs-Python.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/RunMetrics.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/Surveyor.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/__init__.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/Confidence.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/Contig.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/ExtendedNote.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/Histogram.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/Judgement.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/Measurement.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/PianoRoll.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/PianoRollOld.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/__init__.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/test_ExtendedNote.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/test_Histogram.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/test_Measurement.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/test_PianoRoll_assertions.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/test_PianoRoll_integration.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/test_PianoRoll_quantization.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/test_PianoRoll_unit.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/Metric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/ZipfMetrics.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/__init__.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/ChordDensityMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/ChordDistanceMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/ChordMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/ChordNormalizedMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/ChromaticToneMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/ContourBasslineDurationMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/ContourBasslineDurationQuantizedMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/ContourBasslinePitchMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/ContourMelodyDurationMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/ContourMelodyDurationQuantizedMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/ContourMelodyPitchMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/DurationBigramMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/DurationDistanceMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/DurationMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/DurationQuantizedBigramMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/DurationQuantizedDistanceMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/DurationQuantizedMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/HarmonicBigramMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/HarmonicConsonanceMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/HarmonicIntervalMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/MelodicBigramMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/MelodicConsonanceMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/MelodicIntervalMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/PitchDistanceMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/PitchDurationMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/PitchDurationQuantizedMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/PitchMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/RestMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/__init__.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/test_DurationMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/test_Metrics_BasicIntervalsAndBigrams.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/test_Metrics_ChordsAndConsonance.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/test_Metrics_ContoursAndChromatic.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/test_Metrics_QuantizedDurationsAndDistances.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/test_PitchMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/test_RestMetric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/test_Metric.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/utilities/CSVWriter.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/utilities/PowerLawRandom.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/utilities/__init__.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/notationRenderer.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython.egg-info/SOURCES.txt +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython.egg-info/dependency_links.txt +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython.egg-info/requires.txt +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython.egg-info/top_level.txt +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/bin/libportaudio.2.dylib +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/iannix.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/markov.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/midi.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/music.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/osc.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/timer.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/src/zipf.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/tests/testAnimate.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/tests/testPeer.py +0 -0
- {creativepython-1.1.1 → creativepython-1.1.2}/tests/test_keyEvent.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: CreativePython
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: A Python-based software environment for developing algorithmic art projects.
|
|
5
5
|
Author-email: "Dr. Bill Manaris" <manaris@cofc.edu>, Taj Ballinger <ballingertj@g.cofc.edu>, Trevor Ritchie <ritchiets@g.cofc.edu>
|
|
6
6
|
License: MIT License
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "CreativePython"
|
|
7
|
-
version = "1.1.
|
|
7
|
+
version = "1.1.2"
|
|
8
8
|
description = "A Python-based software environment for developing algorithmic art projects."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -61,16 +61,25 @@ def _createEvent(eventType, target, args=None):
|
|
|
61
61
|
# Child process entry point
|
|
62
62
|
#######################################################################################
|
|
63
63
|
|
|
64
|
-
def _launchRenderer(childConn):
|
|
64
|
+
def _launchRenderer(childConn, shutdownReadConn, shutdownWriteConn):
|
|
65
65
|
"""
|
|
66
66
|
Entry point for the GuiRenderer child process.
|
|
67
67
|
Defined here so GuiHandler can reference it without importing from GuiRenderer
|
|
68
68
|
globally (which would also import Qt/PySide6 in the parent process).
|
|
69
69
|
|
|
70
|
+
shutdownReadConn is the read end of the dedicated shutdown pipe; the child
|
|
71
|
+
watches it with QSocketNotifier and shuts down when it becomes readable (EOF).
|
|
72
|
+
shutdownWriteConn is the write end inherited from the parent (fork) or passed
|
|
73
|
+
via pickling (forkserver/spawn); it is closed immediately so that only the
|
|
74
|
+
parent's copy of the write end remains open — ensuring the child sees EOF when
|
|
75
|
+
the parent closes its copy.
|
|
76
|
+
|
|
70
77
|
At runtime this function only executes in the child process.
|
|
71
78
|
"""
|
|
72
79
|
import sys, os
|
|
73
80
|
|
|
81
|
+
shutdownWriteConn.close() # child must not hold the write end open
|
|
82
|
+
|
|
74
83
|
# Suppress macOS system noise (stale XPC connections inherited via fork)
|
|
75
84
|
# that would confuse students. Redirect at the OS level (fd 2) so that
|
|
76
85
|
# C-level libraries (Cocoa, XPC) are also silenced.
|
|
@@ -80,7 +89,7 @@ def _launchRenderer(childConn):
|
|
|
80
89
|
sys.stderr = open(os.devnull, 'w')
|
|
81
90
|
|
|
82
91
|
from CreativePython.GuiRenderer import GuiRenderer
|
|
83
|
-
renderer = GuiRenderer(childConn)
|
|
92
|
+
renderer = GuiRenderer(childConn, shutdownReadConn)
|
|
84
93
|
renderer.run()
|
|
85
94
|
|
|
86
95
|
|
|
@@ -140,6 +149,15 @@ class GuiHandler:
|
|
|
140
149
|
ctx = multiprocessing.get_context('forkserver')
|
|
141
150
|
else:
|
|
142
151
|
ctx = multiprocessing.get_context('fork')
|
|
152
|
+
|
|
153
|
+
# Dedicated one-directional shutdown pipe. The parent holds the write
|
|
154
|
+
# end; the child holds the read end. Closing the write end in the parent
|
|
155
|
+
# sends EOF to the child's read end, which Qt detects immediately via
|
|
156
|
+
# QSocketNotifier — independent of the command queue and independent of
|
|
157
|
+
# whether atexit runs (the OS closes the fd automatically on process death).
|
|
158
|
+
shutdownReadConn, shutdownWriteConn = multiprocessing.Pipe(duplex=False)
|
|
159
|
+
self._shutdownPipe = shutdownWriteConn
|
|
160
|
+
|
|
143
161
|
# With 'spawn' on POSIX (fork+exec), the forked helper calls
|
|
144
162
|
# get_preparation_data() which reads sys.modules['__main__'].__file__.
|
|
145
163
|
# When a user script is running in PENCIL's subprocess, __main__.__file__
|
|
@@ -159,7 +177,7 @@ class GuiHandler:
|
|
|
159
177
|
try:
|
|
160
178
|
self.childProcess = ctx.Process(
|
|
161
179
|
target = _launchRenderer,
|
|
162
|
-
args = (childConn,),
|
|
180
|
+
args = (childConn, shutdownReadConn, shutdownWriteConn),
|
|
163
181
|
daemon = True # child is killed automatically if parent dies unexpectedly
|
|
164
182
|
)
|
|
165
183
|
self.childProcess.start()
|
|
@@ -169,7 +187,8 @@ class GuiHandler:
|
|
|
169
187
|
_main_mod.__file__ = _saved_file
|
|
170
188
|
if _saved_spec is not _UNSET:
|
|
171
189
|
_main_mod.__spec__ = _saved_spec
|
|
172
|
-
childConn.close()
|
|
190
|
+
childConn.close() # parent no longer needs the child end of the command pipe
|
|
191
|
+
shutdownReadConn.close() # parent no longer needs the read end of the shutdown pipe
|
|
173
192
|
|
|
174
193
|
# responseId counter — each sendQuery() call gets a unique ID so the
|
|
175
194
|
# listener thread can route the response back to the correct caller
|
|
@@ -214,11 +233,10 @@ class GuiHandler:
|
|
|
214
233
|
|
|
215
234
|
def _listenForMessages(self):
|
|
216
235
|
"""
|
|
217
|
-
Background thread. Reads
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
Exits when the pipe is closed (EOFError).
|
|
236
|
+
Background thread. Reads incoming messages from GuiRenderer and routes
|
|
237
|
+
each one: responses unblock waiting sendQuery() callers; events invoke
|
|
238
|
+
registered callbacks. Exits when the child closes the pipe (EOFError)
|
|
239
|
+
and releases any sendQuery() callers still waiting for a response.
|
|
222
240
|
"""
|
|
223
241
|
while True:
|
|
224
242
|
try:
|
|
@@ -228,7 +246,11 @@ class GuiHandler:
|
|
|
228
246
|
elif 'type' in message:
|
|
229
247
|
self._dispatchEvent(message)
|
|
230
248
|
except EOFError:
|
|
231
|
-
break
|
|
249
|
+
break
|
|
250
|
+
with self._pendingLock:
|
|
251
|
+
for slot in self._pendingResponses.values():
|
|
252
|
+
slot['values'] = ['shutdown']
|
|
253
|
+
slot['event'].set()
|
|
232
254
|
|
|
233
255
|
def _handleResponse(self, responseDict):
|
|
234
256
|
"""
|
|
@@ -334,23 +356,14 @@ class GuiHandler:
|
|
|
334
356
|
|
|
335
357
|
def _shutdown(self):
|
|
336
358
|
"""
|
|
337
|
-
|
|
359
|
+
Signals the child to shut down and waits for it to exit.
|
|
360
|
+
Closing the shutdown pipe delivers EOF to the child's QSocketNotifier,
|
|
361
|
+
which fires _onShutdownSignal on Qt's main thread immediately — even if
|
|
362
|
+
atexit does not run, since the OS closes the pipe fd on process death.
|
|
338
363
|
Called automatically via atexit when the parent process exits.
|
|
339
|
-
The child is killed with SIGKILL so it does not drain its event queue —
|
|
340
|
-
exit() in the parent should return promptly.
|
|
341
364
|
"""
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
# bypassing Qt's graceful shutdown (which drains the event queue).
|
|
345
|
-
# Python delivers SIGTERM between bytecodes, so it fires within one
|
|
346
|
-
# iteration of the child's _pollPipe loop — far faster than SIGKILL,
|
|
347
|
-
# which macOS may defer while a Metal/GPU operation is in flight.
|
|
348
|
-
self.childProcess.terminate()
|
|
349
|
-
self.childProcess.join(timeout=0.5) # should die in < 1 ms
|
|
350
|
-
if self.childProcess.is_alive():
|
|
351
|
-
self.childProcess.kill() # backup: SIGKILL
|
|
352
|
-
self.childProcess.join(timeout=2.0)
|
|
353
|
-
self.connection.close()
|
|
365
|
+
self._shutdownPipe.close()
|
|
366
|
+
self.childProcess.join(timeout=1.0)
|
|
354
367
|
|
|
355
368
|
|
|
356
369
|
#######################################################################################
|
|
@@ -75,30 +75,20 @@ class GuiRenderer:
|
|
|
75
75
|
Unknown targets and unknown actions are silently ignored.
|
|
76
76
|
"""
|
|
77
77
|
|
|
78
|
-
def __init__(self, childConn):
|
|
78
|
+
def __init__(self, childConn, shutdownReadConn):
|
|
79
79
|
"""
|
|
80
80
|
Creates the QApplication and initializes all registries.
|
|
81
81
|
"""
|
|
82
|
-
self.connection
|
|
82
|
+
self.connection = childConn
|
|
83
|
+
self._shutdownConn = shutdownReadConn
|
|
83
84
|
|
|
84
85
|
# QApplication must be created first; it owns the Qt event loop.
|
|
85
86
|
self._app = QtWidgets.QApplication([])
|
|
86
87
|
|
|
87
|
-
# Do not quit when the last window is closed
|
|
88
|
-
#
|
|
89
|
-
# moment the user closes a Display, breaking the IPC pipe and making
|
|
90
|
-
# it impossible to open new Displays without restarting Python.
|
|
91
|
-
# The child only exits when the parent sends a 'shutdown' command or
|
|
92
|
-
# kills the process directly.
|
|
88
|
+
# Do not quit when the last window is closed — the child stays alive
|
|
89
|
+
# until the parent closes the shutdown pipe, triggering _onShutdownSignal.
|
|
93
90
|
self._app.setQuitOnLastWindowClosed(False)
|
|
94
91
|
|
|
95
|
-
# Qt installs a SIGTERM handler that drains the event queue before
|
|
96
|
-
# exiting (graceful shutdown). When the parent kills this process,
|
|
97
|
-
# we want immediate exit — override Qt's handler after QApplication
|
|
98
|
-
# is constructed so ours takes precedence.
|
|
99
|
-
import signal, os as _os
|
|
100
|
-
signal.signal(signal.SIGTERM, lambda sig, frame: _os._exit(0))
|
|
101
|
-
|
|
102
92
|
# maps objectId (int) -> mirror object (e.g. DisplayMirror, RectangleMirror)
|
|
103
93
|
self._objectRegistry = {}
|
|
104
94
|
|
|
@@ -108,87 +98,58 @@ class GuiRenderer:
|
|
|
108
98
|
|
|
109
99
|
def run(self):
|
|
110
100
|
"""
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
101
|
+
Wires up the command-polling timer and the shutdown notifier, then enters
|
|
102
|
+
Qt's event loop.
|
|
103
|
+
_pollPipe fires every 8ms to process incoming commands in fixed batches.
|
|
104
|
+
_shutdownNotifier watches the dedicated shutdown pipe fd; when the parent
|
|
105
|
+
closes its write end (via _shutdown() or process death), Qt fires
|
|
106
|
+
_onShutdownSignal on the main thread immediately — independent of the
|
|
107
|
+
command queue and of whether atexit runs in the parent.
|
|
114
108
|
"""
|
|
115
109
|
self._timer = QtCore.QTimer()
|
|
116
110
|
self._timer.timeout.connect(self._pollPipe)
|
|
117
111
|
self._timer.start(8)
|
|
118
|
-
self._app.exec()
|
|
119
112
|
|
|
120
|
-
|
|
113
|
+
self._shutdownNotifier = QtCore.QSocketNotifier(
|
|
114
|
+
self._shutdownConn.fileno(),
|
|
115
|
+
QtCore.QSocketNotifier.Type.Read
|
|
116
|
+
)
|
|
117
|
+
self._shutdownNotifier.activated.connect(self._onShutdownSignal)
|
|
118
|
+
|
|
119
|
+
self._app.exec()
|
|
121
120
|
|
|
122
|
-
#
|
|
123
|
-
# def _pollPipe(self):
|
|
124
|
-
# """
|
|
125
|
-
# Drains all pending commands before returning to Qt's event loop.
|
|
126
|
-
# """
|
|
127
|
-
# while self.connection.poll():
|
|
128
|
-
# message = self.connection.recv()
|
|
129
|
-
# keepRunning = self._routeCommand(message)
|
|
130
|
-
# if not keepRunning:
|
|
131
|
-
# break
|
|
121
|
+
# ── Command polling ───────────────────────────────────────────────────────
|
|
132
122
|
|
|
133
|
-
# ── Option B: fixed batch (let Qt render every N commands) ──────────────
|
|
134
123
|
_BATCH_SIZE = 10
|
|
135
|
-
|
|
124
|
+
|
|
136
125
|
def _pollPipe(self):
|
|
137
126
|
"""
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# _BATCH_MIN = 10 # batch size when scene is empty
|
|
159
|
-
# _BATCH_MAX = 1000 # batch size ceiling
|
|
160
|
-
# _BATCH_PER = 25 # add this many to the batch per 1000 objects in scene
|
|
161
|
-
|
|
162
|
-
# def _pollPipe(self):
|
|
163
|
-
# """
|
|
164
|
-
# Drains pending commands, letting Qt process events at adaptive
|
|
165
|
-
# intervals. Small scenes get frequent updates (responsive feel);
|
|
166
|
-
# large scenes batch more commands between repaints (less overhead
|
|
167
|
-
# from increasingly expensive repaints).
|
|
168
|
-
# """
|
|
169
|
-
# if self._polling:
|
|
170
|
-
# return # processEvents() fired the timer again — skip
|
|
171
|
-
# self._polling = True
|
|
172
|
-
# try:
|
|
173
|
-
# objectCount = len(self._objectRegistry)
|
|
174
|
-
# batchSize = min(self._BATCH_MAX,
|
|
175
|
-
# self._BATCH_MIN + objectCount * self._BATCH_PER)
|
|
176
|
-
# count = 0
|
|
177
|
-
# while self.connection.poll():
|
|
178
|
-
# message = self.connection.recv()
|
|
179
|
-
# keepRunning = self._routeCommand(message)
|
|
180
|
-
# if not keepRunning:
|
|
181
|
-
# break
|
|
182
|
-
# count += 1
|
|
183
|
-
# if count % batchSize == 0:
|
|
184
|
-
# self._app.processEvents()
|
|
185
|
-
# finally:
|
|
186
|
-
# self._polling = False
|
|
127
|
+
Processes up to _BATCH_SIZE pending commands per timer tick, then returns
|
|
128
|
+
control to Qt's event loop. Keeping batches small lets Qt handle input
|
|
129
|
+
events and repaints between each batch, keeping the UI responsive during
|
|
130
|
+
heavy animation.
|
|
131
|
+
"""
|
|
132
|
+
count = 0
|
|
133
|
+
while count < self._BATCH_SIZE and self.connection.poll():
|
|
134
|
+
try:
|
|
135
|
+
message = self.connection.recv()
|
|
136
|
+
except (EOFError, OSError):
|
|
137
|
+
# Command pipe closed — parent process died without sending a shutdown
|
|
138
|
+
# signal (e.g. killed by SIGTERM/SIGKILL, atexit didn't run).
|
|
139
|
+
# _onShutdownSignal may also fire if the OS released the shutdown pipe
|
|
140
|
+
# write end, but _app.quit() is safe to call from either path.
|
|
141
|
+
self._timer.stop()
|
|
142
|
+
self._shutdownNotifier.setEnabled(False)
|
|
143
|
+
self._app.quit()
|
|
144
|
+
return
|
|
145
|
+
self._routeCommand(message)
|
|
146
|
+
count += 1
|
|
187
147
|
|
|
188
148
|
def _routeCommand(self, commandDict):
|
|
189
149
|
"""
|
|
190
150
|
Routes an incoming command to the appropriate handler.
|
|
191
|
-
|
|
151
|
+
Shutdown is handled out-of-band by _onShutdownSignal; unknown actions are
|
|
152
|
+
silently ignored.
|
|
192
153
|
"""
|
|
193
154
|
action = commandDict.get('action')
|
|
194
155
|
target = commandDict.get('target')
|
|
@@ -196,11 +157,7 @@ class GuiRenderer:
|
|
|
196
157
|
responseId = commandDict.get('responseId')
|
|
197
158
|
|
|
198
159
|
# ── Built-in actions ──────────────────────────────────────────────────
|
|
199
|
-
if action == '
|
|
200
|
-
self._handleShutdown()
|
|
201
|
-
return False
|
|
202
|
-
|
|
203
|
-
elif action == 'ping':
|
|
160
|
+
if action == 'ping':
|
|
204
161
|
response = _createResponse(responseId, ['pong'])
|
|
205
162
|
self.connection.send(response)
|
|
206
163
|
|
|
@@ -356,9 +313,32 @@ class GuiRenderer:
|
|
|
356
313
|
event = _createEvent(eventType, objectId, args)
|
|
357
314
|
self.connection.send(event)
|
|
358
315
|
|
|
359
|
-
def
|
|
360
|
-
"""
|
|
316
|
+
def _onShutdownSignal(self):
|
|
317
|
+
"""
|
|
318
|
+
Called by Qt when the shutdown pipe becomes readable — meaning the parent
|
|
319
|
+
has closed its write end (via _shutdown() or process death).
|
|
320
|
+
Fires immediately on the Qt main thread, independent of the command queue.
|
|
321
|
+
Drains any remaining commands from the command pipe, sending 'shutdown'
|
|
322
|
+
responses to release any sendQuery() callers blocking in the parent.
|
|
323
|
+
Fire-and-forget commands are discarded. Then quits Qt's event loop
|
|
324
|
+
normally so Qt can clean up its own resources.
|
|
325
|
+
"""
|
|
326
|
+
self._shutdownNotifier.setEnabled(False)
|
|
361
327
|
self._timer.stop()
|
|
328
|
+
|
|
329
|
+
# Drain remaining commands. Queries get a 'shutdown' response so that
|
|
330
|
+
# any sendQuery() call blocking in the parent is released immediately.
|
|
331
|
+
# Fire-and-forget commands are discarded.
|
|
332
|
+
# If the command pipe is already closed, the drain is skipped gracefully.
|
|
333
|
+
try:
|
|
334
|
+
while self.connection.poll():
|
|
335
|
+
message = self.connection.recv()
|
|
336
|
+
responseId = message.get('responseId')
|
|
337
|
+
if responseId is not None:
|
|
338
|
+
self.connection.send(_createResponse(responseId, ['shutdown']))
|
|
339
|
+
except (EOFError, OSError):
|
|
340
|
+
pass # command pipe already closed; listener releases pending queries
|
|
341
|
+
|
|
362
342
|
self._app.quit()
|
|
363
343
|
|
|
364
344
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: CreativePython
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: A Python-based software environment for developing algorithmic art projects.
|
|
5
5
|
Author-email: "Dr. Bill Manaris" <manaris@cofc.edu>, Taj Ballinger <ballingertj@g.cofc.edu>, Trevor Ritchie <ritchiets@g.cofc.edu>
|
|
6
6
|
License: MIT License
|
|
@@ -933,19 +933,19 @@ class Display(_Interactable):
|
|
|
933
933
|
raise TypeError(f'{type(self).__name__}.addPopupMenu(): menu should be a Menu object (it was {type(menu).__name__})')
|
|
934
934
|
_handler().sendCommand('addPopupMenu', self._objectId, {'menuId': menu._objectId})
|
|
935
935
|
|
|
936
|
-
def
|
|
936
|
+
def save(self, filename, width=None, height=None):
|
|
937
937
|
"""
|
|
938
938
|
Saves the display's currently visible canvas to an image file.
|
|
939
939
|
The file format is determined by the filename extension (e.g. .png, .jpg).
|
|
940
940
|
Optional width and height resize the saved image; omitting one preserves the aspect ratio.
|
|
941
|
-
Prints whether the
|
|
941
|
+
Prints whether the save succeeded and the resolved file path.
|
|
942
942
|
"""
|
|
943
943
|
result = _handler().sendQuery('write', self._objectId, {'filename': filename, 'width': width, 'height': height})
|
|
944
944
|
success, resolvedPath = result[0], result[1]
|
|
945
945
|
if success:
|
|
946
|
-
print(f'{type(self).__name__}.
|
|
946
|
+
print(f'{type(self).__name__}.save(): saved canvas to "{resolvedPath}"')
|
|
947
947
|
else:
|
|
948
|
-
print(f'{type(self).__name__}.
|
|
948
|
+
print(f'{type(self).__name__}.save(): failed to save to "{resolvedPath}"')
|
|
949
949
|
|
|
950
950
|
def onClose(self, action):
|
|
951
951
|
"""
|
|
@@ -2296,9 +2296,9 @@ class Icon(_Graphics):
|
|
|
2296
2296
|
rotation = self.getRotation()
|
|
2297
2297
|
return f'Icon(filename = "{filename}", width = {width}, height = {height}, rotation = {rotation})'
|
|
2298
2298
|
|
|
2299
|
-
# ──
|
|
2299
|
+
# ── Save ────────────────────────────────────────────────────────────────
|
|
2300
2300
|
|
|
2301
|
-
def
|
|
2301
|
+
def save(self, filename, width=None, height=None):
|
|
2302
2302
|
"""
|
|
2303
2303
|
Saves the icon to an image file.
|
|
2304
2304
|
The file format is determined by the filename extension (e.g. .png, .jpg).
|
|
@@ -2308,9 +2308,9 @@ class Icon(_Graphics):
|
|
|
2308
2308
|
result = _handler().sendQuery('write', self._objectId, {'filename': filename, 'width': width, 'height': height})
|
|
2309
2309
|
success, resolvedPath = result[0], result[1]
|
|
2310
2310
|
if success:
|
|
2311
|
-
print(f'{type(self).__name__}.
|
|
2311
|
+
print(f'{type(self).__name__}.save(): saved canvas to "{resolvedPath}"')
|
|
2312
2312
|
else:
|
|
2313
|
-
print(f'{type(self).__name__}.
|
|
2313
|
+
print(f'{type(self).__name__}.save(): failed to save "{resolvedPath}"')
|
|
2314
2314
|
|
|
2315
2315
|
# ── Crop ────────────────────────────────────────────────────────────────
|
|
2316
2316
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
################################################################################
|
|
2
|
+
# image.py Version 1.0 30-Jan-2025
|
|
3
|
+
# Taj Ballinger, Trevor Ritchie, and Bill Manaris
|
|
4
|
+
#
|
|
5
|
+
##############################################################
|
|
6
|
+
##################
|
|
7
|
+
|
|
8
|
+
from gui import Display, Icon
|
|
9
|
+
|
|
10
|
+
class Image:
|
|
11
|
+
"""
|
|
12
|
+
Display window for rendering images.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self, arg1, arg2=None):
|
|
15
|
+
"""
|
|
16
|
+
Creates a new image display.
|
|
17
|
+
"""
|
|
18
|
+
# JythonMusic's Image class has overloaded constructors,
|
|
19
|
+
# but Python doesn't allow that. We replicate an overload based on the
|
|
20
|
+
# data types of arg1 and arg2.
|
|
21
|
+
#
|
|
22
|
+
# (str, None) -> a filename for an image to load
|
|
23
|
+
# (str, int) -> a filename for an image to load, scaled to a width
|
|
24
|
+
# (int, None) -> the width of a square, blank canvas
|
|
25
|
+
# (int, int) -> the width and height of a blank canvas
|
|
26
|
+
|
|
27
|
+
self._display = Display() # create a new, blank Display
|
|
28
|
+
self.read(arg1, arg2) # read() handles loading the icon, plus setting the title and size of the Display
|
|
29
|
+
|
|
30
|
+
def show(self):
|
|
31
|
+
"""
|
|
32
|
+
Shows the display window.
|
|
33
|
+
"""
|
|
34
|
+
self._display.show()
|
|
35
|
+
|
|
36
|
+
def hide(self):
|
|
37
|
+
"""
|
|
38
|
+
Hides the display window.
|
|
39
|
+
"""
|
|
40
|
+
self._display.hide()
|
|
41
|
+
|
|
42
|
+
def getWidth(self):
|
|
43
|
+
"""
|
|
44
|
+
Returns the display's canvas width (in pixels).
|
|
45
|
+
"""
|
|
46
|
+
return self._display.getWidth()
|
|
47
|
+
|
|
48
|
+
def getHeight(self):
|
|
49
|
+
"""
|
|
50
|
+
Returns the display's canvas height (in pixels).
|
|
51
|
+
"""
|
|
52
|
+
return self._display.getHeight()
|
|
53
|
+
|
|
54
|
+
# pixel manipulation wrappers
|
|
55
|
+
|
|
56
|
+
def getPixel(self, col, row):
|
|
57
|
+
"""
|
|
58
|
+
Returns the [r, g, b] color of a given pixel in the image.
|
|
59
|
+
"""
|
|
60
|
+
return self._icon.getPixel(col, row)
|
|
61
|
+
|
|
62
|
+
def setPixel(self, col, row, RGBList):
|
|
63
|
+
"""
|
|
64
|
+
Sets the [r, g, b] color of a given pixel in the image.
|
|
65
|
+
"""
|
|
66
|
+
self._icon.setPixel(col, row, RGBList)
|
|
67
|
+
|
|
68
|
+
def getPixels(self):
|
|
69
|
+
"""
|
|
70
|
+
Returns the [r, g, b] color of all pixels in the icon as a 2-dimensional array.
|
|
71
|
+
"""
|
|
72
|
+
return self._icon.getPixels()
|
|
73
|
+
|
|
74
|
+
def setPixels(self, pixels):
|
|
75
|
+
"""
|
|
76
|
+
Sets the [r, g, b] color of all pixels in the icon from a 2-dimensional array.
|
|
77
|
+
"""
|
|
78
|
+
self._icon.setPixels(pixels)
|
|
79
|
+
|
|
80
|
+
def write(self, filename):
|
|
81
|
+
"""
|
|
82
|
+
Saves the display's currently visible canvas to an image file.
|
|
83
|
+
The file format is determined by the filename extension (e.g. .png, .jpg).
|
|
84
|
+
"""
|
|
85
|
+
self._display.save(filename)
|
|
86
|
+
|
|
87
|
+
def read(self, arg1, arg2=None):
|
|
88
|
+
"""
|
|
89
|
+
Updates the image with a new filename.
|
|
90
|
+
arg1 and arg2 have the same overloaded behavior as Image's constructor.
|
|
91
|
+
(str, None) -> a filename for an image to load
|
|
92
|
+
(str, int) -> a filename for an image to load, scaled to a width
|
|
93
|
+
(int, None) -> the width of a square, blank canvas
|
|
94
|
+
(int, int) -> the width and height of a blank canvas
|
|
95
|
+
"""
|
|
96
|
+
# first, load the icon
|
|
97
|
+
if isinstance(arg1, str):
|
|
98
|
+
# arg1 is a filename
|
|
99
|
+
title = arg1
|
|
100
|
+
icon = Icon(arg1, arg2) # load icon with optional resizing
|
|
101
|
+
else:
|
|
102
|
+
# arg1 is a image size
|
|
103
|
+
title = "Image"
|
|
104
|
+
icon = Icon("", arg1, arg2) # load blank icon, sized to given dimensions
|
|
105
|
+
|
|
106
|
+
# remove the previous icon, if needed
|
|
107
|
+
if self._icon is not None:
|
|
108
|
+
self._display.remove(self._icon)
|
|
109
|
+
|
|
110
|
+
# next, update the Display
|
|
111
|
+
width, height = icon.getSize()
|
|
112
|
+
self._display.setSize(width, height)
|
|
113
|
+
self._display.setTitle(title)
|
|
114
|
+
|
|
115
|
+
# finally, add new icon to the display
|
|
116
|
+
self._icon = icon
|
|
117
|
+
self._display.add(icon)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
###### Unit Tests ###################################
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
pass
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
################################################################################
|
|
2
|
-
# image.py Version 1.0 30-Jan-2025
|
|
3
|
-
# Taj Ballinger, Trevor Ritchie, and Bill Manaris
|
|
4
|
-
#
|
|
5
|
-
##############################################################
|
|
6
|
-
##################
|
|
7
|
-
|
|
8
|
-
from gui import Display, Icon
|
|
9
|
-
|
|
10
|
-
class Image(Display):
|
|
11
|
-
"""
|
|
12
|
-
Display window for rendering images.
|
|
13
|
-
"""
|
|
14
|
-
def __init__(self, arg1, arg2=None):
|
|
15
|
-
"""
|
|
16
|
-
Creates a new image display.
|
|
17
|
-
"""
|
|
18
|
-
# JythonMusic's Image class has overloaded constructors,
|
|
19
|
-
# but Python doesn't allow that. We replicate an overload based on the
|
|
20
|
-
# data types of arg1 and arg2.
|
|
21
|
-
#
|
|
22
|
-
# (str, None) -> a filename for an image to load
|
|
23
|
-
# (str, int) -> a filename for an image to load, scaled to a width
|
|
24
|
-
# (int, None) -> the width of a square, blank canvas
|
|
25
|
-
# (int, int) -> the width and height of a blank canvas
|
|
26
|
-
|
|
27
|
-
if isinstance(arg1, str):
|
|
28
|
-
# this is a filename
|
|
29
|
-
title = arg1
|
|
30
|
-
self._icon = Icon(arg1, arg2) # load icon with optional parameter
|
|
31
|
-
|
|
32
|
-
else:
|
|
33
|
-
title = "Image"
|
|
34
|
-
self._icon = Icon("", arg1, arg2) # load blank icon with given dimensions
|
|
35
|
-
|
|
36
|
-
width, height = self._icon.getSize()
|
|
37
|
-
|
|
38
|
-
Display.__init__(self, title, width, height)
|
|
39
|
-
self.add(self._icon)
|
|
40
|
-
|
|
41
|
-
# pixel manipulation wrappers
|
|
42
|
-
|
|
43
|
-
def getPixel(self, col, row):
|
|
44
|
-
"""
|
|
45
|
-
Returns the [r, g, b] color of a given pixel in the image.
|
|
46
|
-
"""
|
|
47
|
-
return self._icon.getPixel(col, row)
|
|
48
|
-
|
|
49
|
-
def setPixel(self, col, row, RGBList):
|
|
50
|
-
"""
|
|
51
|
-
Sets the [r, g, b] color of a given pixel in the image.
|
|
52
|
-
"""
|
|
53
|
-
self._icon.setPixel(col, row, RGBList)
|
|
54
|
-
|
|
55
|
-
def getPixels(self):
|
|
56
|
-
"""
|
|
57
|
-
Returns the [r, g, b] color of all pixels in the icon as a 2-dimensional array.
|
|
58
|
-
"""
|
|
59
|
-
return self._icon.getPixels()
|
|
60
|
-
|
|
61
|
-
def setPixels(self, pixels):
|
|
62
|
-
"""
|
|
63
|
-
Sets the [r, g, b] color of all pixels in the icon from a 2-dimensional array.
|
|
64
|
-
"""
|
|
65
|
-
self._icon.setPixels(pixels)
|
|
66
|
-
|
|
67
|
-
###### Unit Tests ###################################
|
|
68
|
-
|
|
69
|
-
if __name__ == "__main__":
|
|
70
|
-
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/ExtendedNote.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/Measurement.py
RENAMED
|
File without changes
|
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/PianoRollOld.py
RENAMED
|
File without changes
|
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/test_ExtendedNote.py
RENAMED
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/test_Histogram.py
RENAMED
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/test_Measurement.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/data/test_PianoRoll_unit.py
RENAMED
|
File without changes
|
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/ZipfMetrics.py
RENAMED
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/simple/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/metrics/test_Metric.py
RENAMED
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/utilities/CSVWriter.py
RENAMED
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/utilities/PowerLawRandom.py
RENAMED
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython/nevmuse/utilities/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{creativepython-1.1.1 → creativepython-1.1.2}/src/CreativePython.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|