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

@@ -96,6 +96,7 @@ class Asset(models.Model):
96
96
  ordering = ["-id"]
97
97
 
98
98
 
99
+
99
100
  class Vulnerability: # (models.Model):
100
101
  type = None
101
102
  asset = None
@@ -125,6 +126,7 @@ class SensorGroup(models.Model):
125
126
  def __str__(self):
126
127
  return f"{self.asset.calid} - {self.name} ({self.datum})"
127
128
 
129
+
128
130
  class Sensor(models.Model):
129
131
  # class Status:
130
132
  # active: bool
@@ -138,12 +140,31 @@ class Sensor(models.Model):
138
140
  dy = models.DecimalField(decimal_places=2, max_digits=10)
139
141
  dz = models.DecimalField(decimal_places=2, max_digits=10)
140
142
 
141
- group = models.ForeignKey(SensorGroup, related_name="sensors", on_delete=models.RESTRICT)
143
+ group = models.ForeignKey(SensorGroup,
144
+ related_name="sensors",
145
+ on_delete=models.RESTRICT)
142
146
 
143
147
  def __str__(self):
144
148
  return f"{self.group.asset.calid} - {self.name} ({self.group.name})"
145
149
 
146
150
 
151
+ def acceleration(self, event):
152
+ import quakeio
153
+
154
+ motion_data = (
155
+ quakeio.read(
156
+ event.event_file.path, input_format="csmip.zip"
157
+ )
158
+ )
159
+
160
+ series = motion_data.match("l", station_channel=f"{self.name}").accel.data
161
+
162
+ return [
163
+ (series*float(self.dx)).tolist(),
164
+ (series*float(self.dy)).tolist(),
165
+ (series*float(self.dz)).tolist()
166
+ ]
167
+
147
168
 
148
169
  # class Rendering:
149
170
  # def __init__(self, url=None, units, datum):
@@ -25,8 +25,11 @@ urlpatterns = [
25
25
  ),
26
26
  re_path("^inventory/(?P<calid>[0-9 A-Z-]*)/evaluations/$", views.asset_evals, name="asset_evals"),
27
27
  re_path("^inventory/(?P<calid>[0-9 A-Z-]*)/$", views.asset_profile, name="asset_profile"),
28
+ # Sensors
28
29
  re_path("^inventory/(?P<calid>[0-9 A-Z-]*)/sensors/$", views.asset_sensors, name="asset_sensors"),
29
30
  re_path("^inventory/(?P<calid>[0-9 A-Z-]*)/sensor_upload", views.sensor_upload, name="sensor_upload"),
31
+ path("inventory/<slug:calid>/sensors/<int:group_id>/edit/", views.sensor_edit, name="sensor_edit"),
32
+
30
33
 
31
34
  path("inventory/map2/", views.map_inventory),
32
35
  path("california.json", views.california_json),
@@ -408,14 +408,16 @@ def asset_sensors(request, calid):
408
408
  asset = Asset.objects.get(calid=calid)
409
409
  context = {
410
410
  "asset": asset,
411
+ "segment": "assets",
411
412
  "groups": SensorGroup.objects.filter(asset=asset)
412
413
  }
413
414
  html_template = loader.get_template("inventory/asset-sensors.html")
414
415
  return HttpResponse(html_template.render(context, request))
415
416
 
417
+
416
418
  @login_required(login_url="/login/")
417
419
  def sensor_upload(request, calid):
418
- asset = get_object_or_404(Asset, calid=calid) # Fetch the asset using calid
420
+ asset = get_object_or_404(Asset, calid=calid)
419
421
  datums = Datum.objects.filter(asset=asset).values(
420
422
  'id', 'orient_x', 'locate_x', 'orient_y', 'locate_y', 'orient_z', 'locate_z'
421
423
  )
@@ -443,7 +445,8 @@ def sensor_upload(request, calid):
443
445
  formset = SensorFormSet()
