prefab 1.1.2__py3-none-any.whl → 1.1.3__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.
prefab/__init__.py CHANGED
@@ -5,9 +5,9 @@ Usage:
5
5
  import prefab as pf
6
6
  """
7
7
 
8
- __version__ = "1.1.2"
8
+ __version__ = "1.1.3"
9
9
 
10
- from . import compare, geometry, read, shapes
10
+ from . import compare, geometry, predict, read, shapes
11
11
  from .device import BufferSpec, Device
12
12
  from .models import models
13
13
 
@@ -15,6 +15,7 @@ __all__ = [
15
15
  "Device",
16
16
  "BufferSpec",
17
17
  "geometry",
18
+ "predict",
18
19
  "read",
19
20
  "shapes",
20
21
  "compare",
prefab/device.py CHANGED
@@ -1,27 +1,21 @@
1
1
  """Provides the Device class for representing photonic devices."""
2
2
 
3
- import base64
4
- import io
5
- import json
6
- import os
7
3
  from typing import Optional
8
4
 
9
5
  import cv2
10
6
  import gdstk
11
7
  import matplotlib.pyplot as plt
12
8
  import numpy as np
13
- import requests
14
- import toml
15
9
  from matplotlib.axes import Axes
16
10
  from matplotlib.patches import Rectangle
17
11
  from PIL import Image
18
12
  from pydantic import BaseModel, Field, conint, root_validator, validator
19
13
  from scipy.ndimage import distance_transform_edt
20
14
  from skimage import measure
21
- from tqdm import tqdm
22
15
 
23
16
  from . import compare, geometry
24
17
  from .models import Model
18
+ from .predict import predict_array
25
19
 
26
20
  Image.MAX_IMAGE_PIXELS = None
27
21
 
@@ -210,147 +204,6 @@ class Device(BaseModel):
210
204
  or np.array_equal(unique_values, [1])
211
205
  )
212
206
 
213
- def _encode_array(self, array):
214
- image = Image.fromarray(np.uint8(array * 255))
215
- buffered = io.BytesIO()
216
- image.save(buffered, format="PNG")
217
- encoded_png = base64.b64encode(buffered.getvalue()).decode("utf-8")
218
- return encoded_png
219
-
220
- def _decode_array(self, encoded_png):
221
- binary_data = base64.b64decode(encoded_png)
222
- image = Image.open(io.BytesIO(binary_data))
223
- return np.array(image) / 255
224
-
225
- def _predict_array(
226
- self,
227
- model: Model,
228
- model_type: str,
229
- binarize: bool,
230
- gpu: bool = False,
231
- ) -> "Device":
232
- try:
233
- with open(os.path.expanduser("~/.prefab.toml")) as file:
234
- content = file.readlines()
235
- access_token = None
236
- refresh_token = None
237
- for line in content:
238
- if "access_token" in line:
239
- access_token = line.split("=")[1].strip().strip('"')
240
- if "refresh_token" in line:
241
- refresh_token = line.split("=")[1].strip().strip('"')
242
- break
243
- if not access_token or not refresh_token:
244
- raise ValueError("Token not found in the configuration file.")
245
- except FileNotFoundError:
246
- raise FileNotFoundError(
247
- "Could not validate user.\n"
248
- "Please update prefab using: pip install --upgrade prefab.\n"
249
- "Signup/login and generate a new token.\n"
250
- "See https://www.prefabphotonics.com/docs/guides/quickstart."
251
- ) from None
252
-
253
- headers = {
254
- "Authorization": f"Bearer {access_token}",
255
- "X-Refresh-Token": refresh_token,
256
- }
257
-
258
- predict_data = {
259
- "device_array": self._encode_array(self.device_array[:, :, 0]),
260
- "model": model.to_json(),
261
- "model_type": model_type,
262
- "binary": binarize,
263
- }
264
- json_data = json.dumps(predict_data)
265
-
266
- endpoint_url = (
267
- "https://prefab-photonics--predict-gpu-v1.modal.run"
268
- if gpu
269
- else "https://prefab-photonics--predict-v1.modal.run"
270
- )
271
-
272
- try:
273
- with requests.post(
274
- endpoint_url, data=json_data, headers=headers, stream=True
275
- ) as response:
276
- response.raise_for_status()
277
- event_type = None
278
- model_descriptions = {
279
- "p": "Prediction",
280
- "c": "Correction",
281
- "s": "SEMulate",
282
- }
283
- progress_bar = tqdm(
284
- total=100,
285
- desc=f"{model_descriptions[model_type]}",
286
- unit="%",
287
- colour="green",
288
- bar_format="{l_bar}{bar:30}{r_bar}{bar:-10b}",
289
- )
290
-
291
- for line in response.iter_lines():
292
- if line:
293
- decoded_line = line.decode("utf-8").strip()
294
- if decoded_line.startswith("event:"):
295
- event_type = decoded_line.split(":")[1].strip()
296
- elif decoded_line.startswith("data:"):
297
- try:
298
- data_content = json.loads(
299
- decoded_line.split("data: ")[1]
300
- )
301
- if event_type == "progress":
302
- progress = round(100 * data_content["progress"])
303
- progress_bar.update(progress - progress_bar.n)
304
- elif event_type == "result":
305
- results = []
306
- for key in sorted(data_content.keys()):
307
- if key.startswith("result"):
308
- decoded_image = self._decode_array(
309
- data_content[key]
310
- )
311
- results.append(decoded_image)
312
-
313
- if results:
314
- prediction = np.stack(results, axis=-1)
315
- if binarize:
316
- prediction = geometry.binarize_hard(
317
- prediction
318
- )
319
- progress_bar.close()
320
- return prediction
321
- elif event_type == "end":
322
- print("Stream ended.")
323
- progress_bar.close()
324
- break
325
- elif event_type == "auth":
326
- if "new_refresh_token" in data_content["auth"]:
327
- prefab_file_path = os.path.expanduser(
328
- "~/.prefab.toml"
329
- )
330
- with open(
331
- prefab_file_path, "w", encoding="utf-8"
332
- ) as toml_file:
333
- toml.dump(
334
- {
335
- "access_token": data_content[
336
- "auth"
337
- ]["new_access_token"],
338
- "refresh_token": data_content[
339
- "auth"
340
- ]["new_refresh_token"],
341
- },
342
- toml_file,
343
- )
344
- elif event_type == "error":
345
- raise ValueError(f"{data_content['error']}")
346
- except json.JSONDecodeError:
347
- raise ValueError(
348
- "Failed to decode JSON:",
349
- decoded_line.split("data: ")[1],
350
- ) from None
351
- except requests.RequestException as e:
352
- raise RuntimeError(f"Request failed: {e}") from e
353
-
354
207
  def predict(
355
208
  self,
356
209
  model: Model,
@@ -393,7 +246,8 @@ class Device(BaseModel):
393
246
  If the prediction service returns an error or if the response from the
394
247
  service cannot be processed correctly.
395
248
  """
