irie 0.0.59__py3-none-any.whl → 0.0.60__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.

Potentially problematic release.


This version of irie might be problematic. Click here for more details.

Files changed (32) hide show
  1. irie/apps/evaluation/models.py +7 -0
  2. irie/apps/events/views_events.py +2 -2
  3. irie/apps/inventory/models.py +1 -1
  4. irie/apps/inventory/views.py +7 -1
  5. irie/apps/prediction/forms/csi_upload.py +68 -0
  6. irie/apps/prediction/runners/__init__.py +6 -4
  7. irie/apps/prediction/runners/opensees/__init__.py +49 -14
  8. irie/apps/prediction/urls.py +6 -4
  9. irie/apps/prediction/views.py +248 -17
  10. irie/apps/static/assets/css/brace.css +1 -1
  11. irie/apps/static/assets/css/brace.css.map +1 -1
  12. irie/apps/static/assets/css/brace.min.css +1 -1
  13. irie/apps/static/assets/js/brace.js +101 -42
  14. irie/apps/templates/includes/footer.html +1 -1
  15. irie/apps/templates/inventory/asset-event-summary.html +7 -0
  16. irie/apps/templates/inventory/asset-on-map.html +22 -22
  17. irie/apps/templates/inventory/asset-on-map.js +115 -113
  18. irie/apps/templates/inventory/create-datum.html +27 -27
  19. irie/apps/templates/layouts/base.html +3 -3
  20. irie/apps/templates/prediction/create-model.html +32 -29
  21. irie/apps/templates/prediction/upload/confirm.html +93 -0
  22. irie/apps/templates/prediction/upload/step.html +119 -0
  23. irie/apps/templates/prediction/veux/navigator.html +54 -38
  24. irie/apps/templates/prediction/veux/navigator.js +222 -154
  25. irie/apps/templates/prediction/xara-profile.html +7 -3
  26. irie/core/settings.py +1 -0
  27. {irie-0.0.59.dist-info → irie-0.0.60.dist-info}/METADATA +7 -6
  28. {irie-0.0.59.dist-info → irie-0.0.60.dist-info}/RECORD +32 -29
  29. /irie/apps/prediction/{forms.py → forms/__init__.py} +0 -0
  30. {irie-0.0.59.dist-info → irie-0.0.60.dist-info}/WHEEL +0 -0
  31. {irie-0.0.59.dist-info → irie-0.0.60.dist-info}/entry_points.txt +0 -0
  32. {irie-0.0.59.dist-info → irie-0.0.60.dist-info}/top_level.txt +0 -0
@@ -51,6 +51,13 @@ class Evaluation(models.Model):
51
51
  # Thread(target=evaluate, args=(event, evaluation)).start()
52
52
 
53
53
  return evaluation
54
+
55
+ def evaluate(self):
56
+ """
57
+ Evaluate the event and update the evaluation status.
58
+ This method can be run in a separate thread.
59
+ """
60
+ return evaluate(self.event, self)
54
61
 
55
62
 
56
63
  def evaluate(event, evaluation)->"Evaluation":
@@ -84,8 +84,8 @@ def save_event(request, event, success_status):
84
84
  # RECORD ID
85
85
  rec_id = motion_data.get("record_identifier", "")
86
86
 
87
- if EventRecord.objects.filter(record_identifier=rec_id).first():
88
- print("\n\nSKIPPING\n\n")
87
+ if (evnt := EventRecord.objects.filter(record_identifier=rec_id).first()):
88
+ print(f"\n\nSKIPPING, {evnt.id}")
89
89
  return HttpResponse(json.dumps({"data": ""}), status=success_status)
90
90
 
91
91
  # CREATE EVENT
@@ -118,7 +118,7 @@ class Datum(models.Model):
118
118
 
119
119
  def __str__(self):
120
120
  return f"{self.name}"
121
-
121
+
122
122
  def to_cardinal(self):