444
446
 
445
447
  context = {
446
- "group_form": group_form,
448
+ "group_form": group_form,
449
+ "segment": "assets",
447
450
  "formset": formset,
448
451
  "renderings": [
449
452
  {"name": predictor.name, "glb": predictor.render_file.url}
@@ -456,6 +459,68 @@ def sensor_upload(request, calid):
456
459
  return render(request, "inventory/sensor-upload.html", context)
457
460
 
458
461
 
462
+ @login_required(login_url="/login/")
463
+ def sensor_edit(request, calid, group_id):
464
+ """
465
+ Edit an existing SensorGroup + its Sensors while re-using the
466
+ sensor-upload.html template.
467
+ """
468
+ from django.forms import modelformset_factory
469
+
470
+ asset = get_object_or_404(Asset, calid=calid)
471
+ sensor_group = get_object_or_404(SensorGroup, pk=group_id, asset=asset)
472
+
473
+ #
474
+ SensorFormSet = modelformset_factory(
475
+ Sensor,
476
+ form=SensorForm,
477
+ extra=0,
478
+ can_delete=True,
479
+ )
480
+
481
+ if request.method == "POST":
482
+ group_form = SensorGroupForm(request.POST, instance=sensor_group, asset=asset)
483
+ formset = SensorFormSet(request.POST, queryset=sensor_group.sensors.all())
484
+
485
+ if group_form.is_valid() and formset.is_valid():
486
+ # update the group fields
487
+ group_form.save()
488
+
489
+ # saves edits, additions (extra rows you may allow), and deletions
490
+ sensors = formset.save(commit=False)
491
+ for sensor in sensors:
492
+ sensor.group = sensor_group
493
+ sensor.save()
494
+
495
+ # any forms flagged for deletion come back in formset.deleted_objects
496
+ for obj in formset.deleted_objects:
497
+ obj.delete()
498
+
499
+ return redirect("asset_sensors", calid=calid)
500
+ else:
501
+ group_form = SensorGroupForm(instance=sensor_group, asset=asset)
502
+ formset = SensorFormSet(queryset=sensor_group.sensors.all())
503
+
504
+ context = {
505
+ "group_form": group_form,
506
+ "segment": "assets",
507
+ "formset": formset,
508
+ "is_edit": True, # optional flag so the template can tweak titles/buttons
509
+ "renderings": [
510
+ {"name": p.name, "glb": p.render_file.url}
511
+ for p in PredictorModel.objects.filter(asset=asset, protocol="IRIE_PREDICTOR_V1")
512
+ if p.render_file and p.render_file.url
513
+ ],
514
+ "asset": asset,
515
+ "datums": list(
516
+ Datum.objects.filter(asset=asset).values(
517
+ "id", "orient_x", "locate_x", "orient_y", "locate_y", "orient_z", "locate_z"
518
+ )
519
+ ),
520
+ }
521
+ return render(request, "inventory/sensor-upload.html", context)
522
+
523
+
459
524
  def _filter_asset_table(request):
460
525
  # Copy the GET parameters and remove the 'page' parameter
461
526
  page_query = request.GET.copy()
@@ -34,8 +34,10 @@ class PredictorModel(models.Model):
34
34
  def __str__(self):
35
35
  return f"{self.asset.calid} - {self.name} : {self.description}"
36
36
 
37
- def get_runner(self):
38
- pass
37
+ @property
38
+ def runner(self):
39
+ from irie.apps.prediction.predictor import PREDICTOR_TYPES
40
+ return PREDICTOR_TYPES[self.protocol](self)
39
41
 
40
42
  def get_artist(self):
41
43
  pass
@@ -53,9 +55,9 @@ class SensorAssignment(models.Model):
53
55
  ]
54
56
  )
55
57
 