396
- prediction_array = self._predict_array(
249
+ prediction_array = predict_array(
250
+ device_array=self.device_array,
397
251
  model=model,
398
252
  model_type="p",
399
253
  binarize=binarize,
@@ -445,7 +299,8 @@ class Device(BaseModel):
445
299
  If the correction service returns an error or if the response from the
446
300
  service cannot be processed correctly.
447
301
  """
448
- correction_array = self._predict_array(
302
+ correction_array = predict_array(
303
+ device_array=self.device_array,
449
304
  model=model,
450
305
  model_type="c",
451
306
  binarize=binarize,
@@ -487,7 +342,8 @@ class Device(BaseModel):
487
342
  A new instance of the Device class with its geometry transformed to simulate
488
343
  an SEM image style.
489
344
  """
490
- semulated_array = self._predict_array(
345
+ semulated_array = predict_array(
346
+ device_array=self.device_array,
491
347
  model=model,
492
348
  model_type="s",
493
349
  binarize=False,
@@ -550,6 +406,7 @@ class Device(BaseModel):
550
406
  cell_name: str = "prefab_device",
551
407
  gds_layer: tuple[int, int] = (1, 0),
552
408
  contour_approx_mode: int = 2,
409
+ origin: tuple[float, float] = (0.0, 0.0),
553
410
  ):
554
411
  """
555
412
  Exports the device geometry as a GDSII file.
@@ -572,11 +429,15 @@ class Device(BaseModel):
572
429
  The mode of contour approximation used during the conversion. Defaults to 2,
573
430
  which corresponds to `cv2.CHAIN_APPROX_SIMPLE`, a method that compresses
574
431
  horizontal, vertical, and diagonal segments and leaves only their endpoints.
432
+ origin : tuple[float, float], optional
433
+ The x and y coordinates of the origin for the GDSII export. Defaults to
434
+ (0.0, 0.0).
575
435
  """
576
436
  gdstk_cell = self.flatten()._device_to_gdstk(
577
437
  cell_name=cell_name,
578
438
  gds_layer=gds_layer,
579
439
  contour_approx_mode=contour_approx_mode,
440
+ origin=origin,
580
441
  )
581
442
  print(f"Saving GDS to '{gds_path}'...")
582
443
  gdstk_library = gdstk.Library()
@@ -588,6 +449,7 @@ class Device(BaseModel):
588
449
  cell_name: str = "prefab_device",
589
450
  gds_layer: tuple[int, int] = (1, 0),
590
451
  contour_approx_mode: int = 2,
452
+ origin: tuple[float, float] = (0.0, 0.0),
591
453
  ):
592
454
  """
593
455
  Converts the device geometry to a GDSTK cell object.
@@ -607,6 +469,9 @@ class Device(BaseModel):
607
469
  The mode of contour approximation used during the conversion. Defaults to 2,
608
470
  which corresponds to `cv2.CHAIN_APPROX_SIMPLE`, a method that compresses
609
471
  horizontal, vertical, and diagonal segments and leaves only their endpoints.
472
+ origin : tuple[float, float], optional
473
+ The x and y coordinates of the origin for the GDSTK cell. Defaults to
474
+ (0.0, 0.0).
610
475
 
611
476
  Returns
612
477
  -------
@@ -618,6 +483,7 @@ class Device(BaseModel):
618
483
  cell_name=cell_name,
619
484
  gds_layer=gds_layer,
620
485
  contour_approx_mode=contour_approx_mode,
486
+ origin=origin,
621
487
  )
622
488
  return gdstk_cell
623
489
 
@@ -626,6 +492,7 @@ class Device(BaseModel):
626
492
  cell_name: str,
627
493
  gds_layer: tuple[int, int],
628
494
  contour_approx_mode: int,
495
+ origin: tuple[float, float],
629
496
  ) -> gdstk.Cell:
630
497
  approx_mode_mapping = {
631
498
  1: cv2.CHAIN_APPROX_NONE,
@@ -662,8 +529,21 @@ class Device(BaseModel):
662
529
  polygons_to_process = hierarchy_polygons[level]
663
530
 
664
531
  if polygons_to_process:
532
+ center_x_nm = self.device_array.shape[1] / 2
533
+ center_y_nm = self.device_array.shape[0] / 2
534
+
535
+ center_x_um = center_x_nm / 1000
536
+ center_y_um = center_y_nm / 1000
537
+
538
+ adjusted_polygons = [
539
+ [
540
+ (x - center_x_um + origin[0], y - center_y_um + origin[1])
541
+ for x, y in polygon
542
+ ]
543
+ for polygon in polygons_to_process
544
+ ]
665
545
  processed_polygons = gdstk.boolean(
666
- polygons_to_process,
546
+ adjusted_polygons,
667
547
  processed_polygons,
668
548
  operation,
669
549
  layer=gds_layer[0],
@@ -1313,13 +1193,6 @@ class Device(BaseModel):
1313
1193
  """
1314
1194
  Trim the device geometry by removing empty space around it.
1315
1195
 
1316
- Parameters
1317
- ----------
1318
- buffer_thickness : dict, optional
1319
- A dictionary specifying the thickness of the buffer to leave around the
1320
- non-zero elements of the array. Should contain keys 'top', 'bottom', 'left',
1321
- 'right'. Defaults to None, which means no buffer is added.
1322
-
1323
1196
  Returns
1324
1197
  -------
1325
1198
  Device
@@ -1414,15 +1287,10 @@ class Device(BaseModel):
1414
1287
  Flatten the device geometry by summing the vertical layers and normalizing the
1415
1288
  result.
1416
1289
 
1417
- Parameters
1418
- ----------
1419
- device_array : np.ndarray
1420
- The input array to be flattened.
1421
-
1422
1290
  Returns
1423
1291
  -------
1424
- np.ndarray
1425
- The flattened array with values scaled between 0 and 1.
1292
+ Device
1293
+ A new instance of the Device with the flattened geometry.
1426
1294
  """
1427
1295
  flattened_device_array = geometry.flatten(device_array=self.device_array)
1428
1296
  return self.model_copy(update={"device_array": flattened_device_array})
prefab/models.py CHANGED
@@ -10,7 +10,7 @@ class Fab(BaseModel):
10
10
  """
11
11
  Represents a fabrication process in the PreFab model library.
12
12
 
13
- Parameters
13
+ Attributes
14
14
  ----------
15
15
  foundry : str
16
16
  The name of the foundry where the fabrication process takes place.
prefab/predict.py ADDED
@@ -0,0 +1,260 @@
1
+ import base64
2
+ import io
3
+ import json
4
+ import os
5
+
6
+ import numpy as np
7
+ import requests
8
+ import toml
9
+ from PIL import Image
10
+ from tqdm import tqdm
11
+
12
+ from .geometry import binarize_hard
13
+ from .models import Model
14
+
15
+ BASE_URL = "https://prefab-photonics--predict"
16
+
17
+
18
+ def predict_array(
19
+ device_array: np.ndarray,
20
+ model: Model,
21
+ model_type: str,
22
+ binarize: bool,
23
+ gpu: bool = False,
24
+ ) -> np.ndarray:
25
+ """
26
+ Predicts the output array for a given device array using a specified model.
27
+
28
+ This function sends the device array to a prediction service, which uses a machine
29
+ learning model to predict the outcome of the nanofabrication process. The prediction
30
+ can be performed on a GPU if specified.
31
+
32
+ Parameters
33
+ ----------
34
+ device_array : np.ndarray
35
+ The input device array to be predicted.
36
+ model : Model
37
+ The model to use for prediction.
38
+ model_type : str
39
+ The type of model to use (e.g., 'p', 'c', 's').
40
+ binarize : bool
41
+ Whether to binarize the output.
42
+ gpu : bool, optional
43
+ Whether to use GPU for prediction. Defaults to False.
44
+
45
+ Returns
46
+ -------
47
+ np.ndarray
48
+ The predicted output array.
49
+
50
+ Raises
51
+ ------
52
+ RuntimeError
53
+ If the request to the prediction service fails.
54
+ """
55
+ headers = _prepare_headers()
56
+ predict_data = _prepare_predict_data(device_array, model, model_type, binarize)
57
+ endpoint_url = f"{BASE_URL}-gpu-v1.modal.run" if gpu else f"{BASE_URL}-v1.modal.run"
58
+
59
+ try:
60
+ with requests.post(
61
+ endpoint_url,
62
+ data=json.dumps(predict_data),
63
+ headers=headers,
64
+ stream=True,
65
+ ) as response:
66
+ response.raise_for_status()
67
+ return _process_response(response, model_type, binarize)
68
+ except requests.RequestException as e:
69
+ raise RuntimeError(f"Request failed: {e}") from e
70
+
71
+
72
+ def predict_array_with_grad(
73
+ device_array: np.ndarray, model: Model, model_type: str
74
+ ) -> tuple[np.ndarray, np.ndarray]:
75
+ """
76
+ Predicts the output array and its gradient for a given device array using a
77
+ specified model.
78
+
79
+ This function sends the device array to a prediction service, which uses a machine
80
+ learning model to predict both the outcome and the gradient of the nanofabrication
81
+ process.
82
+
83
+ Parameters
84
+ ----------
85
+ device_array : np.ndarray
86
+ The input device array to be predicted.
87
+ model : Model
88
+ The model to use for prediction.
89
+ model_type : str
90
+ The type of model to use (e.g., 'p', 'c', 's').
91
+
92
+ Returns
93
+ -------
94
+ tuple[np.ndarray, np.ndarray]
95
+ A tuple containing the predicted output array and its gradient.
96
+
97
+ Raises
98
+ ------
99
+ RuntimeError
100
+ If the request to the prediction service fails.
101
+ """
102
+ headers = _prepare_headers()
103
+ predict_data = _prepare_predict_data(device_array, model, model_type, False)
104
+ endpoint_url = f"{BASE_URL}-with-grad-v1.modal.run"
105
+
106
+ response = requests.post(
107
+ endpoint_url, data=json.dumps(predict_data), headers=headers
108
+ )
109
+ prediction_array = _decode_array(response.json()["prediction_array"])
110
+ gradient_array = _decode_array(response.json()["gradient_array"])
111
+ gradient_min = response.json()["gradient_min"]
112
+ gradient_max = response.json()["gradient_max"]
113
+ gradient_range = gradient_max - gradient_min
114
+ gradient_array = gradient_array * gradient_range + gradient_min
115
+
116
+ return (prediction_array, gradient_array)
117
+
118
+
119
+ def _encode_array(array):
120
+ """Encode a numpy array as a PNG image and return the base64 encoded string."""
121
+ image = Image.fromarray(np.uint8(array * 255))
122
+ buffered = io.BytesIO()
123
+ image.save(buffered, format="PNG")
124
+ encoded_png = base64.b64encode(buffered.getvalue()).decode("utf-8")
125
+ return encoded_png
126
+
127
+
128
+ def _decode_array(encoded_png):
129
+ """Decode a base64 encoded PNG image and return a numpy array."""
130
+ binary_data = base64.b64decode(encoded_png)
131
+ image = Image.open(io.BytesIO(binary_data))
132
+ return np.array(image) / 255
133
+
134
+
135
+ def _read_tokens():
136
+ """Read access and refresh tokens from the configuration file."""
137
+ token_file_path = os.path.expanduser("~/.prefab.toml")
138
+ try:
139
+ with open(token_file_path) as file:
140
+ tokens = toml.load(file)
141
+ access_token = tokens.get("access_token")
142
+ refresh_token = tokens.get("refresh_token")
143
+ if not access_token or not refresh_token:
144
+ raise ValueError("Tokens not found in the configuration file.")
145
+ return access_token, refresh_token
146
+ except FileNotFoundError:
147
+ raise FileNotFoundError(
148
+ "Could not validate user.\n"
149
+ "Please update prefab using: pip install --upgrade prefab.\n"
150
+ "Signup/login and generate a new token.\n"
151
+ "See https://www.prefabphotonics.com/docs/guides/quickstart."
152
+ ) from None
153
+
154
+
155
+ def _prepare_headers():
156
+ """Prepare HTTP headers for the request."""
157
+ access_token, refresh_token = _read_tokens()
158
+ return {
159
+ "Authorization": f"Bearer {access_token}",
160
+ "X-Refresh-Token": refresh_token,
161
+ }
162
+
163
+
164
+ def _prepare_predict_data(device_array, model, model_type, binarize):
165
+ """Prepare the data payload for the prediction request."""
166
+ return {
167
+ "device_array": _encode_array(np.squeeze(device_array)),
168
+ "model": model.to_json(),
169
+ "model_type": model_type,
170
+ "binary": binarize,
171
+ }
172
+
173
+
174
+ def _process_response(response, model_type, binarize):
175
+ """Process the streaming response from the prediction request."""
176
+ event_type = None
177
+ model_descriptions = {
178
+ "p": "Prediction",
179
+ "c": "Correction",
180
+ "s": "SEMulate",
181
+ }
182
+ progress_bar = tqdm(
183
+ total=100,
184
+ desc=model_descriptions.get(model_type, "Processing"),
185
+ unit="%",
186
+ colour="green",
187
+ bar_format="{l_bar}{bar:30}{r_bar}{bar:-10b}",
188
+ )
189
+
190
+ for line in response.iter_lines():
191
+ if line:
192
+ decoded_line = line.decode("utf-8").strip()
193
+ if decoded_line.startswith("event:"):
194
+ event_type = decoded_line.split(":", 1)[1].strip()
195
+ elif decoded_line.startswith("data:"):
196
+ data_content = _parse_data_line(decoded_line)
197
+ result = _handle_event(event_type, data_content, progress_bar, binarize)
198
+ if result is not None:
199
+ progress_bar.close()
200
+ return result
201
+ progress_bar.close()
202
+
203
+
204
+ def _parse_data_line(decoded_line):
205
+ """Parse a data line from the response stream."""
206
+ data_line = decoded_line.split(":", 1)[1].strip()
207
+ try:
208
+ return json.loads(data_line)
209
+ except json.JSONDecodeError:
210
+ raise ValueError(f"Failed to decode JSON: {data_line}") from None
211
+
212
+
213
+ def _handle_event(event_type, data_content, progress_bar, binarize):
214
+ """Handle different types of events received from the server."""
215
+ if event_type == "progress":
216
+ _update_progress(progress_bar, data_content)
217
+ elif event_type == "result":
218
+ return _process_result(data_content, binarize)
219
+ elif event_type == "end":
220
+ print("Stream ended.")
221
+ elif event_type == "auth":
222
+ _update_tokens(data_content.get("auth", {}))
223
+ elif event_type == "error":
224
+ raise ValueError(f"{data_content['error']}")
225
+
226
+
227
+ def _update_progress(progress_bar, data_content):
228
+ """Update the progress bar based on the progress event."""
229
+ progress = round(100 * data_content.get("progress", 0))
230
+ progress_bar.update(progress - progress_bar.n)
231
+
232
+
233
+ def _process_result(data_content, binarize):
234
+ """Process the result event and return the prediction."""
235
+ results = [
236
+ _decode_array(data_content[key])
237
+ for key in sorted(data_content.keys())
238
+ if key.startswith("result")
239
+ ]
240
+ if results:
241
+ prediction = np.stack(results, axis=-1)
242
+ if binarize:
243
+ prediction = binarize_hard(prediction)
244
+ return prediction
245
+
246
+
247
+ def _update_tokens(auth_data):
248
+ """Update tokens if new tokens are provided in the auth event."""
249
+ new_access_token = auth_data.get("new_access_token")
250
+ new_refresh_token = auth_data.get("new_refresh_token")
251
+ if new_access_token and new_refresh_token:
252
+ prefab_file_path = os.path.expanduser("~/.prefab.toml")
253
+ with open(prefab_file_path, "w", encoding="utf-8") as toml_file:
254
+ toml.dump(
255
+ {
256
+ "access_token": new_access_token,
257
+ "refresh_token": new_refresh_token,
258
+ },
259
+ toml_file,
260
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: prefab
3
- Version: 1.1.2
3
+ Version: 1.1.3
4
4
  Summary: Artificial nanofabrication of integrated photonic circuits using deep learning
5
5
  Project-URL: Homepage, https://prefabphotonics.com
6
6
  Project-URL: Repository, https://github.com/PreFab-Photonics/PreFab
@@ -0,0 +1,13 @@
1
+ prefab/__init__.py,sha256=DuARo1QC4MGMw-yzCBLEoRlOU3MsKZTbWDCAqUpQjWM,425
2
+ prefab/__main__.py,sha256=aAgt1WXa44k1nJqsiSD3uAfNeGpwtjWqMUYCHN5_Qrw,2759
3
+ prefab/compare.py,sha256=2MKUT7N2A639tUGCnJHpfF9MmS-v3oARDkTqHbWJ9OM,3239
4
+ prefab/device.py,sha256=l0etqB0559xa9Frb0IKl5N3kU56vWHlwHc-qehINTlw,52292
5
+ prefab/geometry.py,sha256=0sa6ietUWZGkxOnUPUzD3q2QpFuOpWkSANoopGpPd6s,11035
6
+ prefab/models.py,sha256=JpBqNFIqbo1ymKEl0NWWF4ZkhvK23rEVVBEFl05TXCI,3671
7
+ prefab/predict.py,sha256=AuCh_vOMP0dD68u75WQaOTog6TMi1FG3nLQxv6UgIkA,8579
8
+ prefab/read.py,sha256=MuF-cugFQ7MWBJ8DOvQuwktIk0fJ8PXBeLye0ydrB8o,14734
9
+ prefab/shapes.py,sha256=2qaqyNzu5WG3wVdk4oQzeNXmhwXRHcPnRZlgRrM4MoA,25576
10
+ prefab-1.1.3.dist-info/METADATA,sha256=TGbeM7u0kktbeGeOwGIFS43dwp8rWvLSwHDYlXEcsfI,34824
11
+ prefab-1.1.3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
12
+ prefab-1.1.3.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
13
+ prefab-1.1.3.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- prefab/__init__.py,sha256=fbGTDuVA76ol5cM6fXbPAZMB0BCT2zmHD7Pgr9I-zV0,401
2
- prefab/__main__.py,sha256=aAgt1WXa44k1nJqsiSD3uAfNeGpwtjWqMUYCHN5_Qrw,2759
3
- prefab/compare.py,sha256=2MKUT7N2A639tUGCnJHpfF9MmS-v3oARDkTqHbWJ9OM,3239
4
- prefab/device.py,sha256=dpx4dDspruImy__bp3X11y_s-CBJZr6dKpxn8wGtmSA,58314
5
- prefab/geometry.py,sha256=0sa6ietUWZGkxOnUPUzD3q2QpFuOpWkSANoopGpPd6s,11035
6
- prefab/models.py,sha256=UMzYZzKouroxlwkXCMKIYozmQCMhNhvt8kQrZmwmZB4,3671
7
- prefab/read.py,sha256=MuF-cugFQ7MWBJ8DOvQuwktIk0fJ8PXBeLye0ydrB8o,14734
8
- prefab/shapes.py,sha256=2qaqyNzu5WG3wVdk4oQzeNXmhwXRHcPnRZlgRrM4MoA,25576
9
- prefab-1.1.2.dist-info/METADATA,sha256=wZOFUncUh7ZmZRoJ8AxH_SfpKwd2KdxDnVvmTyiLSR0,34824
10
- prefab-1.1.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
11
- prefab-1.1.2.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
12
- prefab-1.1.2.dist-info/RECORD,,
File without changes