123
123
  """
124
124
  Create rotation matrix from datum basis to cardinal basis.
@@ -170,6 +170,9 @@ def asset_event_summary(request, cesmd, event):
170
170
  evaluation_data = {}
171
171
  evaluation = None
172
172
 
173
+ if request.method == 'POST':
174
+ evaluation.evaluate()
175
+
173
176
  try:
174
177
  for metric in evaluation_data.values():
175
178
  metric["completion"] = (
@@ -451,7 +454,10 @@ def sensor_upload(request, calid):
451
454
  "group_form": group_form,
452
455
  "formset": formset,
453
456
  "renderings": [
454
- {"name": predictor.name, "glb": predictor.render_file.url}
457
+ {
458
+ "name": predictor.name,
459
+ "glb": predictor.render_file.url
460
+ }
455
461
  for predictor in PredictorModel.objects.filter(asset=asset, protocol="IRIE_PREDICTOR_V1")
456
462
  if predictor.render_file and predictor.render_file.url
457
463
  ],
@@ -0,0 +1,68 @@
1
+ # forms.py
2
+ from django import forms
3
+ from irie.apps.inventory.models import Datum
4
+ from irie.apps.prediction.models import PredictorModel, SensorAssignment
5
+
6
+ class DatumSelectForm(forms.Form):
7
+ """
8
+ Step 0 – choose an existing datum or say 'create new'.
9
+ The empty choice means 'I want to add a new datum'.
10
+ """
11
+ datum = forms.ModelChoiceField(
12
+ queryset=Datum.objects.none(),
13
+ required=False,
14
+ empty_label="-- create new datum --",
15
+ label="Select Datum",
16
+ help_text="Select an existing datum or create a new one."
17
+ )
18
+
19
+ def __init__(self, *args, asset=None, datum_queryset=None, **kwargs):
20
+ super().__init__(*args, **kwargs)
21
+ self.fields["datum"].queryset = datum_queryset or Datum.objects.none()
22
+
23
+ class DatumCreateForm(forms.ModelForm):
24
+ class Meta:
25
+ model = Datum
26
+ fields = ("angle_x", "angle_y")
27
+
28
+ def __init__(self, *args, asset=None, **kwargs):
29
+ super().__init__(*args, **kwargs)
30
+ self.asset = asset
31
+
32
+ class PredictorForm(forms.ModelForm):
33
+ class Meta:
34
+ model = PredictorModel
35
+ help_text = {
36
+ "config_file": "File exported from CSi Bridge or SAP2000 (.b2k or .s2k).",
37
+ }
38
+ fields = "__all__"
39
+ exclude = [
40
+ "render_file",
41
+ "asset",
42
+ "metrics",
43
+ "active",
44
+ "description",
45
+ "entry_point",
46
+ "config",
47
+ "protocol"
48
+ ]
49
+ def __init__(self, *args, asset=None, **kwargs):
50
+ super().__init__(*args, **kwargs)
51
+ self.asset = asset
52
+ self.fields['name'].widget.attrs["class"] = "rounded-0"
53
+ self.fields['config_file'].widget.attrs["class"] = "rounded-0"
54
+
55
+ class SensorForm(forms.ModelForm):
56
+ class Meta:
57
+ model = SensorAssignment
58
+ fields = (
59
+ "role",
60
+ "sensor"
61
+ )
62
+
63
+ def __init__(self, *args, **kwargs):
64
+ super().__init__(*args, **kwargs)
65
+
66
+ class ConfirmForm(forms.Form):
67
+ # no fields – just a read-only summary screen
68
+ pass
@@ -32,13 +32,15 @@ class Runner:
32
32
  # Create from existing PredictorModel when loaded from database.
33
33
  # This is done when running analysis
34
34
  self.id = pred.id
35
- self.asset = pred.asset
35
+ # self.asset = pred.asset
36
36
  self.name: str = pred.name
37
37
  self.description = "" # conf.description
38
38
  self.conf = pred.config
39
- self.entry_point = pred.entry_point
40
- self.metrics = pred.metrics
41
- self.active = pred.active
39
+ if pred.entry_point:
40
+ self.entry_point = pred.entry_point
41
+ if pred.metrics:
42
+ self.metrics = pred.metrics
43
+ self.active = pred.active
42
44
 
43
45
  # NEW:
44
46
  self.predictor = pred
@@ -6,6 +6,7 @@
6
6
  #
7
7
  import os.path
8
8
  import shutil
9
+ import tqdm
9
10
  import sys, json
10
11
  import zipfile
11
12
  from pathlib import Path
@@ -54,7 +55,7 @@ def _create_excitation(model, predictor, inputs, dt):
54
55
  def _analyze_and_render(model, artist, nt, dt):
55
56
  import veux, veux.motion
56
57
  motion = veux.motion.Motion(artist)
57
- for i in range(nt):
58
+ for i in tqdm.tqdm(range(nt)):
58
59
  if model.analyze(1, dt) != 0:
59
60
  return -1
60
61
  motion.advance(i*dt)
@@ -102,7 +103,7 @@ class OpenSeesRunner(Runner):
102
103
  with open(uploaded_file.name, 'wb+') as destination:
103
104
  for chunk in uploaded_file.chunks():
104
105
  destination.write(chunk)
105
-
106
+
106
107
  # predictor.config_file = uploaded_file # data.pop("file")
107
108
  predictor.name = data.pop("name")
108
109
  predictor.config = data
@@ -227,6 +228,16 @@ class OpenSeesRunner(Runner):
227
228
  "analysis": schemas.load("hwd_analysis.schema.json"),
228
229
  }
229
230
  }
231
+
232
+ def render(self):
233
+ import veux
234
+ model = Job(self._csi).assemble().model
235
+
236
+ outlines = collect_outlines(self._csi, model.frame_tags)
237
+ artist = veux.render(model, canvas="gltf", vertical=3,
238
+ reference={"frame.surface", "frame.axes"},
239
+ model_config={"frame_outlines": outlines})
240
+ return artist
230
241
 
231
242
  def getMetricList(self):
232
243
  return [
@@ -236,6 +247,7 @@ class OpenSeesRunner(Runner):
236
247
  # "ACC_RESPONSE_HISTORY",
237
248
  ]
238
249
 
250
+
239
251
  def newPrediction(self, event, output_directory = None):
240
252
  """