56
- orient_z = models.FloatField()
57
- orient_x = models.FloatField()
58
- orient_y = models.FloatField()
58
+ # orient_z = models.FloatField()
59
+ # orient_x = models.FloatField()
60
+ # orient_y = models.FloatField()
59
61
  # show_x = models.FloatField()
60
62
  # show_y = models.FloatField()
61
63
  # show_z = models.FloatField()
@@ -10,6 +10,14 @@ import sys, json
10
10
  import zipfile
11
11
  from pathlib import Path
12
12
  import contextlib
13
+ try:
14
+ from xcsi.csi import collect_outlines, load as load_csi
15
+ from xcsi.csi._frame.section import create_section
16
+ from xcsi.csi._frame.section import iter_sections
17
+ from xcsi.job import Job
18
+ from xcsi.metrics import PeakDrift
19
+ except:
20
+ pass
13
21
 
14
22
  from irie.apps.prediction.runners import (Runner, RunID, classproperty)
15
23
 
@@ -26,6 +34,37 @@ OPENSEES = [
26
34
  ]
27
35
 
28
36
 
37
+ def _create_excitation(model, predictor, inputs, dt):
38
+ import numpy as np
39
+ # rotation = predictor.config["orientation"]
40
+ rotation = np.eye(3)*150
41
+ i = 1
42
+ for sensor in predictor.sensorassignment_set.all():
43
+ if sensor.role == "input":
44
+ for dof in range(3):
45
+ series = sum(ai*dx
46
+ for ai, dx in zip(np.array(inputs[sensor.id]["series"]), rotation[dof]))
47
+
48
+ model.timeSeries("Path", i, dt=dt, values=series.tolist())
49
+ model.pattern("UniformExcitation", i, dof+1, accel=i)
50
+ i += 1
51
+
52
+
53
+ def _analyze_and_render(model, artist, nt, dt):
54
+ import veux, veux.motion
55
+ motion = veux.motion.Motion(artist)
56
+ for i in range(nt):
57
+ if model.analyze(1, dt) != 0:
58
+ return -1
59
+ motion.advance(i*dt)
60
+ motion.draw_sections(position=lambda x: [1000*u for u in model.nodeDisp(x)],
61
+ rotation=model.nodeRotation)
62
+
63
+ motion.add_to(artist.canvas)
64
+ return 0
65
+
66
+
67
+
29
68
  @contextlib.contextmanager
30
69
  def new_cd(x):
31
70
  d = os.getcwd()
@@ -199,10 +238,14 @@ class OpenSeesRunner(Runner):
199
238
  def newPrediction(self, event, output_directory = None):
200
239
  """
201
240
  Create a new prediction run and return the run_id. If output_directory is None,
202
- the output directory will be created automatically. Otherwise, the output directory
203
- will be copied to the new output directory.
241
+ the output directory will be created automatically.
204
242
  """
205
- event = event.event_file.path
243
+ inputs = {}
244
+ for sensor in self.predictor.sensorassignment_set.all():
245
+ if sensor.role == "input":
246
+ inputs[sensor.id] = {"series": sensor.sensor.acceleration(event)}
247
+
248
+
206
249
  if output_directory is not None:
207
250
  # this case will eventually be deleted, its just for
208
251
  # debugging metric renderers.
@@ -225,23 +268,30 @@ class OpenSeesRunner(Runner):
225
268
  run_dir.mkdir(parents=True, exist_ok=False)
226
269
 
227
270
  # Copy files to run directory
