geobox 2.0.1__py3-none-any.whl → 2.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geobox/__init__.py +61 -63
- geobox/aio/__init__.py +61 -63
- geobox/aio/api.py +489 -473
- geobox/aio/apikey.py +263 -263
- geobox/aio/attachment.py +341 -339
- geobox/aio/base.py +261 -262
- geobox/aio/basemap.py +196 -196
- geobox/aio/dashboard.py +340 -342
- geobox/aio/feature.py +23 -33
- geobox/aio/field.py +315 -321
- geobox/aio/file.py +72 -72
- geobox/aio/layout.py +340 -341
- geobox/aio/log.py +23 -23
- geobox/aio/map.py +1033 -1034
- geobox/aio/model3d.py +415 -415
- geobox/aio/mosaic.py +696 -696
- geobox/aio/plan.py +314 -314
- geobox/aio/query.py +693 -693
- geobox/aio/raster.py +907 -869
- geobox/aio/raster_analysis.py +740 -0
- geobox/aio/route.py +4 -4
- geobox/aio/scene.py +340 -342
- geobox/aio/settings.py +18 -18
- geobox/aio/task.py +404 -402
- geobox/aio/tile3d.py +337 -339
- geobox/aio/tileset.py +102 -103
- geobox/aio/usage.py +52 -51
- geobox/aio/user.py +506 -507
- geobox/aio/vector_tool.py +1968 -0
- geobox/aio/vectorlayer.py +315 -306
- geobox/aio/version.py +272 -273
- geobox/aio/view.py +1019 -983
- geobox/aio/workflow.py +340 -341
- geobox/api.py +18 -2
- geobox/apikey.py +262 -262
- geobox/attachment.py +336 -337
- geobox/base.py +384 -384
- geobox/basemap.py +194 -194
- geobox/dashboard.py +339 -341
- geobox/enums.py +432 -348
- geobox/feature.py +5 -5
- geobox/field.py +320 -320
- geobox/file.py +4 -4
- geobox/layout.py +339 -340
- geobox/log.py +4 -4
- geobox/map.py +1031 -1032
- geobox/model3d.py +410 -410
- geobox/mosaic.py +696 -696
- geobox/plan.py +313 -313
- geobox/query.py +691 -691
- geobox/raster.py +907 -863
- geobox/raster_analysis.py +737 -0
- geobox/scene.py +341 -342
- geobox/settings.py +194 -194
- geobox/task.py +399 -400
- geobox/tile3d.py +337 -338
- geobox/tileset.py +4 -4
- geobox/usage.py +3 -3
- geobox/user.py +503 -503
- geobox/vector_tool.py +1968 -0
- geobox/vectorlayer.py +5 -5
- geobox/version.py +272 -272
- geobox/view.py +981 -981
- geobox/workflow.py +338 -339
- {geobox-2.0.1.dist-info → geobox-2.2.0.dist-info}/METADATA +15 -1
- geobox-2.2.0.dist-info/RECORD +72 -0
- geobox-2.0.1.dist-info/RECORD +0 -68
- {geobox-2.0.1.dist-info → geobox-2.2.0.dist-info}/WHEEL +0 -0
- {geobox-2.0.1.dist-info → geobox-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {geobox-2.0.1.dist-info → geobox-2.2.0.dist-info}/top_level.txt +0 -0
geobox/model3d.py
CHANGED
|
@@ -1,411 +1,411 @@
|
|
|
1
|
-
from typing import Dict, List, Optional, Optional, Union, TYPE_CHECKING
|
|
2
|
-
from urllib.parse import urljoin, urlencode
|
|
3
|
-
import os
|
|
4
|
-
import zipfile
|
|
5
|
-
import sys
|
|
6
|
-
|
|
7
|
-
from .base import Base
|
|
8
|
-
from .exception import ApiRequestError
|
|
9
|
-
from .utils import get_save_path
|
|
10
|
-
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from . import GeoboxClient
|
|
13
|
-
from .user import User
|
|
14
|
-
from .aio import AsyncGeoboxClient
|
|
15
|
-
from .aio.model3d import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Model(Base):
|
|
19
|
-
|
|
20
|
-
BASE_ENDPOINT = '3dmodels/'
|
|
21
|
-
|
|
22
|
-
def __init__(self,
|
|
23
|
-
api: 'GeoboxClient',
|
|
24
|
-
uuid: str,
|
|
25
|
-
data: Optional[Dict] = {}):
|
|
26
|
-
"""
|
|
27
|
-
Initialize a 3D Model instance.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
api (GeoboxClient): The GeoboxClient instance for making requests.
|
|
31
|
-
uuid (str): The unique identifier for the model.
|
|
32
|
-
data (Dict, optional): The data of the model.
|
|
33
|
-
"""
|
|
34
|
-
super().__init__(api, uuid=uuid, data=data)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@classmethod
|
|
38
|
-
def get_models(cls, api: 'GeoboxClient', **kwargs) -> Union[List['Model'], int]:
|
|
39
|
-
"""
|
|
40
|
-
Get a list of models with optional filtering and pagination.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
api (GeoboxClient): The GeoboxClient instance for making requests.
|
|
44
|
-
|
|
45
|
-
Keyword Args:
|
|
46
|
-
q (str): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'".
|
|
47
|
-
search (str): search term for keyword-based searching among search_fields or all textual fields if search_fields does not have value. NOTE: if q param is defined this param will be ignored.
|
|
48
|
-
search_fields (str): comma separated list of fields for searching.
|
|
49
|
-
order_by (str): comma separated list of fields for sorting results [field1 A|D, field2 A|D, …]. e.g. name A, type D. NOTE: "A" denotes ascending order and "D" denotes descending order.
|
|
50
|
-
return_count (bool): whether to return total count. default is False.
|
|
51
|
-
skip (int): number of models to skip. default is 0.
|
|
52
|
-
limit (int): maximum number of models to return. default is 10.
|
|
53
|
-
user_id (int): specific user. privileges required.
|
|
54
|
-
shared (bool): Whether to return shared models. default is False.
|
|
55
|
-
|
|
56
|
-
Returns:
|
|
57
|
-
List[Model] | int: A list of Model objects or the count number.
|
|
58
|
-
|
|
59
|
-
Example:
|
|
60
|
-
>>> from geobox import GeoboxClient
|
|
61
|
-
>>> from geobox.model3d import Model
|
|
62
|
-
>>> client = GeoboxClient()
|
|
63
|
-
>>> models = Model.get_models(api=client,
|
|
64
|
-
... search="my_model",
|
|
65
|
-
... search_fields="name, description",
|
|
66
|
-
... order_by="name A",
|
|
67
|
-
... return_count=True,
|
|
68
|
-
... skip=0,
|
|
69
|
-
... limit=10,
|
|
70
|
-
... shared=False)
|
|
71
|
-
or
|
|
72
|
-
>>> models = client.get_models(search="my_model",
|
|
73
|
-
... search_fields="name, description",
|
|
74
|
-
... order_by="name A",
|
|
75
|
-
... return_count=True,
|
|
76
|
-
... skip=0,
|
|
77
|
-
... limit=10,
|
|
78
|
-
... shared=False)
|
|
79
|
-
"""
|
|
80
|
-
params = {
|
|
81
|
-
'f': 'json',
|
|
82
|
-
'q': kwargs.get('q'),
|
|
83
|
-
'search': kwargs.get('search'),
|
|
84
|
-
'search_fields': kwargs.get('search_fields'),
|
|
85
|
-
'order_by': kwargs.get('order_by'),
|
|
86
|
-
'return_count': kwargs.get('return_count', False),
|
|
87
|
-
'skip': kwargs.get('skip', 0),
|
|
88
|
-
'limit': kwargs.get('limit', 10),
|
|
89
|
-
'user_id': kwargs.get('user_id'),
|
|
90
|
-
'shared': kwargs.get('shared', False)
|
|
91
|
-
}
|
|
92
|
-
return super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: Model(api, item['uuid'], item))
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
@classmethod
|
|
96
|
-
def get_model(cls, api: 'GeoboxClient', uuid: str, user_id: int = None) -> 'Model':
|
|
97
|
-
"""
|
|
98
|
-
Get a model by its UUID.
|
|
99
|
-
|
|
100
|
-
Args:
|
|
101
|
-
api (GeoboxClient): The GeoboxClient instance for making requests.
|
|
102
|
-
uuid (str): The UUID of the model to get.
|
|
103
|
-
user_id (int, optional): Specific user. privileges required.
|
|
104
|
-
|
|
105
|
-
Returns:
|
|
106
|
-
Model: The model object.
|
|
107
|
-
|
|
108
|
-
Raises:
|
|
109
|
-
NotFoundError: If the model with the specified UUID is not found.
|
|
110
|
-
|
|
111
|
-
Example:
|
|
112
|
-
>>> from geobox import GeoboxClient
|
|
113
|
-
>>> from geobox.model3d import Model
|
|
114
|
-
>>> client = GeoboxClient()
|
|
115
|
-
>>> model = Model.get_model(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
116
|
-
or
|
|
117
|
-
>>> model = client.get_model(uuid="12345678-1234-5678-1234-567812345678")
|
|
118
|
-
"""
|
|
119
|
-
params = {
|
|
120
|
-
'f': 'json',
|
|
121
|
-
'user_id': user_id
|
|
122
|
-
}
|
|
123
|
-
return super()._get_detail(api, cls.BASE_ENDPOINT, uuid, params, factory_func=lambda api, item: Model(api, item['uuid'], item))
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
@classmethod
|
|
127
|
-
def get_model_by_name(cls, api: 'GeoboxClient', name: str, user_id: int = None) -> Union['Model', None]:
|
|
128
|
-
"""
|
|
129
|
-
Get a model by name
|
|
130
|
-
|
|
131
|
-
Args:
|
|
132
|
-
api (GeoboxClient): The GeoboxClient instance for making requests.
|
|
133
|
-
name (str): the name of the model to get
|
|
134
|
-
user_id (int, optional): specific user. privileges required.
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
Model | None: returns the model if a model matches the given name, else None
|
|
138
|
-
|
|
139
|
-
Example:
|
|
140
|
-
>>> from geobox import GeoboxClient
|
|
141
|
-
>>> from geobox.model3d import Model
|
|
142
|
-
>>> client = GeoboxClient()
|
|
143
|
-
>>> model = Model.get_model_by_name(client, name='test')
|
|
144
|
-
or
|
|
145
|
-
>>> model = client.get_model_by_name(name='test')
|
|
146
|
-
"""
|
|
147
|
-
models = cls.get_models(api, q=f"name = '{name}'", user_id=user_id)
|
|
148
|
-
if models and models[0].name == name:
|
|
149
|
-
return models[0]
|
|
150
|
-
else:
|
|
151
|
-
return None
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def update(self, **kwargs) -> Dict:
|
|
155
|
-
"""
|
|
156
|
-
Update the model's properties.
|
|
157
|
-
|
|
158
|
-
Keyword Args:
|
|
159
|
-
name (str): The new name for the model.
|
|
160
|
-
display_name (str): The new display name.
|
|
161
|
-
description (str): The new description for the model.
|
|
162
|
-
settings (Dict): The new settings for the model.
|
|
163
|
-
thumbnail (str): The new thumbnail for the model.
|
|
164
|
-
|
|
165
|
-
Returns:
|
|
166
|
-
Dict: The updated model data.
|
|
167
|
-
|
|
168
|
-
Raises:
|
|
169
|
-
ValidationError: If the update data is invalid.
|
|
170
|
-
|
|
171
|
-
Example:
|
|
172
|
-
>>> from geobox import GeoboxClient
|
|
173
|
-
>>> from geobox.model3d import Model
|
|
174
|
-
>>> client = GeoboxClient()
|
|
175
|
-
>>> model = Model.get_model(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
176
|
-
>>> settings = {
|
|
177
|
-
... "model_settings": {
|
|
178
|
-
... "scale": 0,
|
|
179
|
-
... "rotation": [
|
|
180
|
-
... 0
|
|
181
|
-
... ],
|
|
182
|
-
... "location": [
|
|
183
|
-
... 0
|
|
184
|
-
... ]
|
|
185
|
-
... },
|
|
186
|
-
... "view_settings": {
|
|
187
|
-
... "center": [
|
|
188
|
-
... 0
|
|
189
|
-
... ],
|
|
190
|
-
... "zoom": 0,
|
|
191
|
-
... "pitch": 0,
|
|
192
|
-
... "bearing": 0
|
|
193
|
-
... }
|
|
194
|
-
... }
|
|
195
|
-
>>> model.update(name="new_name", description="new_description", settings=settings, thumbnail="new_thumbnail")
|
|
196
|
-
"""
|
|
197
|
-
data = {
|
|
198
|
-
'name': kwargs.get('name'),
|
|
199
|
-
'display_name': kwargs.get('display_name'),
|
|
200
|
-
'description': kwargs.get('description'),
|
|
201
|
-
'settings': kwargs.get('settings'),
|
|
202
|
-
'thumbnail': kwargs.get('thumbnail')
|
|
203
|
-
}
|
|
204
|
-
return super()._update(self.endpoint, data)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
def delete(self) -> None:
|
|
208
|
-
"""
|
|
209
|
-
Delete the model.
|
|
210
|
-
|
|
211
|
-
Returns:
|
|
212
|
-
None
|
|
213
|
-
|
|
214
|
-
Example:
|
|
215
|
-
>>> from geobox import GeoboxClient
|
|
216
|
-
>>> from geobox.model3d import Model
|
|
217
|
-
>>> client = GeoboxClient()
|
|
218
|
-
>>> model = Model.get_model(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
219
|
-
>>> model.delete()
|
|
220
|
-
"""
|
|
221
|
-
super().
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
def _create_progress_bar(self) -> 'tqdm':
|
|
225
|
-
"""Creates a progress bar for the task."""
|
|
226
|
-
try:
|
|
227
|
-
from tqdm.auto import tqdm
|
|
228
|
-
except ImportError:
|
|
229
|
-
from .api import logger
|
|
230
|
-
logger.warning("[tqdm] extra is required to show the progress bar. install with: pip insatll geobox[tqdm]")
|
|
231
|
-
return None
|
|
232
|
-
|
|
233
|
-
return tqdm(unit="B",
|
|
234
|
-
total=int(self.size),
|
|
235
|
-
file=sys.stdout,
|
|
236
|
-
dynamic_ncols=True,
|
|
237
|
-
desc="Downloading",
|
|
238
|
-
unit_scale=True,
|
|
239
|
-
unit_divisor=1024,
|
|
240
|
-
ascii=True
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def download(self, save_path: str = None, progress_bar: bool = True) -> str:
|
|
245
|
-
"""
|
|
246
|
-
Download the 3D model, save it as a .glb file, zip it, and return the zip file path.
|
|
247
|
-
|
|
248
|
-
Args:
|
|
249
|
-
save_path (str, optional): Directory where the file should be saved.
|
|
250
|
-
progress_bar (bool, optional): Whether to show a progress bar. Default: True
|
|
251
|
-
|
|
252
|
-
Returns:
|
|
253
|
-
str: Path to the .zip file containing the .glb model.
|
|
254
|
-
|
|
255
|
-
Raises:
|
|
256
|
-
ApiRequestError: If the API request fails.
|
|
257
|
-
|
|
258
|
-
Example:
|
|
259
|
-
>>> from geobox import GeoboxClient
|
|
260
|
-
>>> client = GeoboxClient()
|
|
261
|
-
>>> model = client.get_models()[0]
|
|
262
|
-
>>> model.download()
|
|
263
|
-
"""
|
|
264
|
-
if not self.uuid:
|
|
265
|
-
raise ValueError("Model UUID is required to download content")
|
|
266
|
-
|
|
267
|
-
if self.data.get('obj'):
|
|
268
|
-
model = self.api.get_model(self.obj)
|
|
269
|
-
else:
|
|
270
|
-
model = self
|
|
271
|
-
|
|
272
|
-
save_path = get_save_path(save_path)
|
|
273
|
-
os.makedirs(save_path, exist_ok=True)
|
|
274
|
-
|
|
275
|
-
endpoint = urljoin(model.api.base_url, f"{model.endpoint}/content/")
|
|
276
|
-
with model.api.session.get(endpoint, stream=True) as response:
|
|
277
|
-
if response.status_code != 200:
|
|
278
|
-
raise ApiRequestError(f"Failed to get model content: {response.status_code}")
|
|
279
|
-
|
|
280
|
-
glb_filename = f"{model.name}.glb"
|
|
281
|
-
glb_path = os.path.join(save_path, glb_filename)
|
|
282
|
-
|
|
283
|
-
with open(glb_path, "wb") as f:
|
|
284
|
-
pbar = model._create_progress_bar() if progress_bar else None
|
|
285
|
-
for chunk in response.iter_content(chunk_size=8192):
|
|
286
|
-
f.write(chunk)
|
|
287
|
-
if pbar:
|
|
288
|
-
pbar.update(len(chunk))
|
|
289
|
-
pbar.refresh()
|
|
290
|
-
if pbar:
|
|
291
|
-
pbar.close()
|
|
292
|
-
|
|
293
|
-
zip_filename = f"{model.name}.zip"
|
|
294
|
-
zip_path = os.path.join(save_path, zip_filename)
|
|
295
|
-
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
|
296
|
-
zipf.write(glb_path, arcname=os.path.basename(glb_path))
|
|
297
|
-
|
|
298
|
-
os.remove(glb_path)
|
|
299
|
-
|
|
300
|
-
return os.path.abspath(zip_path)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
@property
|
|
304
|
-
def thumbnail(self) -> str:
|
|
305
|
-
"""
|
|
306
|
-
Get the thumbnail URL of the model.
|
|
307
|
-
|
|
308
|
-
Returns:
|
|
309
|
-
str: The thumbnail of the model.
|
|
310
|
-
|
|
311
|
-
Example:
|
|
312
|
-
>>> from geobox import GeoboxClient
|
|
313
|
-
>>> from geobox.model3d import Model
|
|
314
|
-
>>> client = GeoboxClient()
|
|
315
|
-
>>> model = Model.get_model(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
316
|
-
>>> model.thumbnail
|
|
317
|
-
"""
|
|
318
|
-
return super().
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
def share(self, users: List['User']) -> None:
|
|
322
|
-
"""
|
|
323
|
-
Shares the model with specified users.
|
|
324
|
-
|
|
325
|
-
Args:
|
|
326
|
-
users (List[users]): The list of user objects to share the model with.
|
|
327
|
-
|
|
328
|
-
Returns:
|
|
329
|
-
None
|
|
330
|
-
|
|
331
|
-
Example:
|
|
332
|
-
>>> from geobox import GeoboxClient
|
|
333
|
-
>>> from geobox.model3d import Model
|
|
334
|
-
>>> client = GeoboxClient()
|
|
335
|
-
>>> model = Model.get_model(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
336
|
-
>>> users = client.search_users(search='John')
|
|
337
|
-
>>> model.share(users=users)
|
|
338
|
-
"""
|
|
339
|
-
super()._share(self.endpoint, users)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
def unshare(self, users: List['User']) -> None:
|
|
343
|
-
"""
|
|
344
|
-
Unshares the model with specified users.
|
|
345
|
-
|
|
346
|
-
Args:
|
|
347
|
-
users (List[User]): The list of user objects to unshare the model with.
|
|
348
|
-
|
|
349
|
-
Returns:
|
|
350
|
-
None
|
|
351
|
-
|
|
352
|
-
Example:
|
|
353
|
-
>>> from geobox import GeoboxClient
|
|
354
|
-
>>> from geobox.model3d import Model
|
|
355
|
-
>>> client = GeoboxClient()
|
|
356
|
-
>>> model = Model.get_model(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
357
|
-
>>> users = client.search_users(search='John')
|
|
358
|
-
>>> model.unshare(users=users)
|
|
359
|
-
"""
|
|
360
|
-
super()._unshare(self.endpoint, users)
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
def get_shared_users(self, search: str = None, skip: int = 0, limit: int = 10) -> List['User']:
|
|
364
|
-
"""
|
|
365
|
-
Retrieves the list of users the model is shared with.
|
|
366
|
-
|
|
367
|
-
Args:
|
|
368
|
-
search (str, optional): The search query.
|
|
369
|
-
skip (int, optional): The number of users to skip.
|
|
370
|
-
limit (int, optional): The maximum number of users to retrieve.
|
|
371
|
-
|
|
372
|
-
Returns:
|
|
373
|
-
List[User]: The list of shared users.
|
|
374
|
-
|
|
375
|
-
Example:
|
|
376
|
-
>>> from geobox import GeoboxClient
|
|
377
|
-
>>> from geobox.model3d import Model
|
|
378
|
-
>>> client = GeoboxClient()
|
|
379
|
-
>>> model = Model.get_model(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
380
|
-
>>> model.get_shared_users(search='John', skip=0, limit=10)
|
|
381
|
-
"""
|
|
382
|
-
params = {
|
|
383
|
-
'search': search,
|
|
384
|
-
'skip': skip,
|
|
385
|
-
'limit': limit
|
|
386
|
-
}
|
|
387
|
-
return super()._get_shared_users(self.endpoint, params)
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
def to_async(self, async_client: 'AsyncGeoboxClient') -> 'AsyncModel':
|
|
391
|
-
"""
|
|
392
|
-
Switch to async version of the 3d model instance to have access to the async methods
|
|
393
|
-
|
|
394
|
-
Args:
|
|
395
|
-
async_client (AsyncGeoboxClient): The async version of the GeoboxClient instance for making requests.
|
|
396
|
-
|
|
397
|
-
Returns:
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
Example:
|
|
401
|
-
>>> from geobox import Geoboxclient
|
|
402
|
-
>>> from geobox.aio import AsyncGeoboxClient
|
|
403
|
-
>>> from geobox.model3d import Model
|
|
404
|
-
>>> client = GeoboxClient()
|
|
405
|
-
>>> model = Model.get_model(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
406
|
-
>>> async with AsyncGeoboxClient() as async_client:
|
|
407
|
-
>>> async_model = model.to_async(async_client)
|
|
408
|
-
"""
|
|
409
|
-
from .aio.model3d import
|
|
410
|
-
|
|
1
|
+
from typing import Dict, List, Optional, Optional, Union, TYPE_CHECKING
|
|
2
|
+
from urllib.parse import urljoin, urlencode
|
|
3
|
+
import os
|
|
4
|
+
import zipfile
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from .base import Base
|
|
8
|
+
from .exception import ApiRequestError
|
|
9
|
+
from .utils import get_save_path
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from . import GeoboxClient
|
|
13
|
+
from .user import User
|
|
14
|
+
from .aio import AsyncGeoboxClient
|
|
15
|
+
from .aio.model3d import AsyncModel
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Model(Base):
|
|
19
|
+
|
|
20
|
+
BASE_ENDPOINT = '3dmodels/'
|
|
21
|
+
|
|
22
|
+
def __init__(self,
|
|
23
|
+
api: 'GeoboxClient',
|
|
24
|
+
uuid: str,
|
|
25
|
+
data: Optional[Dict] = {}):
|
|
26
|
+
"""
|
|
27
|
+
Initialize a 3D Model instance.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
api (GeoboxClient): The GeoboxClient instance for making requests.
|
|
31
|
+
uuid (str): The unique identifier for the model.
|
|
32
|
+
data (Dict, optional): The data of the model.
|
|
33
|
+
"""
|
|
34
|
+
super().__init__(api, uuid=uuid, data=data)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def get_models(cls, api: 'GeoboxClient', **kwargs) -> Union[List['Model'], int]:
|
|
39
|
+
"""
|
|
40
|
+
Get a list of models with optional filtering and pagination.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
api (GeoboxClient): The GeoboxClient instance for making requests.
|
|
44
|
+
|
|
45
|
+
Keyword Args:
|
|
46
|
+
q (str): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'".
|
|
47
|
+
search (str): search term for keyword-based searching among search_fields or all textual fields if search_fields does not have value. NOTE: if q param is defined this param will be ignored.
|
|
48
|
+
search_fields (str): comma separated list of fields for searching.
|
|
49
|
+
order_by (str): comma separated list of fields for sorting results [field1 A|D, field2 A|D, …]. e.g. name A, type D. NOTE: "A" denotes ascending order and "D" denotes descending order.
|
|
50
|
+
return_count (bool): whether to return total count. default is False.
|
|
51
|
+
skip (int): number of models to skip. default is 0.
|
|
52
|
+
limit (int): maximum number of models to return. default is 10.
|
|
53
|
+
user_id (int): specific user. privileges required.
|
|
54
|
+
shared (bool): Whether to return shared models. default is False.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
List[Model] | int: A list of Model objects or the count number.
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
>>> from geobox import GeoboxClient
|
|
61
|
+
>>> from geobox.model3d import Model
|
|
62
|
+
>>> client = GeoboxClient()
|
|
63
|
+
>>> models = Model.get_models(api=client,
|
|
64
|
+
... search="my_model",
|
|
65
|
+
... search_fields="name, description",
|
|
66
|
+
... order_by="name A",
|
|
67
|
+
... return_count=True,
|
|
68
|
+
... skip=0,
|
|
69
|
+
... limit=10,
|
|
70
|
+
... shared=False)
|
|
71
|
+
or
|
|
72
|
+
>>> models = client.get_models(search="my_model",
|
|
73
|
+
... search_fields="name, description",
|
|
74
|
+
... order_by="name A",
|
|
75
|
+
... return_count=True,
|
|
76
|
+
... skip=0,
|
|
77
|
+
... limit=10,
|
|
78
|
+
... shared=False)
|
|
79
|
+
"""
|
|
80
|
+
params = {
|
|
81
|
+
'f': 'json',
|
|
82
|
+
'q': kwargs.get('q'),
|
|
83
|
+
'search': kwargs.get('search'),
|
|
84
|
+
'search_fields': kwargs.get('search_fields'),
|
|
85
|
+
'order_by': kwargs.get('order_by'),
|
|
86
|
+
'return_count': kwargs.get('return_count', False),
|
|
87
|
+
'skip': kwargs.get('skip', 0),
|
|
88
|
+
'limit': kwargs.get('limit', 10),
|
|
89
|
+
'user_id': kwargs.get('user_id'),
|
|
90
|
+
'shared': kwargs.get('shared', False)
|
|
91
|
+
}
|
|
92
|
+
return super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: Model(api, item['uuid'], item))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def get_model(cls, api: 'GeoboxClient', uuid: str, user_id: int = None) -> 'Model':
|
|
97
|
+
"""
|
|
98
|
+
Get a model by its UUID.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
api (GeoboxClient): The GeoboxClient instance for making requests.
|
|
102
|
+
uuid (str): The UUID of the model to get.
|
|
103
|
+
user_id (int, optional): Specific user. privileges required.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Model: The model object.
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
NotFoundError: If the model with the specified UUID is not found.
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
>>> from geobox import GeoboxClient
|
|
113
|
+
>>> from geobox.model3d import Model
|
|
114
|
+
>>> client = GeoboxClient()
|
|
115
|
+
>>> model = Model.get_model(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
116
|
+
or
|
|
117
|
+
>>> model = client.get_model(uuid="12345678-1234-5678-1234-567812345678")
|
|
118
|
+
"""
|
|
119
|
+
params = {
|
|
120
|
+
'f': 'json',
|
|
121
|
+
'user_id': user_id
|
|
122
|
+
}
|
|
123
|
+
return super()._get_detail(api, cls.BASE_ENDPOINT, uuid, params, factory_func=lambda api, item: Model(api, item['uuid'], item))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@classmethod
|
|
127
|
+
def get_model_by_name(cls, api: 'GeoboxClient', name: str, user_id: int = None) -> Union['Model', None]:
|
|
128
|
+
"""
|
|
129
|
+
Get a model by name
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
api (GeoboxClient): The GeoboxClient instance for making requests.
|
|
133
|
+
name (str): the name of the model to get
|
|
134
|
+
user_id (int, optional): specific user. privileges required.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Model | None: returns the model if a model matches the given name, else None
|
|
138
|
+
|
|
139
|
+
Example:
|
|
140
|
+
>>> from geobox import GeoboxClient
|
|
141
|
+
>>> from geobox.model3d import Model
|
|
142
|
+
>>> client = GeoboxClient()
|
|
143
|
+
>>> model = Model.get_model_by_name(client, name='test')
|
|
144
|
+
or
|
|
145
|
+
>>> model = client.get_model_by_name(name='test')
|
|
146
|
+
"""
|
|
147
|
+
models = cls.get_models(api, q=f"name = '{name}'", user_id=user_id)
|
|
148
|
+
if models and models[0].name == name:
|
|
149
|
+
return models[0]
|
|
150
|
+
else:
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def update(self, **kwargs) -> Dict:
|
|
155
|
+
"""
|
|
156
|
+
Update the model's properties.
|
|
157
|
+
|
|
158
|
+
Keyword Args:
|
|
159
|
+
name (str): The new name for the model.
|
|
160
|
+
display_name (str): The new display name.
|
|
161
|
+
description (str): The new description for the model.
|
|
162
|
+
settings (Dict): The new settings for the model.
|
|
163
|
+
thumbnail (str): The new thumbnail for the model.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Dict: The updated model data.
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
ValidationError: If the update data is invalid.
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
>>> from geobox import GeoboxClient
|
|
173
|
+
>>> from geobox.model3d import Model
|
|
174
|
+
>>> client = GeoboxClient()
|
|
175
|
+
>>> model = Model.get_model(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
176
|
+
>>> settings = {
|
|
177
|
+
... "model_settings": {
|
|
178
|
+
... "scale": 0,
|
|
179
|
+
... "rotation": [
|
|
180
|
+
... 0
|
|
181
|
+
... ],
|
|
182
|
+
... "location": [
|
|
183
|
+
... 0
|
|
184
|
+
... ]
|
|
185
|
+
... },
|
|
186
|
+
... "view_settings": {
|
|
187
|
+
... "center": [
|
|
188
|
+
... 0
|
|
189
|
+
... ],
|
|
190
|
+
... "zoom": 0,
|
|
191
|
+
... "pitch": 0,
|
|
192
|
+
... "bearing": 0
|
|
193
|
+
... }
|
|
194
|
+
... }
|
|
195
|
+
>>> model.update(name="new_name", description="new_description", settings=settings, thumbnail="new_thumbnail")
|
|
196
|
+
"""
|
|
197
|
+
data = {
|
|
198
|
+
'name': kwargs.get('name'),
|
|
199
|
+
'display_name': kwargs.get('display_name'),
|
|
200
|
+
'description': kwargs.get('description'),
|
|
201
|
+
'settings': kwargs.get('settings'),
|
|
202
|
+
'thumbnail': kwargs.get('thumbnail')
|
|
203
|
+
}
|
|
204
|
+
return super()._update(self.endpoint, data)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def delete(self) -> None:
|
|
208
|
+
"""
|
|
209
|
+
Delete the model.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
None
|
|
213
|
+
|
|
214
|
+
Example:
|
|
215
|
+
>>> from geobox import GeoboxClient
|
|
216
|
+
>>> from geobox.model3d import Model
|
|
217
|
+
>>> client = GeoboxClient()
|
|
218
|
+
>>> model = Model.get_model(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
219
|
+
>>> model.delete()
|
|
220
|
+
"""
|
|
221
|
+
super()._delete(self.endpoint)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _create_progress_bar(self) -> 'tqdm':
|
|
225
|
+
"""Creates a progress bar for the task."""
|
|
226
|
+
try:
|
|
227
|
+
from tqdm.auto import tqdm
|
|
228
|
+
except ImportError:
|
|
229
|
+
from .api import logger
|
|
230
|
+
logger.warning("[tqdm] extra is required to show the progress bar. install with: pip insatll geobox[tqdm]")
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
return tqdm(unit="B",
|
|
234
|
+
total=int(self.size),
|
|
235
|
+
file=sys.stdout,
|
|
236
|
+
dynamic_ncols=True,
|
|
237
|
+
desc="Downloading",
|
|
238
|
+
unit_scale=True,
|
|
239
|
+
unit_divisor=1024,
|
|
240
|
+
ascii=True
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def download(self, save_path: str = None, progress_bar: bool = True) -> str:
|
|
245
|
+
"""
|
|
246
|
+
Download the 3D model, save it as a .glb file, zip it, and return the zip file path.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
save_path (str, optional): Directory where the file should be saved.
|
|
250
|
+
progress_bar (bool, optional): Whether to show a progress bar. Default: True
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
str: Path to the .zip file containing the .glb model.
|
|
254
|
+
|
|
255
|
+
Raises:
|
|
256
|
+
ApiRequestError: If the API request fails.
|
|
257
|
+
|
|
258
|
+
Example:
|
|
259
|
+
>>> from geobox import GeoboxClient
|
|
260
|
+
>>> client = GeoboxClient()
|
|
261
|
+
>>> model = client.get_models()[0]
|
|
262
|
+
>>> model.download()
|
|
263
|
+
"""
|
|
264
|
+
if not self.uuid:
|
|
265
|
+
raise ValueError("Model UUID is required to download content")
|
|
266
|
+
|
|
267
|
+
if self.data.get('obj'):
|
|
268
|
+
model = self.api.get_model(self.obj)
|
|
269
|
+
else:
|
|
270
|
+
model = self
|
|
271
|
+
|
|
272
|
+
save_path = get_save_path(save_path)
|
|
273
|
+
os.makedirs(save_path, exist_ok=True)
|
|
274
|
+
|
|
275
|
+
endpoint = urljoin(model.api.base_url, f"{model.endpoint}/content/")
|
|
276
|
+
with model.api.session.get(endpoint, stream=True) as response:
|
|
277
|
+
if response.status_code != 200:
|
|
278
|
+
raise ApiRequestError(f"Failed to get model content: {response.status_code}")
|
|
279
|
+
|
|
280
|
+
glb_filename = f"{model.name}.glb"
|
|
281
|
+
glb_path = os.path.join(save_path, glb_filename)
|
|
282
|
+
|
|
283
|
+
with open(glb_path, "wb") as f:
|
|
284
|
+
pbar = model._create_progress_bar() if progress_bar else None
|
|
285
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
286
|
+
f.write(chunk)
|
|
287
|
+
if pbar:
|
|
288
|
+
pbar.update(len(chunk))
|
|
289
|
+
pbar.refresh()
|
|
290
|
+
if pbar:
|
|
291
|
+
pbar.close()
|
|
292
|
+
|
|
293
|
+
zip_filename = f"{model.name}.zip"
|
|
294
|
+
zip_path = os.path.join(save_path, zip_filename)
|
|
295
|
+
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
|
296
|
+
zipf.write(glb_path, arcname=os.path.basename(glb_path))
|
|
297
|
+
|
|
298
|
+
os.remove(glb_path)
|
|
299
|
+
|
|
300
|
+
return os.path.abspath(zip_path)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def thumbnail(self) -> str:
|
|
305
|
+
"""
|
|
306
|
+
Get the thumbnail URL of the model.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
str: The thumbnail of the model.
|
|
310
|
+
|
|
311
|
+
Example:
|
|
312
|
+
>>> from geobox import GeoboxClient
|
|
313
|
+
>>> from geobox.model3d import Model
|
|
314
|
+
>>> client = GeoboxClient()
|
|
315
|
+
>>> model = Model.get_model(api=client, uuid="12345678-1234-5678-1234-567812345678")
|
|
316
|
+
>>> model.thumbnail
|
|
317
|
+
"""
|
|
318
|
+
return super()._thumbnail()
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def share(self, users: List['User']) -> None:
|
|
322
|
+
"""
|
|
323
|
+
Shares the model with specified users.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
users (List[users]): The list of user objects to share the model with.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
None
|
|
330
|
+
|
|
331
|
+
Example:
|
|
332
|
+
>>> from geobox import GeoboxClient
|
|
333
|
+
>>> from geobox.model3d import Model
|
|
334
|
+
>>> client = GeoboxClient()
|
|
335
|
+
>>> model = Model.get_model(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
336
|
+
>>> users = client.search_users(search='John')
|
|
337
|
+
>>> model.share(users=users)
|
|
338
|
+
"""
|
|
339
|
+
super()._share(self.endpoint, users)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def unshare(self, users: List['User']) -> None:
|
|
343
|
+
"""
|
|
344
|
+
Unshares the model with specified users.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
users (List[User]): The list of user objects to unshare the model with.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
None
|
|
351
|
+
|
|
352
|
+
Example:
|
|
353
|
+
>>> from geobox import GeoboxClient
|
|
354
|
+
>>> from geobox.model3d import Model
|
|
355
|
+
>>> client = GeoboxClient()
|
|
356
|
+
>>> model = Model.get_model(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
357
|
+
>>> users = client.search_users(search='John')
|
|
358
|
+
>>> model.unshare(users=users)
|
|
359
|
+
"""
|
|
360
|
+
super()._unshare(self.endpoint, users)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def get_shared_users(self, search: str = None, skip: int = 0, limit: int = 10) -> List['User']:
|
|
364
|
+
"""
|
|
365
|
+
Retrieves the list of users the model is shared with.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
search (str, optional): The search query.
|
|
369
|
+
skip (int, optional): The number of users to skip.
|
|
370
|
+
limit (int, optional): The maximum number of users to retrieve.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
List[User]: The list of shared users.
|
|
374
|
+
|
|
375
|
+
Example:
|
|
376
|
+
>>> from geobox import GeoboxClient
|
|
377
|
+
>>> from geobox.model3d import Model
|
|
378
|
+
>>> client = GeoboxClient()
|
|
379
|
+
>>> model = Model.get_model(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
380
|
+
>>> model.get_shared_users(search='John', skip=0, limit=10)
|
|
381
|
+
"""
|
|
382
|
+
params = {
|
|
383
|
+
'search': search,
|
|
384
|
+
'skip': skip,
|
|
385
|
+
'limit': limit
|
|
386
|
+
}
|
|
387
|
+
return super()._get_shared_users(self.endpoint, params)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def to_async(self, async_client: 'AsyncGeoboxClient') -> 'AsyncModel':
|
|
391
|
+
"""
|
|
392
|
+
Switch to async version of the 3d model instance to have access to the async methods
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
async_client (AsyncGeoboxClient): The async version of the GeoboxClient instance for making requests.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
AsyncModel: the async instance of the 3d model.
|
|
399
|
+
|
|
400
|
+
Example:
|
|
401
|
+
>>> from geobox import Geoboxclient
|
|
402
|
+
>>> from geobox.aio import AsyncGeoboxClient
|
|
403
|
+
>>> from geobox.model3d import Model
|
|
404
|
+
>>> client = GeoboxClient()
|
|
405
|
+
>>> model = Model.get_model(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
406
|
+
>>> async with AsyncGeoboxClient() as async_client:
|
|
407
|
+
>>> async_model = model.to_async(async_client)
|
|
408
|
+
"""
|
|
409
|
+
from .aio.model3d import AsyncModel
|
|
410
|
+
|
|
411
411
|
return AsyncModel(api=async_client, uuid=self.uuid, data=self.data)
|