1
|
|
|
""" |
2
|
|
|
libtcod works with a special 'root' console. You create this console using |
3
|
|
|
the :any:`tcod.console_init_root` function. Usually after setting the font |
4
|
|
|
with :any:`console_set_custom_font` first. |
5
|
|
|
|
6
|
|
|
Example:: |
7
|
|
|
|
8
|
|
|
# Make sure 'arial10x10.png' is in the same directory as this script. |
9
|
|
|
import time |
10
|
|
|
|
11
|
|
|
import tcod |
12
|
|
|
|
13
|
|
|
# Setup the font. |
14
|
|
|
tcod.console_set_custom_font( |
15
|
|
|
'arial10x10.png', |
16
|
|
|
tcod.FONT_LAYOUT_ASCII_INROW | tcod.FONT_LAYOUT_TCOD, |
17
|
|
|
) |
18
|
|
|
# Initialize the root console in a context. |
19
|
|
|
with tcod.console_init_root(80, 60, 'title') as root_console: |
20
|
|
|
root_console.print_(x=0, y=0, string='Hello World!') |
21
|
|
|
tcod.console_flush() # Show the console. |
22
|
|
|
time.sleep(3) # Wait 3 seconds. |
23
|
|
|
# The window is closed here, after the above context exits. |
24
|
|
|
""" |
25
|
|
|
|
26
|
|
|
from __future__ import absolute_import |
27
|
|
|
|
28
|
|
|
import sys |
29
|
|
|
|
30
|
|
|
import warnings |
31
|
|
|
|
32
|
|
|
import numpy as np |
|
|
|
|
33
|
|
|
|
34
|
|
|
import tcod.libtcod |
35
|
|
|
from tcod.libtcod import ffi, lib |
36
|
|
|
import tcod._internal |
37
|
|
|
|
38
|
|
|
if sys.version_info[0] == 2: # Python 2 |
39
|
|
|
def _fmt(string): |
|
|
|
|
40
|
|
|
if not isinstance(string, unicode): |
|
|
|
|
41
|
|
|
string = string.decode('latin-1') |
42
|
|
|
return string.replace(u'%', u'%%') |
43
|
|
|
else: |
44
|
|
|
def _fmt(string): |
45
|
|
|
"""Return a string that escapes 'C printf' side effects.""" |
46
|
|
|
return string.replace('%', '%%') |
47
|
|
|
|
48
|
|
|
|
49
|
|
|
class Console(object): |
50
|
|
|
"""A console object containing a grid of characters with |
51
|
|
|
foreground/background colors. |
52
|
|
|
|
53
|
|
|
.. versionchanged:: 4.3 |
54
|
|
|
Added `order` parameter. |
55
|
|
|
|
56
|
|
|
Args: |
57
|
|
|
width (int): Width of the new Console. |
58
|
|
|
height (int): Height of the new Console. |
59
|
|
|
order (str): Which numpy memory order to use. |
60
|
|
|
|
61
|
|
|
Attributes: |
62
|
|
|
console_c (CData): A cffi pointer to a TCOD_console_t object. |
63
|
|
|
""" |
64
|
|
|
|
65
|
|
|
def __init__(self, width, height, order='C'): |
66
|
|
|
self._key_color = None |
67
|
|
|
self._ch = np.zeros((height, width), dtype=np.intc) |
68
|
|
|
self._fg = np.zeros((height, width), dtype='(3,)u1') |
69
|
|
|
self._bg = np.zeros((height, width), dtype='(3,)u1') |
70
|
|
|
if tcod._internal.verify_order(order) == 'F': |
71
|
|
|
self._ch = self._ch.transpose() |
72
|
|
|
self._fg = self._fg.transpose(1, 0, 2) |
73
|
|
|
self._bg = self._bg.transpose(1, 0, 2) |
74
|
|
|
|
75
|
|
|
# libtcod uses the root console for defaults. |
76
|
|
|
bkgnd_flag = alignment = 0 |
77
|
|
|
if lib.TCOD_ctx.root != ffi.NULL: |
78
|
|
|
bkgnd_flag = lib.TCOD_ctx.root.bkgnd_flag |
79
|
|
|
alignment = lib.TCOD_ctx.root.alignment |
80
|
|
|
|
81
|
|
|
self._console_data = self.console_c = ffi.new( |
82
|
|
|
'TCOD_console_data_t*', |
83
|
|
|
{ |
84
|
|
|
'w': width, 'h': height, |
85
|
|
|
'ch_array': ffi.cast('int*', self._ch.ctypes.data), |
86
|
|
|
'fg_array': ffi.cast('TCOD_color_t*', self._fg.ctypes.data), |
87
|
|
|
'bg_array': ffi.cast('TCOD_color_t*', self._bg.ctypes.data), |
88
|
|
|
'bkgnd_flag': bkgnd_flag, |
89
|
|
|
'alignment': alignment, |
90
|
|
|
'fore': (255, 255, 255), |
91
|
|
|
'back': (0, 0, 0), |
92
|
|
|
}, |
93
|
|
|
) |
94
|
|
|
|
95
|
|
|
@classmethod |
96
|
|
|
def _from_cdata(cls, cdata, order='C'): |
|
|
|
|
97
|
|
|
if isinstance(cdata, cls): |
98
|
|
|
return cdata |
99
|
|
|
self = object.__new__(cls) |
100
|
|
|
self.console_c = cdata |
101
|
|
|
self._init_setup_console_data(order) |
102
|
|
|
return self |
103
|
|
|
|
104
|
|
|
def _init_setup_console_data(self, order='C'): |
105
|
|
|
"""Setup numpy arrays over libtcod data buffers.""" |
106
|
|
|
self._key_color = None |
107
|
|
|
if self.console_c == ffi.NULL: |
108
|
|
|
self._console_data = lib.TCOD_ctx.root |
109
|
|
|
else: |
110
|
|
|
self._console_data = ffi.cast('TCOD_console_data_t *', self.console_c) |
|
|
|
|
111
|
|
|
|
112
|
|
|
def unpack_color(color_data): |
113
|
|
|
"""return a (height, width, 3) shaped array from an image struct""" |
114
|
|
|
color_buffer = ffi.buffer(color_data[0:self.width * self.height]) |
115
|
|
|
array = np.frombuffer(color_buffer, np.uint8) |
116
|
|
|
return array.reshape((self.height, self.width, 3)) |
117
|
|
|
|
118
|
|
|
self._fg = unpack_color(self._console_data.fg_array) |
119
|
|
|
self._bg = unpack_color(self._console_data.bg_array) |
120
|
|
|
|
121
|
|
|
buf = self._console_data.ch_array |
122
|
|
|
buf = ffi.buffer(buf[0:self.width * self.height]) |
123
|
|
|
self._ch = np.frombuffer(buf, np.intc).reshape((self.height, |
124
|
|
|
self.width)) |
125
|
|
|
|
126
|
|
|
order = tcod._internal.verify_order(order) |
127
|
|
|
if order == 'F': |
128
|
|
|
self._fg = self._fg.transpose(1, 0, 2) |
129
|
|
|
self._bg = self._bg.transpose(1, 0, 2) |
130
|
|
|
self._ch = self._ch.transpose() |
131
|
|
|
|
132
|
|
|
@property |
133
|
|
|
def width(self): |
134
|
|
|
"""int: The width of this Console. (read-only)""" |
135
|
|
|
return lib.TCOD_console_get_width(self.console_c) |
136
|
|
|
|
137
|
|
|
@property |
138
|
|
|
def height(self): |
139
|
|
|
"""int: The height of this Console. (read-only)""" |
140
|
|
|
return lib.TCOD_console_get_height(self.console_c) |
141
|
|
|
|
142
|
|
|
@property |
143
|
|
|
def bg(self): |
|
|
|
|
144
|
|
|
"""A uint8 array with the shape (height, width, 3). |
145
|
|
|
|
146
|
|
|
You can change the consoles background colors by using this array. |
147
|
|
|
|
148
|
|
|
Index this array with ``console.bg[y, x, channel]`` |
149
|
|
|
""" |
150
|
|
|
return self._bg |
151
|
|
|
|
152
|
|
|
@property |
153
|
|
|
def fg(self): |
|
|
|
|
154
|
|
|
"""A uint8 array with the shape (height, width, 3). |
155
|
|
|
|
156
|
|
|
You can change the consoles foreground colors by using this array. |
157
|
|
|
|
158
|
|
|
Index this array with ``console.fg[y, x, channel]`` |
159
|
|
|
""" |
160
|
|
|
return self._fg |
161
|
|
|
|
162
|
|
|
@property |
163
|
|
|
def ch(self): |
|
|
|
|
164
|
|
|
"""An integer array with the shape (height, width). |
165
|
|
|
|
166
|
|
|
You can change the consoles character codes by using this array. |
167
|
|
|
|
168
|
|
|
Index this array with ``console.ch[y, x]`` |
169
|
|
|
""" |
170
|
|
|
return self._ch |
171
|
|
|
|
172
|
|
|
@property |
173
|
|
|
def default_bg(self): |
174
|
|
|
"""Tuple[int, int, int]: The default background color.""" |
175
|
|
|
color = self._console_data.back |
176
|
|
|
return color.r, color.g, color.b |
177
|
|
|
@default_bg.setter |
178
|
|
|
def default_bg(self, color): |
|
|
|
|
179
|
|
|
self._console_data.back = color |
180
|
|
|
|
181
|
|
|
@property |
182
|
|
|
def default_fg(self): |
183
|
|
|
"""Tuple[int, int, int]: The default foreground color.""" |
184
|
|
|
color = self._console_data.fore |
185
|
|
|
return color.r, color.g, color.b |
186
|
|
|
@default_fg.setter |
187
|
|
|
def default_fg(self, color): |
|
|
|
|
188
|
|
|
self._console_data.fore = color |
189
|
|
|
|
190
|
|
|
@property |
191
|
|
|
def default_bg_blend(self): |
192
|
|
|
"""int: The default blending mode.""" |
193
|
|
|
return self._console_data.bkgnd_flag |
194
|
|
|
@default_bg_blend.setter |
195
|
|
|
def default_bg_blend(self, value): |
|
|
|
|
196
|
|
|
self._console_data.bkgnd_flag = value |
197
|
|
|
|
198
|
|
|
@property |
199
|
|
|
def default_alignment(self): |
200
|
|
|
"""int: The default text alignment.""" |
201
|
|
|
return self._console_data.alignment |
202
|
|
|
@default_alignment.setter |
203
|
|
|
def default_alignment(self, value): |
|
|
|
|
204
|
|
|
self._console_data.alignment = value |
205
|
|
|
|
206
|
|
|
def clear(self): |
207
|
|
|
"""Reset this console to its default colors and the space character. |
208
|
|
|
""" |
209
|
|
|
lib.TCOD_console_clear(self.console_c) |
210
|
|
|
|
211
|
|
|
def put_char(self, x, y, ch, bg_blend=tcod.libtcod.BKGND_DEFAULT): |
|
|
|
|
212
|
|
|
"""Draw the character c at x,y using the default colors and a blend mode. |
|
|
|
|
213
|
|
|
|
214
|
|
|
Args: |
215
|
|
|
x (int): The x coordinate from the left. |
216
|
|
|
y (int): The y coordinate from the top. |
217
|
|
|
ch (int): Character code to draw. Must be in integer form. |
218
|
|
|
bg_blend (int): Blending mode to use, defaults to BKGND_DEFAULT. |
219
|
|
|
""" |
220
|
|
|
lib.TCOD_console_put_char(self.console_c, x, y, ch, bg_blend) |
221
|
|
|
|
222
|
|
|
def print_(self, x, y, string, bg_blend=tcod.libtcod.BKGND_DEFAULT, |
|
|
|
|
223
|
|
|
alignment=None): |
224
|
|
|
"""Print a color formatted string on a console. |
225
|
|
|
|
226
|
|
|
Args: |
227
|
|
|
x (int): The x coordinate from the left. |
228
|
|
|
y (int): The y coordinate from the top. |
229
|
|
|
string (Text): A Unicode string optionaly using color codes. |
230
|
|
|
bg_blend (int): Blending mode to use, defaults to BKGND_DEFAULT. |
231
|
|
|
alignment (Optinal[int]): Text alignment. |
232
|
|
|
""" |
233
|
|
|
alignment = self.default_alignment if alignment is None else alignment |
234
|
|
|
|
235
|
|
|
lib.TCOD_console_print_ex_utf(self.console_c, x, y, |
236
|
|
|
bg_blend, alignment, _fmt(string)) |
237
|
|
|
|
238
|
|
|
def print_rect(self, x, y, width, height, string, |
|
|
|
|
239
|
|
|
bg_blend=tcod.libtcod.BKGND_DEFAULT, alignment=None): |
|
|
|
|
240
|
|
|
"""Print a string constrained to a rectangle. |
241
|
|
|
|
242
|
|
|
If h > 0 and the bottom of the rectangle is reached, |
243
|
|
|
the string is truncated. If h = 0, |
244
|
|
|
the string is only truncated if it reaches the bottom of the console. |
245
|
|
|
|
246
|
|
|
Args: |
247
|
|
|
x (int): The x coordinate from the left. |
248
|
|
|
y (int): The y coordinate from the top. |
249
|
|
|
width (int): Maximum width to render the text. |
250
|
|
|
height (int): Maximum lines to render the text. |
251
|
|
|
string (Text): A Unicode string. |
252
|
|
|
bg_blend (int): Background blending flag. |
253
|
|
|
alignment (Optional[int]): Alignment flag. |
254
|
|
|
|
255
|
|
|
Returns: |
256
|
|
|
int: The number of lines of text once word-wrapped. |
257
|
|
|
""" |
258
|
|
|
alignment = self.default_alignment if alignment is None else alignment |
259
|
|
|
return lib.TCOD_console_print_rect_ex_utf(self.console_c, |
260
|
|
|
x, y, width, height, bg_blend, alignment, _fmt(string)) |
261
|
|
|
|
262
|
|
|
def get_height_rect(self, x, y, width, height, string): |
|
|
|
|
263
|
|
|
"""Return the height of this text word-wrapped into this rectangle. |
264
|
|
|
|
265
|
|
|
Args: |
266
|
|
|
x (int): The x coordinate from the left. |
267
|
|
|
y (int): The y coordinate from the top. |
268
|
|
|
width (int): Maximum width to render the text. |
269
|
|
|
height (int): Maximum lines to render the text. |
270
|
|
|
string (Text): A Unicode string. |
271
|
|
|
|
272
|
|
|
Returns: |
273
|
|
|
int: The number of lines of text once word-wrapped. |
274
|
|
|
""" |
275
|
|
|
return lib.TCOD_console_get_height_rect_utf( |
276
|
|
|
self.console_c, x, y, width, height, _fmt(string)) |
277
|
|
|
|
278
|
|
|
def rect(self, x, y, width, height, clear, |
|
|
|
|
279
|
|
|
bg_blend=tcod.libtcod.BKGND_DEFAULT): |
|
|
|
|
280
|
|
|
"""Draw a the background color on a rect optionally clearing the text. |
281
|
|
|
|
282
|
|
|
If clr is True the affected tiles are changed to space character. |
283
|
|
|
|
284
|
|
|
Args: |
285
|
|
|
x (int): The x coordinate from the left. |
286
|
|
|
y (int): The y coordinate from the top. |
287
|
|
|
width (int): Maximum width to render the text. |
288
|
|
|
height (int): Maximum lines to render the text. |
289
|
|
|
clear (bool): If True all text in the affected area will be |
290
|
|
|
removed. |
291
|
|
|
bg_blend (int): Background blending flag. |
292
|
|
|
""" |
293
|
|
|
lib.TCOD_console_rect(self.console_c, x, y, width, height, clear, |
294
|
|
|
bg_blend) |
295
|
|
|
|
296
|
|
|
def hline(self, x, y, width, bg_blend=tcod.libtcod.BKGND_DEFAULT): |
|
|
|
|
297
|
|
|
"""Draw a horizontal line on the console. |
298
|
|
|
|
299
|
|
|
This always uses the character 196, the horizontal line character. |
300
|
|
|
|
301
|
|
|
Args: |
302
|
|
|
x (int): The x coordinate from the left. |
303
|
|
|
y (int): The y coordinate from the top. |
304
|
|
|
width (int): The horozontal length of this line. |
305
|
|
|
bg_blend (int): The background blending flag. |
306
|
|
|
""" |
307
|
|
|
lib.TCOD_console_hline(self.console_c, x, y, width, bg_blend) |
308
|
|
|
|
309
|
|
|
def vline(self, x, y, height, bg_blend=tcod.libtcod.BKGND_DEFAULT): |
|
|
|
|
310
|
|
|
"""Draw a vertical line on the console. |
311
|
|
|
|
312
|
|
|
This always uses the character 179, the vertical line character. |
313
|
|
|
|
314
|
|
|
Args: |
315
|
|
|
x (int): The x coordinate from the left. |
316
|
|
|
y (int): The y coordinate from the top. |
317
|
|
|
height (int): The horozontal length of this line. |
318
|
|
|
bg_blend (int): The background blending flag. |
319
|
|
|
""" |
320
|
|
|
lib.TCOD_console_vline(self.console_c, x, y, height, bg_blend) |
321
|
|
|
|
322
|
|
|
def print_frame(self, x, y, width, height, string='', |
|
|
|
|
323
|
|
|
clear=True, bg_blend=tcod.libtcod.BKGND_DEFAULT): |
|
|
|
|
324
|
|
|
"""Draw a framed rectangle with optinal text. |
325
|
|
|
|
326
|
|
|
This uses the default background color and blend mode to fill the |
327
|
|
|
rectangle and the default foreground to draw the outline. |
328
|
|
|
|
329
|
|
|
string will be printed on the inside of the rectangle, word-wrapped. |
330
|
|
|
|
331
|
|
|
Args: |
332
|
|
|
x (int): The x coordinate from the left. |
333
|
|
|
y (int): The y coordinate from the top. |
334
|
|
|
width (int): The width if the frame. |
335
|
|
|
height (int): The height of the frame. |
336
|
|
|
string (Text): A Unicode string to print. |
337
|
|
|
clear (bool): If True all text in the affected area will be |
338
|
|
|
removed. |
339
|
|
|
bg_blend (int): The background blending flag. |
340
|
|
|
|
341
|
|
|
Note: |
342
|
|
|
This method does not support Unicode outside of the 0-255 range. |
343
|
|
|
""" |
344
|
|
|
lib.TCOD_console_print_frame(self.console_c, x, y, width, height, |
345
|
|
|
clear, bg_blend, string.encode('latin-1')) |
346
|
|
|
|
347
|
|
|
def blit(self, dest, dest_x=0, dest_y=0, |
348
|
|
|
src_x=0, src_y=0, width=0, height=0, |
349
|
|
|
fg_alpha=1.0, bg_alpha=1.0, key_color=None): |
350
|
|
|
"""Blit from this console onto the ``dest`` console. |
351
|
|
|
|
352
|
|
|
Args: |
353
|
|
|
dest (Console): The destintaion console to blit onto. |
354
|
|
|
dest_x (int): Leftmost coordinate of the destintaion console. |
355
|
|
|
dest_y (int): Topmost coordinate of the destintaion console. |
356
|
|
|
src_x (int): X coordinate from this console to blit, from the left. |
357
|
|
|
src_y (int): Y coordinate from this console to blit, from the top. |
358
|
|
|
width (int): The width of the region to blit. |
359
|
|
|
|
360
|
|
|
If this is 0 the maximum possible width will be used. |
361
|
|
|
height (int): The height of the region to blit. |
362
|
|
|
|
363
|
|
|
If this is 0 the maximum possible height will be used. |
364
|
|
|
fg_alpha (float): Foreground color alpha vaule. |
365
|
|
|
bg_alpha (float): Background color alpha vaule. |
366
|
|
|
key_color (Optional[Tuple[int, int, int]]): |
367
|
|
|
None, or a (red, green, blue) tuple with values of 0-255. |
368
|
|
|
|
369
|
|
|
.. versionchanged:: 4.0 |
370
|
|
|
Parameters were rearraged and made optional. |
371
|
|
|
|
372
|
|
|
Previously they were: |
373
|
|
|
`(x, y, width, height, dest, dest_x, dest_y, *)` |
374
|
|
|
""" |
375
|
|
|
# The old syntax is easy to detect and correct. |
376
|
|
|
if hasattr(src_y, 'console_c'): |
377
|
|
|
src_x, src_y, width, height, dest, dest_x, dest_y = \ |
378
|
|
|
dest, dest_x, dest_y, src_x, src_y, width, height |
379
|
|
|
warnings.warn( |
380
|
|
|
"Parameter names have been moved around, see documentation.", |
381
|
|
|
DeprecationWarning, |
382
|
|
|
) |
383
|
|
|
|
384
|
|
|
if key_color or self._key_color: |
385
|
|
|
key_color = ffi.new('TCOD_color_t*', key_color) |
386
|
|
|
lib.TCOD_console_blit_key_color( |
387
|
|
|
self.console_c, src_x, src_y, width, height, |
388
|
|
|
dest.console_c, dest_x, dest_y, fg_alpha, bg_alpha, key_color |
|
|
|
|
389
|
|
|
) |
390
|
|
|
else: |
391
|
|
|
lib.TCOD_console_blit( |
392
|
|
|
self.console_c, src_x, src_y, width, height, |
393
|
|
|
dest.console_c, dest_x, dest_y, fg_alpha, bg_alpha |
|
|
|
|
394
|
|
|
) |
395
|
|
|
|
396
|
|
|
def set_key_color(self, color): |
397
|
|
|
"""Set a consoles blit transparent color. |
398
|
|
|
|
399
|
|
|
Args: |
400
|
|
|
color (Tuple[int, int, int]): |
401
|
|
|
""" |
402
|
|
|
self._key_color = color |
403
|
|
|
|
404
|
|
|
def __enter__(self): |
405
|
|
|
"""Returns this console in a managed context. |
406
|
|
|
|
407
|
|
|
When the root console is used as a context, the graphical window will |
408
|
|
|
close once the context is left as if :any:`tcod.console_delete` was |
409
|
|
|
called on it. |
410
|
|
|
|
411
|
|
|
This is useful for some Python IDE's like IDLE, where the window would |
412
|
|
|
not be closed on its own otherwise. |
413
|
|
|
""" |
414
|
|
|
if self.console_c != ffi.NULL: |
415
|
|
|
raise NotImplementedError('Only the root console has a context.') |
416
|
|
|
return self |
417
|
|
|
|
418
|
|
|
def __exit__(self, *args): |
419
|
|
|
"""Closes the graphical window on exit. |
420
|
|
|
|
421
|
|
|
Some tcod functions may have undefined behavior after this point. |
422
|
|
|
""" |
423
|
|
|
lib.TCOD_console_delete(self.console_c) |
424
|
|
|
|
425
|
|
|
def __bool__(self): |
426
|
|
|
"""Returns False if this is the root console. |
427
|
|
|
|
428
|
|
|
This mimics libtcodpy behavior. |
429
|
|
|
""" |
430
|
|
|
return self.console_c != ffi.NULL |
431
|
|
|
|
432
|
|
|
__nonzero__ = __bool__ |
433
|
|
|
|
434
|
|
|
def __getstate__(self): |
435
|
|
|
state = self.__dict__.copy() |
436
|
|
|
del state['console_c'] |
437
|
|
|
state['_console_data'] = { |
438
|
|
|
'w': self.width, 'h': self.height, |
439
|
|
|
'bkgnd_flag': self.default_bg_blend, |
440
|
|
|
'alignment': self.default_alignment, |
441
|
|
|
'fore': self.default_fg, |
442
|
|
|
'back': self.default_bg, |
443
|
|
|
} |
444
|
|
|
if self.console_c == ffi.NULL: |
445
|
|
|
state['_ch'] = np.copy(self._ch) |
446
|
|
|
state['_fg'] = np.copy(self._fg) |
447
|
|
|
state['_bg'] = np.copy(self._bg) |
448
|
|
|
return state |
449
|
|
|
|
450
|
|
|
def __setstate__(self, state): |
451
|
|
|
self._key_color = None |
452
|
|
|
self.__dict__.update(state) |
453
|
|
|
self._console_data.update( |
454
|
|
|
{ |
455
|
|
|
'ch_array': ffi.cast('int*', self._ch.ctypes.data), |
456
|
|
|
'fg_array': ffi.cast('TCOD_color_t*', self._fg.ctypes.data), |
457
|
|
|
'bg_array': ffi.cast('TCOD_color_t*', self._bg.ctypes.data), |
458
|
|
|
} |
459
|
|
|
) |
460
|
|
|
self._console_data = self.console_c = ffi.new( |
461
|
|
|
'TCOD_console_data_t*', self._console_data) |
462
|
|
|
|
This can be caused by one of the following:
1. Missing Dependencies
This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.
2. Missing __init__.py files
This error could also result from missing
__init__.py
files in your module folders. Make sure that you place one file in each sub-folder.