228
- shutil.copyfile(event, run_dir/"event.zip")
229
- shutil.copyfile(self.model_file.resolve(), run_dir/self.model_file.name)
230
-
231
- if self.model_file.suffix == ".zip":
232
- with zipfile.ZipFile(self.model_file, 'r') as zip_ref:
233
- zip_ref.extractall(run_dir)
234
- model_file = (run_dir/"nonlinear.tcl").resolve()
235
-
236
- elif self.model_file.suffix == ".b2k":
237
- pass
238
-
239
- elif self.model_file.suffix == ".tcl":
240
- model_file = (run_dir/self.model_file.name).resolve()
271
+ if False:
272
+ event = event.event_file.path
273
+ shutil.copyfile(event, run_dir/"event.zip")
274
+
275
+ model_file = None
276
+ if hasattr(self, "model_file") and self.model_file is not None:
277
+ shutil.copyfile(self.model_file.resolve(),
278
+ run_dir/self.model_file.name)
279
+
280
+ if self.model_file.suffix == ".zip":
281
+ with zipfile.ZipFile(self.model_file, 'r') as zip_ref:
282
+ zip_ref.extractall(run_dir)
283
+ model_file = (run_dir/"nonlinear.tcl").resolve()
284
+
285
+ elif self.model_file.suffix == ".b2k":
286
+ pass
287
+
288
+ elif self.model_file.suffix == ".tcl":
289
+ model_file = (run_dir/self.model_file.name).resolve()
241
290
 
242
291
  self.runs[run_id] = {
243
292
  "run_output_directory": run_dir,
244
- "event_file_name": Path(event),
293
+ # "event_file_name": Path(event),
294
+ "inputs": inputs,
245
295
  "model_file": model_file,
246
296
  **self.conf
247
297
  }
@@ -264,56 +314,33 @@ class OpenSeesRunner(Runner):
264
314
  if run_id not in self.runs:
265
315
  self._load_config(run_id)
266
316
 
267
- event_file_path = os.path.relpath(self.runs[run_id]["event_file_name"],
268
- self.model_file.parents[0])
269
- output_directory = os.path.relpath(self.runs[run_id]["run_output_directory"],
270
- self.model_file.parents[0])
317
+ if False:
318
+ event_file_path = os.path.relpath(self.runs[run_id]["event_file_name"],
319
+ self.model_file.parents[0])
320
+
321
+ output_directory = os.path.relpath(self.runs[run_id]["run_output_directory"],
322
+ self.model_file.parents[0])
271
323
 
272
- event_file_path = self.runs[run_id]["event_file_name"]
324
+ event_file_path = self.runs[run_id]["event_file_name"]
273
325
 
274
326
  # Create model
275
327
  import opensees.openseespy as ops
276
328
 
277
329
  import sys
278
- model = ops.Model(echo_file=sys.stdout)
279
- model.eval("set argv {}")
280
- with new_cd(self.runs[run_id]["run_output_directory"]):
281
- model.eval(f"source {self.runs[run_id]['model_file']}")
282
-
283
- model.eval(f"print -json -file modelDetails.json")
330
+ csi = self._csi
284
331
 
285
- model.eval(f"set python {sys.executable}")
332
+ with new_cd(self.runs[run_id]["run_output_directory"]):
286
333
 
287
- model.eval(r"""
288
- proc py {args} {
289
- global python
290
- eval "[exec {*}$python {*}$args]"
291
- }
292
-
293
- proc pt {args} {
294
- global python
295
- puts "[exec {*}$python {*}$args]"
296
- }
334
+ model = ops.Model(ndm=3, ndf=6, echo_file=open("model.tcl", "w"))
335
+ if csi is not None:
336
+ asm = Job(csi).assemble(model=model)
337
+ # model = create_model(csi, model=model)
338
+ sections = collect_outlines(csi, model.frame_tags)
339
+ else:
340
+ asm = None
341
+ sections = None
342
+ model.eval(f"source {self.runs[run_id]['model_file']}")
297
343
 
298
- proc write_modes {mode_file nmodes} {
299
- set fid_modes [open $mode_file w+]
300
- for {set m 1} {$m <= $nmodes} {incr m} {
301
- puts $fid_modes "$m:"
302
- foreach n [getNodeTags] {
303
- puts $fid_modes " $n: \[[join [nodeEigenvector $n $m] {, }]\]";
304
- }
305
- }
306
- close $fid_modes
307
- }
308
- proc write_displacements {file_name {resp Disp}} {
309
- set fid [open "$file_name" "w+"]
310
- puts $fid "[getTime]:"
311
- foreach n [getNodeTags] {
312
- puts $fid " $n: \[[join [node${resp} $n] {, }]\]";
313
- }
314
- close $fid;
315
- }
316
- """)
317
344
 