241
253
  Create a new prediction run and return the run_id. If output_directory is None,
@@ -272,7 +284,7 @@ class OpenSeesRunner(Runner):
272
284
  if False:
273
285
  event = event.event_file.path
274
286
  shutil.copyfile(event, run_dir/"event.zip")
275
-
287
+
276
288
  model_file = None
277
289
  if hasattr(self, "model_file") and self.model_file is not None:
278
290
  shutil.copyfile(self.model_file.resolve(),
@@ -327,7 +339,6 @@ class OpenSeesRunner(Runner):
327
339
  # Create model
328
340
  import opensees.openseespy as ops
329
341
 
330
- import sys
331
342
  csi = self._csi
332
343
 
333
344
  with new_cd(self.runs[run_id]["run_output_directory"]):
@@ -445,6 +456,7 @@ class OpenSeesRunner(Runner):
445
456
  for metric in metrics:
446
457
  metric.record(asm)
447
458
 
459
+
448
460
  nt = 500
449
461
  dt = 0.02
450
462
  _create_excitation(model, self.predictor, self.runs[run_id]["inputs"], dt)
@@ -496,16 +508,16 @@ class OpenSeesRunner(Runner):
496
508
  # if type == "COLUMN_STRAIN_STATES":
497
509
  # return _clean_json(column_strain_state_metric(model, output_dir, config))
498
510
 
499
- if type == "PEAK_ACCEL":
500
- return _clean_json(peak_acceleration_metric(output_dir, config))
511
+ # if type == "PEAK_ACCEL":
512
+ # return _clean_json(peak_acceleration_metric(output_dir, config))
501
513
 
502
- elif type == "PEAK_DRIFT":
503
- return _clean_json(peak_drift_metric(model, output_dir, config))
514
+ # elif type == "PEAK_DRIFT":
515
+ # return _clean_json(peak_drift_metric(model, output_dir, config))
504
516
 
505
- elif type == "ACC_RESPONSE_HISTORY":
506
- # config = CONFIG
507
- # return accel_response_history_plot(output_dir, config)
508
- return {}
517
+ # elif type == "ACC_RESPONSE_HISTORY":
518
+ # # config = CONFIG
519
+ # # return accel_response_history_plot(output_dir, config)
520
+ # return {}
509
521
  return {}
510
522
 
511
523
  #
@@ -519,17 +531,40 @@ class OpenSeesRunner(Runner):
519
531
  try:
520
532
  csi_file = self.predictor.config_file
521
533
  self._csi_data = load_csi((str(line.decode()).replace("\r\n","\n") for line in csi_file.readlines()))
534
+
522
535
  except Exception as e:
523
536
  import sys
524
537
  print(f"Error loading CSiBridge file: {e}", file=sys.stderr)
525
- self._csi_data = None
538
+ self._csi_data = {}
526
539
 
527
540
  return self._csi_data
528
541
 
529
542
 
530
543
  def structural_section(self, name):
531
544
  if (s:= create_section(self._csi, name)) is not None:
532
- return {}, s._create_model(mesh_size=0.1)
545
+ model = s._create_model(mesh_size=0.1)
546
+ if model is None:
547
+ return [], None
548
+ cmm = model.cmm()
549
+ cnn = model.cnn()
550
+ cnm = model.cnm()
551
+ properties = [
552
+ {
553
+ "name": "Elastic", "data": [
554
+ {"name": "A", "value": float(cnn[0,0])},
555
+ {"name": "Iyy", "value": float(cmm[1,1])},
556
+ {"name": "Izz", "value": float(cmm[2,2])},
557
+ ],
558
+ },
559
+ {
560
+ "name": "Ultimate", "data": [
561
+ # {"name": "A", "value": float(cnn[0,0])},
562
+ # {"name": "Iyy", "value": float(cmm[1,1])},
563
+ # {"name": "Izz", "value": float(cmm[2,2])},
564
+ ]
565
+ }
566
+ ]
567
+ return properties, model
533
568
 
534
569
 
535
570
  def structural_sections(self):
@@ -7,10 +7,10 @@
7
7
  # Author: Claudio Perez
8
8
  #
9
9
  #----------------------------------------------------------------------------#
10
- from django.urls import re_path
10
+ from django.urls import path, re_path
11
11
  from .views import (
12
- asset_predictors, predictor_profile, predictor_render,
13
- create_mdof, create_model, asset_map
12
+ asset_predictors, predictor_profile, predictor_render, predictor_table,
13
+ create_mdof, create_model, asset_map, CsiUpload, FORMS
14
14
  )
15
15
 
16
16
  _ROOT = "^inventory/(?P<calid>[0-9 A-Z-]*)/predictors"
@@ -18,8 +18,10 @@ _ROOT = "^inventory/(?P<calid>[0-9 A-Z-]*)/predictors"
18
18
  urlpatterns = [
19
19
  re_path(f"{_ROOT}/(?P<preid>[0-9]{{1,}})/$", predictor_profile),
20
20
  re_path(f"{_ROOT}/(?P<preid>[0-9]{{1,}})/render/", predictor_render),
21
+ re_path(f"{_ROOT}/(?P<preid>[0-9]{{1,}})/properties/", predictor_table),
21
22
  re_path(f"{_ROOT}/create/map/$", asset_map),
22
- re_path(f"{_ROOT}/create/model/$", create_model),
23
+ # re_path(f"{_ROOT}/create/model/$", create_model),
24
+ re_path(f"{_ROOT}/create/model/$", CsiUpload.as_view(FORMS), name="csi_upload"),
23
25
  re_path(f"{_ROOT}/create/v1/$", create_mdof),
24
26
  re_path(f"{_ROOT}/$", asset_predictors, name="asset_predictors")
25
27
  ]
@@ -16,13 +16,15 @@ import uuid
16
16
  import base64
17
17
  import hashlib
18
18
 
19
+ from django.template.loader import render_to_string
20
+ from django.utils.html import escape
19
21
  from django.template import loader
20
22
  from django.contrib.auth.decorators import login_required
21
23
  from django.core.exceptions import ObjectDoesNotExist
22
24
  from django.core.files.base import ContentFile
23
25
  from django.shortcuts import HttpResponse, get_object_or_404
24
26
 
25
- from irie.apps.inventory.models import Asset
27
+ from irie.apps.inventory.models import Asset, Datum
26
28
  from irie.apps.prediction.predictor import PREDICTOR_TYPES
27
29
  from irie.apps.prediction.models import PredictorModel
28
30
  from .forms import PredictorForm
@@ -76,25 +78,93 @@ def asset_predictors(request, calid):
76
78
  def predictor_render(request, calid, preid):
77
79
 
78
80
  predictor = get_object_or_404(PredictorModel, pk=int(preid))
81
+
82
+ canvas = None
83
+
84
+ sname = request.GET.get("section", None)
85
+
86
+ runner = predictor.runner
87
+
88
+ if sname is None:
89
+ try:
90
+ artist = runner.render()
91
+ if artist is None:
92
+ return HttpResponse(
93
+ json.dumps({"error": "No rendering available"}),
94
+ content_type="application/json",
95
+ status=404
96
+ )
97
+ canvas = artist.canvas
98
+ except Exception as e:
99
+ return HttpResponse(
100
+ json.dumps({"error": str(e)}),
101
+ content_type="application/json",
102
+ status=500
103
+ )
104
+ else:
105
+ try:
106
+ _, mesh = runner.structural_section(sname)
107
+
108
+ artist = veux.create_artist(mesh.model, canvas="gltf")
109
+ artist.draw_surfaces()
110
+ # artist.draw_outlines()
111
+ canvas = artist.canvas
112
+ # canvas = veux._create_canvas(name="gltf")
113
+ import numpy as np
114
+ R = np.array([[1, 0],
115
+ [0, 1],
116
+ [0, 0]])
117
+ exterior = mesh.exterior()
118
+ exterior = np.append(exterior, np.array([[exterior[0][0], exterior[0][1]]]), axis=0)
119
+
120
+ canvas.plot_lines(exterior@R.T)
121
+ if (interior := mesh.interior()) is not None:
122
+ for i in interior:
123
+ i = np.append(i, np.array([[i[0][0], i[0][1]]]), axis=0)
124
+ canvas.plot_lines(i@R.T)
125
+ try:
126
+ canvas.plot_vectors(np.zeros((3,3)),
127
+ np.eye(3)*min(mesh.depth, mesh.width)/3, extrude=True)
128
+ except:
129
+ pass
130
+
131
+ except Exception as e:
132
+ raise e
133
+ return HttpResponse(
134
+ json.dumps({"error": "Section not found"}),
135
+ content_type="application/json",
136
+ status=404
137
+ )
138
+
139
+ if canvas is None:
140
+ return HttpResponse(
141
+ json.dumps({"error": "No rendering available"}),
142
+ content_type="application/json",
143
+ status=404
144
+ )
145
+
146
+ glb = canvas.to_glb()
147
+ return HttpResponse(glb, content_type="application/binary")
148
+
149
+
150
+ @login_required(login_url="/login/")
151
+ def predictor_table(request, calid, preid):
152
+
153
+ predictor = get_object_or_404(PredictorModel, pk=int(preid))
79
154
 
80
155
  sname = request.GET.get("section", None)
81
156
 
82
- runner = PREDICTOR_TYPES[predictor.protocol](predictor)
157
+ runner = predictor.runner
83
158
 
84
159
  try:
85
- _, mesh = runner.structural_section(sname)
86
-
87
- can = veux._create_canvas(name="gltf")
88
- import numpy as np
89
- R = np.array([[1, 0],[0, 1], [0, 0]])
90
- can.plot_lines(mesh.exterior()@R.T)
91
- if (interior := mesh.interior()) is not None:
92
- for i in interior:
93
- can.plot_lines(i@R.T)
94
- glb = can.to_glb()
95
- return HttpResponse(glb, content_type="application/binary")
160
+ properties, _ = runner.structural_section(sname)
161
+
162
+ data = json.dumps(properties)
163
+ return HttpResponse(data,
164
+ content_type="application/json")
96
165
 
97
166
  except Exception as e:
167
+ print(e)
98
168
  return HttpResponse(
99
169
  json.dumps({"error": "Section not found"}),
100
170
  content_type="application/json",
@@ -102,6 +172,44 @@ def predictor_render(request, calid, preid):
102
172
  )
103
173
 
104
174
 
175
+ @login_required(login_url="/login/")
176
+ def predictor_analysis(request, calid, preid):
177
+
178
+ predictor = get_object_or_404(PredictorModel, pk=int(preid))
179
+
180
+ runner = predictor.runner
181
+ sname = request.GET.get("section", None)
182
+
183
+ if sname is not None:
184
+
185
+ try:
186
+ _, mesh = runner.structural_section(sname)
187
+ except Exception as e:
188
+ return HttpResponse(
189
+ json.dumps({"error": "Section not found"}),
190
+ content_type="application/json",
191
+ status=404
192
+ )
193
+
194
+ try:
195
+ content = "<div></div>"
196
+ return HttpResponse(content)
197
+
198
+ except Exception as e:
199
+ return HttpResponse(
200
+ json.dumps({"error": str(e)}),
201
+ content_type="application/json",
202
+ status=500
203
+ )
204
+
205
+
206
+ return HttpResponse(
207
+ json.dumps({"error": str(e)}),
208
+ content_type="application/json",
209
+ status=500
210
+ )
211
+
212
+
105
213
  @login_required(login_url="/login/")
106
214
  def predictor_profile(request, calid, preid):
107
215
 
@@ -124,8 +232,8 @@ def predictor_profile(request, calid, preid):
124
232
 
125
233
  predictor = get_object_or_404(PredictorModel, pk=int(preid))
126
234
 
127
- context["asset"] = asset
128
- context["runner"] = PREDICTOR_TYPES[predictor.protocol](predictor)
235
+ context["asset"] = asset
236
+ context["runner"] = predictor.runner
129
237
  context["predictor"] = predictor
130
238
  context["sensors"] = predictor.sensorassignment_set.all()
131
239
 
@@ -151,7 +259,8 @@ def predictor_profile(request, calid, preid):
151
259
  if "DEBUG" in os.environ and os.environ["DEBUG"]:
152
260
  raise e
153
261
  html_template = loader.get_template("site/page-500.html")
154
- return HttpResponse(html_template.render(context, request))
262
+ return HttpResponse(html_template.render(context, request), status=500)
263
+
155
264
 
156
265
 
157
266
  @login_required(login_url="/login/")
@@ -204,7 +313,6 @@ def asset_map(request, calid):
204
313
  return HttpResponse(r200.render(context, request))
205
314
 
206
315
  except Exception as e:
207
- raise e
208
316
  r500 = loader.get_template("site/page-500.html")
209
317
  return HttpResponse(r500.render({"message": str(e)}, request), status=500)
210
318
 
@@ -220,7 +328,129 @@ def create_mdof(request):
220
328
  html_template = loader.get_template("prediction/" + page_template)
221
329
  return HttpResponse(html_template.render(context, request))
222
330
 
331
+ from formtools.wizard.views import SessionWizardView
332
+ from .forms.csi_upload import DatumCreateForm, DatumSelectForm, PredictorForm, SensorForm, ConfirmForm
333
+
334
+ FORMS = [
335
+ ("select datum", DatumSelectForm),
336
+ ("create datum", DatumCreateForm),
337
+ ("structure", PredictorForm),
338
+ ("sensor", SensorForm),
339
+ ("confirm", ConfirmForm),
340
+ ]
341
+
342
+ TEMPLATES = {
343
+ "select datum": "prediction/upload/step.html",
344
+ "create datum": "prediction/upload/step.html",
345
+ "structure": "prediction/upload/step.html",
346
+ "sensor": "prediction/upload/step.html",
347
+ "confirm": "prediction/upload/step.html",
348
+ }
349
+
350
+ from django.shortcuts import redirect
351
+ from django.core.files.storage import FileSystemStorage
352
+ from django.conf import settings
353
+
354
+ class CsiUpload(SessionWizardView):
355
+ form_list = FORMS
356
+ file_storage = FileSystemStorage(location=settings.MEDIA_ROOT)
357
+
358
+ condition_dict = {
359
+ "create datum": lambda self: (
360
+ self.get_cleaned_data_for_step("select datum") is None \
361
+ or self.get_cleaned_data_for_step("select datum").get("datum") is None
362
+ ),
363
+ }
364
+
365
+ def dispatch(self, request, *args, **kwargs):
366
+ self.asset = get_object_or_404(Asset, calid=kwargs["calid"])
367
+ self.request = request
368
+ return super().dispatch(request, *args, **kwargs)
369
+
370
+ def get_template_names(self):
371
+ return [TEMPLATES[self.steps.current]]
372
+
373
+ def get_form_kwargs(self, step=None):
374
+ kwargs = super().get_form_kwargs(step)
375
+
376
+ if step == "select datum":
377
+ kwargs["asset"] = self.asset
378
+ kwargs["initial"] = {"asset": self.asset.id}
379
+ kwargs["datum_queryset"] = Datum.objects.filter(asset=self.asset)
380
+ elif step == "create datum":
381
+ kwargs["asset"] = self.asset
382
+ kwargs["initial"] = {"asset": self.asset.id}
383
+ elif step == "structure":
384
+ kwargs["asset"] = self.asset
385
+ kwargs["initial"] = {
386
+ "asset": self.asset,
387
+ "active": False,
388
+ "protocol": PredictorModel.Protocol.TYPE1,
389
+ }
390
+ elif step == "sensor":
391
+ datum_data = self.get_cleaned_data_for_step("datum") or {}
392
+ kwargs["initial"] = {
393
+ # "datum": datum_data.get("id"),
394
+ "asset": self.asset.id,
395
+ "node": -1,
396
+ "predictor": self.get_cleaned_data_for_step("structure").get("id")
397
+ }
398
+ return kwargs
399
+
400
+ def get_context_data(self, form, **kwargs):
401
+ context = super().get_context_data(form=form, **kwargs)
402
+ context["asset"] = self.asset
403
+ context["segment"] = "assets"
404
+ context["step"] = self.steps.current
405
+
406
+ if self.steps.current == "confirm":
407
+ predictor = self.get_form("structure").save(commit=False)
408
+ model_file = self.get_cleaned_data_for_step("structure").get("config_file")
409
+
410
+ predictor.protocol = PredictorModel.Protocol.TYPE1
411
+ predictor.config_file = model_file
412
+ artist = predictor.runner.render()
413
+ glb = artist.canvas.to_glb()
414
+ glb64 = base64.b64encode(glb).decode("utf-8")
415
+
416
+ context["rndrdoc"] = escape(render_to_string(
417
+ "inventory/asset-on-map.html",
418
+ context={
419
+ "asset": self.asset,
420
+ "viewer": "three",
421
+ "scale": 1/3.2808, # TODO
422
+ # "offset": json.dumps(list(reversed(list(self.asset.coordinates)))),
423
+ "rotate": "[0, 0, 0]",
424
+ "render_glb": f"data:application/octet-stream;base64,{glb64}",
425
+ "location": json.dumps(list(reversed(list(self.asset.coordinates)))),
426
+ },
427
+ request=self.request
428
+ ))
429
+ return context
430
+
431
+ def done(self, form_list, form_dict, **kwargs):
432
+ # datum comes from select step unless create step ran
433
+ if "select datum" in form_dict:
434
+ datum = form_dict["select datum"].cleaned_data["datum"]
435
+ elif "datum create" in form_dict:
436
+ datum = form_dict["datum create"].save(commit=False)
437
+ datum.save()
438
+ else:
439
+ datum = form_dict["select datum"].cleaned_data["datum"]
440
+
441
+ predictor = form_dict["structure"].save(commit=False)
442
+ sensor = form_dict["sensor"].save(commit=False)
223
443
 
444
+ predictor.asset = self.asset
445
+ predictor.active = True
446
+ predictor.protocol = PredictorModel.Protocol.TYPE1
447
+ predictor.save()
448
+
449
+ sensor.node = -1
450
+ sensor.predictor = predictor
451
+ sensor.save()
452
+ return redirect("asset_predictors", calid=predictor.asset.calid)
453
+
224
454
  @login_required(login_url="/login/")
225
455
  def create_model(request, calid):
226
456
 
@@ -252,6 +482,7 @@ def create_model(request, calid):
252
482
  except Exception as e:
253
483
  return HttpResponse(r400.render({"message": json.dumps({"error": str(e)})}), status=500)
254
484
 
485
+
255
486
  # 3) Render the model
256
487
  outlines = collect_outlines(csi, model.frame_tags)
257
488
  artist = veux.create_artist(model,
@@ -5577,7 +5577,7 @@ textarea.form-control-lg {
5577
5577
  --bs-accordion-body-padding-x: 1.25rem;
5578
5578
  --bs-accordion-body-padding-y: 1rem;
5579
5579
  --bs-accordion-active-color: rgb(27.9, 36.9, 49.5);
5580
- --bs-accordion-active-bg: #9CA3AF;
5580
+ --bs-accordion-active-bg: transparent;
5581
5581
  }
5582
5582
 
5583
5583
  .accordion-button {