318
345
  #
319
346
  # Run gravity analysis
@@ -328,61 +355,60 @@ class OpenSeesRunner(Runner):
328
355
  system SparseGeneral;
329
356
  analysis Static;
330
357
  analyze 10;
331
- # write_displacements "dispsGrav.yaml"
332
358
  """)
333
359
 
334
360
  #
335
361
  # DAMPING
336
362
  #
337
- model.eval(r"""
338
- set nmodes 8; # Number of modes to analyze for modal analysis
339
-
340
- # set wb [eigen -fullGenLapack $nmodes];
341
- # puts "\tFundamental-Period After Gravity Analysis:"
342
- # for {set iPd 1} {$iPd <= $nmodes} {incr iPd 1} {
343
- # set wwb [lindex $wb $iPd-1];
344
- # set Tb [expr 2*$pi/sqrt($wwb)];
345
- # puts "\tPeriod$iPd= $Tb"
363
+ # model.eval(r"""
364
+ # set nmodes 8; # Number of modes to analyze for modal analysis
365
+
366
+ # # set wb [eigen -fullGenLapack $nmodes];
367
+ # # puts "\tFundamental-Period After Gravity Analysis:"
368
+ # # for {set iPd 1} {$iPd <= $nmodes} {incr iPd 1} {
369
+ # # set wwb [lindex $wb $iPd-1];
370
+ # # set Tb [expr 2*$pi/sqrt($wwb)];
371
+ # # puts "\tPeriod$iPd= $Tb"
372
+ # # }
373
+ # # write_modes $output_directory/modesPostG.yaml $nmodes
374
+ # # remove recorders
375
+
376
+ # set nmodes [tcl::mathfunc::max {*}$damping_modes $nmodes]
377
+ # set lambdaN [eigen -fullGenLapack $nmodes];
378
+
379
+ # # set lambdaN [eigen $nmodes];
380
+ # if {$damping_type == "rayleigh"} {
381
+ # set nEigenI [lindex $damping_modes 0]; # first rayleigh damping mode
382
+ # set nEigenJ [lindex $damping_modes 1]; # second rayleigh damping mode
383
+ # set iDamp [lindex $damping_ratios 0]; # first rayleigh damping ratio
384
+ # set jDamp [lindex $damping_ratios 1]; # second rayleigh damping ratio
385
+ # set lambdaI [lindex $lambdaN [expr $nEigenI-1]];
386
+ # set lambdaJ [lindex $lambdaN [expr $nEigenJ-1]];
387
+ # set omegaI [expr $lambdaI**0.5];
388
+ # set omegaJ [expr $lambdaJ**0.5];
389
+ # set TI [expr 2.0*$pi/$omegaI];
390
+ # set TJ [expr 2.0*$pi/$omegaJ];
391
+ # set alpha0 [expr 2.0*($iDamp/$omegaI-$jDamp/$omegaJ)/(1/$omegaI**2-1/$omegaJ**2)];
392
+ # set alpha1 [expr 2.0*$iDamp/$omegaI-$alpha0/$omegaI**2];
393
+ # puts "\tRayleigh damping parameters:"
394
+ # puts "\tmodes: $nEigenI, $nEigenJ ; ratios: $iDamp, $jDamp"
395
+ # puts "\tTI = $TI; TJ = $TJ"
396
+ # puts "\tlambdaI = $lambdaI; lambdaJ = $lambdaJ"
397
+ # puts "\tomegaI = $omegaI; omegaJ = $omegaJ"
398
+ # puts "\talpha0 = $alpha0; alpha1 = $alpha1"
399
+ # rayleigh $alpha0 0.0 0.0 $alpha1;
400
+
401
+ # } elseif {$damping_type == "modal"} {
402
+ # # needs a bit of edit. currently assuming that the ratios are applied in order at the first modes. but should be applied at the specified damping_modes modes.
403
+ # set nratios [llength $damping_ratios]
404
+ # puts "\tModal damping parameters:"
405
+ # puts "\tratios of $damping_ratios at the first $nratios modes"
406
+ # for {set i 1} {$i <= [expr $nmodes - $nratios]} {incr i} {
407
+ # lappend damping_ratios 0
408
+ # }
409
+ # modalDamping {*}$damping_ratios
346
410
  # }
347
- # write_modes $output_directory/modesPostG.yaml $nmodes
348
- # remove recorders
349
-
350
- set nmodes [tcl::mathfunc::max {*}$damping_modes $nmodes]
351
- set lambdaN [eigen -fullGenLapack $nmodes];
352
-
353
- # set lambdaN [eigen $nmodes];
354
- if {$damping_type == "rayleigh"} {
355
- set nEigenI [lindex $damping_modes 0]; # first rayleigh damping mode
356
- set nEigenJ [lindex $damping_modes 1]; # second rayleigh damping mode
357
- set iDamp [lindex $damping_ratios 0]; # first rayleigh damping ratio
358
- set jDamp [lindex $damping_ratios 1]; # second rayleigh damping ratio
359
- set lambdaI [lindex $lambdaN [expr $nEigenI-1]];
360
- set lambdaJ [lindex $lambdaN [expr $nEigenJ-1]];
361
- set omegaI [expr $lambdaI**0.5];
362
- set omegaJ [expr $lambdaJ**0.5];
363
- set TI [expr 2.0*$pi/$omegaI];
364
- set TJ [expr 2.0*$pi/$omegaJ];
365
- set alpha0 [expr 2.0*($iDamp/$omegaI-$jDamp/$omegaJ)/(1/$omegaI**2-1/$omegaJ**2)];
366
- set alpha1 [expr 2.0*$iDamp/$omegaI-$alpha0/$omegaI**2];
367
- puts "\tRayleigh damping parameters:"
368
- puts "\tmodes: $nEigenI, $nEigenJ ; ratios: $iDamp, $jDamp"
369
- puts "\tTI = $TI; TJ = $TJ"
370
- puts "\tlambdaI = $lambdaI; lambdaJ = $lambdaJ"
371
- puts "\tomegaI = $omegaI; omegaJ = $omegaJ"
372
- puts "\talpha0 = $alpha0; alpha1 = $alpha1"
373
- rayleigh $alpha0 0.0 0.0 $alpha1;
374
-
375
- } elseif {$damping_type == "modal"} {
376
- # needs a bit of edit. currently assuming that the ratios are applied in order at the first modes. but should be applied at the specified damping_modes modes.
377
- set nratios [llength $damping_ratios]
378
- puts "\tModal damping parameters:"
379
- puts "\tratios of $damping_ratios at the first $nratios modes"
380
- for {set i 1} {$i <= [expr $nmodes - $nratios]} {incr i} {
381
- lappend damping_ratios 0
382
- }
383
- modalDamping {*}$damping_ratios
384
- }
385
- """)
411
+ # """)
386
412
 
387
413
 
388
414
  #
@@ -390,73 +416,60 @@ class OpenSeesRunner(Runner):
390
416
  #
391
417
 
392
418
  ## COLUMN SECTION DEFORMATIONS AT TOP AND BOTTOM FOR STRAIN-BASED DAMAGE STATES
393
- column_strains = tuple(k["key"] for k in self.runs[run_id]["columns"] if k["strain"])
394
- if len(column_strains) > 0:
395
- model.recorder("Element", "section", 1, "deformation", xml="eleDef1.txt", ele=column_strains) # section 1 deformation]
396
- model.recorder("Element", "section", 4, "deformation", xml="eleDef4.txt", ele=column_strains) # section 4 deformation]
397
-
398
-
419
+ if False:
420
+ column_strains = tuple(k["key"] for k in self.runs[run_id]["columns"] if k["strain"])
421
+ if len(column_strains) > 0:
422
+ model.recorder("Element", "section", 1, "deformation", xml="eleDef1.txt", ele=column_strains) # section 1 deformation]
423
+ model.recorder("Element", "section", 4, "deformation", xml="eleDef4.txt", ele=column_strains) # section 4 deformation]
399
424
 
400
425
  #
401
426
  # Run dynamic analysis
402
427
  #
403
- model.eval(f"""
404
- wipeAnalysis
405
- # Uniform Support Excitation
406
- # lassign [pt -m CE58658.makePattern {event_file_path} --scale $dynamic_scale_factor --node $input_location] dt steps
407
- # lassign [py -m CE58658.makePattern {event_file_path} --scale $dynamic_scale_factor --node $input_location] dt steps
408
- set dt 0.1
409
- set steps 3
410
- """)
411
428
 
412
429
  # RESPONSE HISTORY RECORDERS
430
+ if False:
431
+ model.recorder("Node", "accel", xml="model/AA_all.txt", timeSeries=(1, 2), dof=(1, 2))
432
+ model.recorder("Node", "accel", xml="model/RD_all.txt", dof=(1, 2))
413
433
 
414
- model.recorder("Node", "accel", xml="model/AA_all.txt", timeSeries=(1, 2), dof=(1, 2))
415
- model.recorder("Node", "accel", xml="model/RD_all.txt", dof=(1, 2))
434
+ column_nodes = tuple(k["node"] for k in self.runs[run_id]["bents"] if k["record"])
435
+ model.recorder("Node", "accel", file="TopColAccel_X_txt.txt", timeSeries=1 , node=column_nodes, dof=1)
436
+ model.recorder("Node", "accel", file="TopColAccel_Y_txt.txt", timeSeries=2 , node=column_nodes, dof=2)
437
+ model.recorder("Node", "disp", file="TopColDrift_X_txt.txt", node=column_nodes, dof=1)
438
+ model.recorder("Node", "disp", file="TopColDrift_Y_txt.txt", node=column_nodes, dof=2)
416
439
 
417
- column_nodes = tuple(k["node"] for k in self.runs[run_id]["bents"] if k["record"])
418
- model.recorder("Node", "accel", file="TopColAccel_X_txt.txt", timeSeries=1 , node=column_nodes, dof=1)
419
- model.recorder("Node", "accel", file="TopColAccel_Y_txt.txt", timeSeries=2 , node=column_nodes, dof=2)
420
- model.recorder("Node", "disp", file="TopColDrift_X_txt.txt", node=column_nodes, dof=1)
421
- model.recorder("Node", "disp", file="TopColDrift_Y_txt.txt", node=column_nodes, dof=2)
440
+ metrics = [
441
+ PeakDrift((31, 81))
442
+ ]
422
443
 
423
- model.eval("""
424
- set dtfact 1;
425
- set Tol 1.0e-8;
426
- set maxNumIter 100;
427
- set printFlag 0;
428
- set TestType EnergyIncr;
444
+ for metric in metrics:
445
+ metric.record(asm)
446
+
447
+ nt = 500
448
+ dt = 0.02
449
+ _create_excitation(model, self.predictor, self.runs[run_id]["inputs"], dt)
450
+
451
+ model.eval(f"print -json -file model.json")
452
+
453
+ model.eval(f"""
454
+ wipeAnalysis
429
455
  set NewmarkGamma 0.50;
430
- set NewmarkBeta 0.25;
456
+ set NewmarkBeta 0.25;
431
457
  constraints Transformation;
432
- numberer RCM;
433
- test $TestType $Tol $maxNumIter $printFlag;
434
- set algorithmType "Newton";
435
- system BandSPD;
436
- integrator Newmark $NewmarkGamma $NewmarkBeta;
437
-
438
- algorithm {*}$algorithmType;
439
- analysis Transient;
440
-
441
- set DtAnalysis $dt;
442
- set TmaxAnalysis [expr $dt*$steps];
443
- set Nsteps $steps;
444
- if {$dynamic_truncated != 0} {
445
- set Nsteps $dynamic_timesteps;
446
- }
447
- puts "\tGround Motion: dt= $DtAnalysis, NumPts= $Nsteps, TmaxAnalysis= $TmaxAnalysis";
448
-
449
- puts "\tRunning dynamic ground motion analysis..."
450
- set t3 [clock clicks -millisec];
451
- catch {progress create $Nsteps} _
458
+ numberer RCM;
459
+ test EnergyIncr 1.0e-6 50 0;
460
+ system Umfpack;
461
+ integrator Newmark $NewmarkGamma $NewmarkBeta;
462
+ algorithm Newton;
463
+ analysis Transient;
464
+ """)
452
465
 
453
- analyze 2 $DtAnalysis;
466
+ import veux
467
+ artist = veux.create_artist(model, vertical=3, model_config={
468
+ "frame_outlines": sections
469
+ })
470
+ _analyze_and_render(model, artist, nt, dt)
454
471
 
455
- # for {set ik 1} {$ik <= $Nsteps} {incr ik 1} {
456
- # catch {progress update} _
457
- # set ok [analyze 1 $DtAnalysis];
458
- # }
459
- """)
472
+ artist.save("motion.glb")
460
473
 
461
474
  model.wipe()
462
475
 
@@ -477,10 +490,7 @@ class OpenSeesRunner(Runner):
477
490
  else:
478
491
  output_dir = self.out_dir/str(run_id)
479
492
 
480
- # with open(output_dir/"modelDetails.json", "r") as f:
481
- # model = json.load(f)
482
-
483
- model = read_model(output_dir/"modelDetails.json")
493
+ model = read_model(output_dir/"model.json")
484
494
 
485
495
  # if type == "COLUMN_STRAIN_STATES":
486
496
  # return _clean_json(column_strain_state_metric(model, output_dir, config))
@@ -504,11 +514,10 @@ class OpenSeesRunner(Runner):
504
514
  @property
505
515
  def _csi(self):
506
516
  if not hasattr(self, "_csi_data") or self._csi_data is None:
507
- from openbim.csi import load, create_model, collect_outlines
508
517
  # 1) Parse the CSI file
509
518
  try:
510
519
  csi_file = self.predictor.config_file
511
- self._csi_data = load((str(line.decode()).replace("\r\n","\n") for line in csi_file.readlines()))
520
+ self._csi_data = load_csi((str(line.decode()).replace("\r\n","\n") for line in csi_file.readlines()))
512
521
  except Exception as e:
513
522
  import sys
514
523
  print(f"Error loading CSiBridge file: {e}", file=sys.stderr)
@@ -518,21 +527,13 @@ class OpenSeesRunner(Runner):
518
527
 
519
528
 
520
529
  def structural_section(self, name):
521
- from openbim.csi._frame.section import create_section
522
- # from openbim.csi._frame.outlines import section_mesh
523
530
  if (s:= create_section(self._csi, name)) is not None:
524
531
  return {}, s._create_model(mesh_size=0.1)
525
532
 
526
533
 
527
- def structural_sections(self):
528
- from openbim.csi._frame.section import iter_sections
534
+ def structural_sections(self):
529
535
  yield from iter_sections(self._csi)
530
- # for s, name in iter_sections(self._csi):
531
- # yield {
532
- # "name": name,
533
- # "type": "Section",
534
- # "section": name,
535
- # }
536
+
536
537
 
537
538
  def structural_members(self):
538
539