midas-civil 0.0.7__tar.gz → 0.0.8__tar.gz

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 midas-civil might be problematic. Click here for more details.

Files changed (26) hide show
  1. {midas_civil-0.0.7 → midas_civil-0.0.8}/PKG-INFO +1 -1
  2. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_boundary.py +248 -10
  3. midas_civil-0.0.8/midas_civil/_element.py +496 -0
  4. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil.egg-info/PKG-INFO +1 -1
  5. {midas_civil-0.0.7 → midas_civil-0.0.8}/setup.py +1 -1
  6. midas_civil-0.0.7/midas_civil/_element.py +0 -275
  7. {midas_civil-0.0.7 → midas_civil-0.0.8}/LICENSE +0 -0
  8. {midas_civil-0.0.7 → midas_civil-0.0.8}/README.md +0 -0
  9. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/__init__.py +0 -0
  10. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_construction.py +0 -0
  11. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_group.py +0 -0
  12. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_load.py +0 -0
  13. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_mapi.py +0 -0
  14. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_material.py +0 -0
  15. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_model.py +0 -0
  16. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_node.py +0 -0
  17. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_result.py +0 -0
  18. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_result_extract.py +0 -0
  19. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_section.py +0 -0
  20. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_thickness.py +0 -0
  21. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil/_utils.py +0 -0
  22. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil.egg-info/SOURCES.txt +0 -0
  23. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil.egg-info/dependency_links.txt +0 -0
  24. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil.egg-info/requires.txt +0 -0
  25. {midas_civil-0.0.7 → midas_civil-0.0.8}/midas_civil.egg-info/top_level.txt +0 -0
  26. {midas_civil-0.0.7 → midas_civil-0.0.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: midas_civil
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: Python library for MIDAS Civil NX
5
5
  Author: Sumit Shekhar
6
6
  Author-email: sumit.midasit@gmail.com
@@ -11,7 +11,6 @@ def convList(item):
11
11
 
12
12
 
13
13
 
14
-
15
14
  class Boundary:
16
15
 
17
16
  @classmethod
@@ -20,6 +19,8 @@ class Boundary:
20
19
  if cls.Support.sups!=[]: cls.Support.create()
21
20
  if cls.ElasticLink.links!=[]: cls.ElasticLink.create()
22
21
  if cls.RigidLink.links!=[]: cls.RigidLink.create()
22
+ if cls.MLFC.func!=[]: cls.RigidLink.create()
23
+ if cls.PointSpring.springs!=[]: cls.PointSpring.create()
23
24
 
24
25
 
25
26
  @classmethod
@@ -28,6 +29,8 @@ class Boundary:
28
29
  cls.Support.delete()
29
30
  cls.ElasticLink.delete()
30
31
  cls.RigidLink.delete()
32
+ cls.MLFC.delete()
33
+ cls.PointSpring.delete()
31
34
 
32
35
  @classmethod
33
36
  def sync(cls):
@@ -35,8 +38,8 @@ class Boundary:
35
38
  cls.Support.sync()
36
39
  cls.ElasticLink.sync()
37
40
  cls.RigidLink.sync()
38
-
39
-
41
+ cls.MLFC.sync()
42
+ cls.PointSpring.delete()
40
43
 
41
44
 
42
45
  class Support:
@@ -506,10 +509,33 @@ class Boundary:
506
509
  #---------------------------------------------------------------------------------------------------------------
507
510
 
508
511
  class MLFC:
512
+
509
513
  func = []
510
514
  _id = []
511
515
 
512
- def __init__(self,name,type:str='FORCE',symm:bool=True,data:list=[[0,0],[1,1]],id=None):
516
+ def __init__(self, name:str, type:str='FORCE', symm:bool=True, data:list=[[0,0],[1,1]], id:int=None):
517
+ """
518
+ Force-Deformation Function constructor.
519
+
520
+ Parameters:
521
+ name (str): The name for the Force-Deformation Function.
522
+ type (str, optional): Type of function, either "FORCE" or "MOMENT". Defaults to "FORCE".
523
+ symm (bool, optional): Defines if the function is symmetric (True) or unsymmetric (False). Defaults to True.
524
+ data (list[list[float]], optional): A list of [X, Y] coordinate pairs defining the function curve. Required.
525
+ - For "FORCE" type: X is displacement, Y is force.
526
+ - For "MOMENT" type: X is rotation in radians, Y is moment.
527
+ Defaults to [[0,0],[1,1]].
528
+ id (int, optional): The function ID. If not provided, it will be auto-assigned.
529
+
530
+ Examples:
531
+ ```python
532
+ # Create a symmetric force vs. displacement function
533
+ Boundary.MLFC(name="MyForceFunction", type="FORCE", symm=True, data=[[0,0], [0.1, 100], [0.2, 150]])
534
+
535
+ # Create an unsymmetric moment vs. rotation function with a specific ID
536
+ Boundary.MLFC(name="MyMomentFunction", type="MOMENT", symm=False, data=[[0,0], [0.01, 500], [0.02, 750]], id=5)
537
+ ```
538
+ """
513
539
  self.NAME = name
514
540
  self.TYPE = type
515
541
  self.SYMM = symm
@@ -534,6 +560,7 @@ class Boundary:
534
560
 
535
561
  @classmethod
536
562
  def json(cls):
563
+
537
564
  json = {"Assign": {}}
538
565
  for fn in cls.func:
539
566
  json["Assign"][fn.ID]={
@@ -550,22 +577,20 @@ class Boundary:
550
577
  @classmethod
551
578
  def create(cls):
552
579
  """
553
- Sends all FUNC data to Midas API.
580
+ Sends all FUNC data.
554
581
  """
555
582
  MidasAPI("PUT", "/db/MLFC", cls.json())
556
583
 
557
584
  @classmethod
558
585
  def get(cls):
559
586
  """
560
- Retrieves data from Midas API.
587
+ Retrieves data.
561
588
  """
562
589
  return MidasAPI("GET", "/db/MLFC")
563
590
 
564
591
  @classmethod
565
592
  def sync(cls):
566
- """
567
- Updates the class with data from the Midas API.
568
- """
593
+
569
594
  cls.links = []
570
595
  a = cls.get()
571
596
  if a != {'message': ''}:
@@ -584,4 +609,217 @@ class Boundary:
584
609
  Deletes all func from the database and resets the class.
585
610
  """
586
611
  cls.links = []
587
- return MidasAPI("DELETE", "/db/MLFC")
612
+ return MidasAPI("DELETE", "/db/MLFC")
613
+
614
+ #--------------------------------------------------------------------------------------------------------------------------------------
615
+
616
+
617
+ class PointSpring:
618
+ """Create Point Spring Object in Python"""
619
+ springs = []
620
+
621
+ def __init__(self,
622
+ node: int,
623
+ spring_type: str = "LINEAR",
624
+ group: str = "",
625
+ id: int = None,
626
+ stiffness: list = None,
627
+ fixed_option: list = None,
628
+ damping: list = None,
629
+ direction: str = "Dx+",
630
+ normal_vector: list = None,
631
+ function_id: int = 1):
632
+ """
633
+ Point Spring constructor.
634
+
635
+ Parameters:
636
+ node: Node ID where spring is applied
637
+ spring_type: Type of spring ("LINEAR", "COMP", "TENS", "MULTI")
638
+ group: Group name (default "")
639
+ id: Spring ID (optional, auto-assigned if None)
640
+ stiffness: Spring stiffness values [SDx, SDy, SDz, SRx, SRy, SRz] or single value for COMP/TENS
641
+ fixed_option: Fixed option array [Boolean, 6] for LINEAR type
642
+ damping: Damping values [Cx, Cy, Cz, CRx, CRy, CRz] (if provided, damping is enabled)
643
+ direction: Direction string ("Dx+", "Dx-", "Dy+", "Dy-", "Dz+", "Dz-", "Vector")
644
+ normal_vector: Normal vector [x, y, z] when direction is "Vector"
645
+ function_id: Function ID for MULTI type
646
+
647
+ Examples:
648
+ # Linear spring
649
+ PointSpring(1, "LINEAR", "Group1", stiffness=[1000, 1000, 1000, 100, 100, 100])
650
+
651
+ # Compression only spring
652
+ PointSpring(2, "COMP", "Group2", stiffness=5000, direction="Dz+")
653
+
654
+ # Tension only spring with vector direction
655
+ PointSpring(3, "TENS", "Group3", stiffness=3000, direction="Vector", normal_vector=[0, -1, -1])
656
+
657
+ # Multi-linear spring
658
+ PointSpring(4, "MULTI", "Group4", direction="Dz+", function_id=1)
659
+ """
660
+
661
+ # Check if group exists, create if not
662
+ if group != "":
663
+ chk = 0
664
+ a = [v['NAME'] for v in Group.Boundary.json()["Assign"].values()]
665
+ if group in a:
666
+ chk = 1
667
+ if chk == 0:
668
+ Group.Boundary(group)
669
+
670
+ # Validate spring type
671
+ valid_types = ["LINEAR", "COMP", "TENS", "MULTI"]
672
+ if spring_type not in valid_types:
673
+ spring_type = "LINEAR"
674
+
675
+ self.NODE = node
676
+ self.TYPE = spring_type
677
+ self.GROUP_NAME = group
678
+
679
+ # Convert direction string to integer
680
+ direction_map = {
681
+ "Dx+": 0, "Dx-": 1, "Dy+": 2, "Dy-": 3,
682
+ "Dz+": 4, "Dz-": 5, "Vector": 6
683
+ }
684
+ self.DIR = direction_map.get(direction, 0)
685
+
686
+ # Auto-assign ID if not provided
687
+ if id is None:
688
+ self.ID = len(Boundary.PointSpring.springs) + 1
689
+ else:
690
+ self.ID = id
691
+
692
+ # Type-specific parameters
693
+ if spring_type == "LINEAR":
694
+ self.SDR = stiffness if stiffness else [0, 0, 0, 0, 0, 0]
695
+ self.F_S = fixed_option if fixed_option else [False] * 6
696
+ # Damping logic: if damping values provided, enable damping
697
+ self.DAMPING = damping is not None and any(d != 0 for d in damping) if damping else False
698
+ self.Cr = damping if damping else [0, 0, 0, 0, 0, 0]
699
+
700
+ elif spring_type in ["COMP", "TENS"]:
701
+ self.STIFF = stiffness if stiffness else 0
702
+ self.DV = normal_vector if normal_vector else [0, 0, 0]
703
+
704
+ elif spring_type == "MULTI":
705
+ self.DV = normal_vector if normal_vector else [0, 0, 0]
706
+ self.FUNCTION = function_id
707
+
708
+ # Add to static list
709
+ Boundary.PointSpring.springs.append(self)
710
+
711
+ @classmethod
712
+ def json(cls):
713
+ """
714
+ Converts PointSpring data to JSON format for API submission.
715
+ """
716
+ json_data = {"Assign": {}}
717
+
718
+ for spring in cls.springs:
719
+ spring_data = {
720
+ "ID": spring.ID,
721
+ "TYPE": spring.TYPE,
722
+ "GROUP_NAME": spring.GROUP_NAME
723
+ }
724
+
725
+ # Add type-specific parameters
726
+ if spring.TYPE == "LINEAR":
727
+ spring_data["SDR"] = spring.SDR
728
+ spring_data["F_S"] = spring.F_S
729
+ spring_data["DAMPING"] = spring.DAMPING
730
+ if spring.DAMPING:
731
+ spring_data["Cr"] = spring.Cr
732
+
733
+ elif spring.TYPE in ["COMP", "TENS"]:
734
+ spring_data["STIFF"] = spring.STIFF
735
+ spring_data["DIR"] = spring.DIR
736
+ spring_data["DV"] = spring.DV
737
+
738
+ elif spring.TYPE == "MULTI":
739
+ spring_data["DIR"] = spring.DIR
740
+ spring_data["DV"] = spring.DV
741
+ spring_data["FUNCTION"] = spring.FUNCTION
742
+
743
+ json_data["Assign"][spring.NODE] = {"ITEMS": [spring_data]}
744
+
745
+ return json_data
746
+
747
+ @classmethod
748
+ def create(cls):
749
+ """
750
+ Sends all PointSpring data
751
+ """
752
+ MidasAPI("PUT", "/db/nspr", cls.json())
753
+
754
+ @classmethod
755
+ def get(cls):
756
+ """
757
+ Retrieves PointSpring data
758
+ """
759
+ return MidasAPI("GET", "/db/nspr")
760
+
761
+ @classmethod
762
+ def sync(cls):
763
+ """
764
+ Updates the PointSpring class with data
765
+ """
766
+ cls.springs = []
767
+ a = cls.get()
768
+
769
+ if a != {'message': ''}:
770
+ for node_id, node_data in a.get("NSPR", {}).items():
771
+ for item in node_data.get("ITEMS"):
772
+ spring_type = item.get("TYPE")
773
+ group_name = item.get("GROUP_NAME")
774
+ spring_id = item.get("ID", 1)
775
+
776
+ # Extract type-specific parameters
777
+ if spring_type == "LINEAR":
778
+ stiffness = item.get("SDR")
779
+ fixed_option = item.get("F_S")
780
+ damping = item.get("Cr") if item.get("DAMPING", False) else None
781
+
782
+ # Convert direction back to string
783
+ dir_map = {0: "Dx+", 1: "Dx-", 2: "Dy+", 3: "Dy-", 4: "Dz+", 5: "Dz-", 6: "Vector"}
784
+ direction_str = dir_map.get(0, "Dx+") # Default for LINEAR
785
+
786
+ Boundary.PointSpring(
787
+ int(node_id), spring_type, group_name, spring_id,
788
+ stiffness, fixed_option, damping, direction_str
789
+ )
790
+
791
+ elif spring_type in ["COMP", "TENS"]:
792
+ stiffness = item.get("STIFF")
793
+ direction_int = item.get("DIR")
794
+ normal_vector = item.get("DV")
795
+
796
+ # Convert direction back to string
797
+ dir_map = {0: "Dx+", 1: "Dx-", 2: "Dy+", 3: "Dy-", 4: "Dz+", 5: "Dz-", 6: "Vector"}
798
+ direction_str = dir_map.get(direction_int, "Dx+")
799
+
800
+ Boundary.PointSpring(
801
+ int(node_id), spring_type, group_name, spring_id,
802
+ stiffness, None, None, direction_str, normal_vector
803
+ )
804
+
805
+ elif spring_type == "MULTI":
806
+ direction_int = item.get("DIR")
807
+ normal_vector = item.get("DV")
808
+ function_id = item.get("FUNCTION")
809
+
810
+ # Convert direction back to string
811
+ dir_map = {0: "Dx+", 1: "Dx-", 2: "Dy+", 3: "Dy-", 4: "Dz+", 5: "Dz-", 6: "Vector"}
812
+ direction_str = dir_map.get(direction_int, "Dx+")
813
+
814
+ Boundary.PointSpring(
815
+ int(node_id), spring_type, group_name, spring_id,
816
+ None, None, None, direction_str, normal_vector, function_id
817
+ )
818
+
819
+ @classmethod
820
+ def delete(cls):
821
+ """
822
+ Deletes all point springs from the database and resets the class.
823
+ """
824
+ cls.springs = []
825
+ return MidasAPI("DELETE", "/db/nspr")
@@ -0,0 +1,496 @@
1
+ from ._mapi import *
2
+ from ._node import *
3
+
4
+ import numpy as np
5
+
6
+ def _ADD(self):
7
+ """
8
+ Adds an element to the main list. If the ID is 0, it auto-increments.
9
+ If the ID already exists, it replaces the existing element.
10
+ """
11
+ id = int(self.ID)
12
+ if not Element.ids:
13
+ count = 1
14
+ else:
15
+ count = max(Element.ids) + 1
16
+
17
+ if id == 0:
18
+ self.ID = count
19
+ Element.elements.append(self)
20
+ Element.ids.append(int(self.ID))
21
+ elif id in Element.ids:
22
+ self.ID = int(id)
23
+ print(f'⚠️ Element with ID {id} already exists! It will be replaced.')
24
+ index = Element.ids.index(id)
25
+ Element.elements[index] = self
26
+ else:
27
+ self.ID = id
28
+ Element.elements.append(self)
29
+ Element.ids.append(int(self.ID))
30
+
31
+ def _updateElem(self):
32
+ """Sends a PUT request to update a single element in Midas."""
33
+ js2s = {'Assign': {self.ID: _Obj2JS(self)}}
34
+ MidasAPI('PUT', '/db/elem', js2s)
35
+ return js2s
36
+
37
+ def _Obj2JS(obj):
38
+ """Converts a Python element object to its JSON dictionary representation."""
39
+ # Base attributes common to many elements
40
+ js = {
41
+ "TYPE": obj.TYPE,
42
+ "MATL": obj.MATL,
43
+ "SECT": obj.SECT,
44
+ "NODE": obj.NODE,
45
+ }
46
+
47
+ # Add optional attributes if they exist on the object
48
+ if hasattr(obj, 'ANGLE'): js["ANGLE"] = obj.ANGLE
49
+ if hasattr(obj, 'STYPE'): js["STYPE"] = obj.STYPE
50
+
51
+ # Handle type-specific and subtype-specific attributes
52
+ if obj.TYPE == 'TENSTR': # Tension/Hook/Cable
53
+ # Tension-only (stype=1) - can have TENS parameter
54
+ if obj.STYPE == 1:
55
+ if hasattr(obj, 'TENS'): js["TENS"] = obj.TENS
56
+ if hasattr(obj, 'T_LIMIT'): js["T_LIMIT"] = obj.T_LIMIT
57
+ if hasattr(obj, 'T_bLMT'): js["T_bLMT"] = obj.T_bLMT
58
+
59
+ # Hook (stype=2) - has NON_LEN parameter
60
+ elif obj.STYPE == 2:
61
+ if hasattr(obj, 'NON_LEN'): js["NON_LEN"] = obj.NON_LEN
62
+
63
+ # Cable (stype=3) - has CABLE, NON_LEN, and TENS parameters
64
+ elif obj.STYPE == 3:
65
+ if hasattr(obj, 'CABLE'): js["CABLE"] = obj.CABLE
66
+ if hasattr(obj, 'NON_LEN'): js["NON_LEN"] = obj.NON_LEN
67
+ if hasattr(obj, 'TENS'): js["TENS"] = obj.TENS
68
+
69
+ elif obj.TYPE == 'COMPTR': # Compression/Gap
70
+ # Compression-only (stype=1) - can have TENS, T_LIMIT, T_bLMT
71
+ if obj.STYPE == 1:
72
+ if hasattr(obj, 'TENS'): js["TENS"] = obj.TENS
73
+ if hasattr(obj, 'T_LIMIT'): js["T_LIMIT"] = obj.T_LIMIT
74
+ if hasattr(obj, 'T_bLMT'): js["T_bLMT"] = obj.T_bLMT
75
+
76
+ # Gap (stype=2) - has NON_LEN parameter
77
+ elif obj.STYPE == 2:
78
+ if hasattr(obj, 'NON_LEN'): js["NON_LEN"] = obj.NON_LEN
79
+
80
+ return js
81
+
82
+ def _JS2Obj(id, js):
83
+ """Converts a JSON dictionary back into a Python element object during sync."""
84
+ elem_type = js.get('TYPE')
85
+
86
+ # Prepare arguments for constructors
87
+ args = {
88
+ 'id': int(id),
89
+ 'mat': js.get('MATL'),
90
+ 'sect': js.get('SECT'),
91
+ 'node': js.get('NODE'),
92
+ 'angle': js.get('ANGLE'),
93
+ 'stype': js.get('STYPE')
94
+ }
95
+
96
+ # Prepare individual parameters for optional/subtype-specific parameters
97
+ non_len = js.get('NON_LEN')
98
+ cable_type = js.get('CABLE')
99
+ tens = js.get('TENS')
100
+ t_limit = js.get('T_LIMIT')
101
+
102
+ if elem_type == 'BEAM':
103
+ Element.Beam(args['node'][0], args['node'][1], args['mat'], args['sect'], args['angle'], args['id'])
104
+ elif elem_type == 'TRUSS':
105
+ Element.Truss(args['node'][0], args['node'][1], args['mat'], args['sect'], args['angle'], args['id'])
106
+ elif elem_type == 'PLATE':
107
+ Element.Plate(args['node'], args['stype'], args['mat'], args['sect'], args['angle'], args['id'])
108
+ elif elem_type == 'TENSTR':
109
+ Element.Tension(args['node'][0], args['node'][1], args['stype'], args['mat'], args['sect'], args['angle'], args['id'], non_len, cable_type, tens, t_limit)
110
+ elif elem_type == 'COMPTR':
111
+ Element.Compression(args['node'][0], args['node'][1], args['stype'], args['mat'], args['sect'], args['angle'], args['id'], tens, t_limit, non_len)
112
+ elif elem_type == 'SOLID':
113
+ Element.Solid(nodes=args['node'], mat=args['mat'], sect=args['sect'], id=args['id'])
114
+
115
+
116
+ class _common:
117
+ """Common base class for all element types."""
118
+ def __str__(self):
119
+ return str(f'ID = {self.ID} \nJSON : {_Obj2JS(self)}\n')
120
+
121
+ def update(self):
122
+ return _updateElem(self)
123
+
124
+ # --- Main Element Class ---
125
+ class Element:
126
+ """
127
+ Main class to create and manage structural elements like Beams, Trusses,
128
+ Plates, Tension/Compression-only elements, and Solids.
129
+ """
130
+ elements = []
131
+ ids = []
132
+
133
+ @classmethod
134
+ def json(cls):
135
+ json_data = {"Assign": {}}
136
+ for elem in cls.elements:
137
+ js = _Obj2JS(elem)
138
+ json_data["Assign"][elem.ID] = js
139
+ return json_data
140
+
141
+ @classmethod
142
+ def create(cls):
143
+ if cls.elements:
144
+ MidasAPI("PUT", "/db/ELEM", Element.json())
145
+
146
+ @staticmethod
147
+ def get():
148
+ return MidasAPI("GET", "/db/ELEM")
149
+
150
+ @staticmethod
151
+ def sync():
152
+ a = Element.get()
153
+ if a and 'ELEM' in a and a['ELEM']:
154
+ Element.elements = []
155
+ Element.ids = []
156
+ for elem_id, data in a['ELEM'].items():
157
+ _JS2Obj(elem_id, data)
158
+
159
+ @staticmethod
160
+ def delete():
161
+ MidasAPI("DELETE", "/db/ELEM")
162
+ Element.elements = []
163
+ Element.ids = []
164
+
165
+ # --- Element Type Subclasses ---
166
+
167
+ class Beam(_common):
168
+
169
+ def __init__(self, i: int, j: int, mat: int = 1, sect: int = 1, angle: float = 0, id: int = 0):
170
+ """
171
+ Creates a BEAM element for frame analysis.
172
+
173
+ Parameters:
174
+ i: Start node ID
175
+ j: End node ID
176
+ mat: Material property number (default 1)
177
+ sect: Section property number (default 1)
178
+ angle: Beta angle for section orientation in degrees (default 0.0)
179
+ id: Element ID (default 0 for auto-increment)
180
+
181
+ Examples:
182
+ ```python
183
+ # Simple beam with default properties
184
+ Element.Beam(1, 2)
185
+
186
+ # Beam with specific material and section
187
+ Element.Beam(1, 2, mat=2, sect=3)
188
+
189
+ # Beam with 90° rotation (strong axis vertical)
190
+ Element.Beam(1, 2, mat=1, sect=1, angle=90.0)
191
+
192
+ # Beam with specific ID
193
+ Element.Beam(1, 2, mat=1, sect=1, angle=0.0, id=100)
194
+ ```
195
+ """
196
+ self.ID = id
197
+ self.TYPE = 'BEAM'
198
+ self.MATL = mat
199
+ self.SECT = sect
200
+ self.NODE = [i, j]
201
+ self.ANGLE = angle
202
+ _ADD(self)
203
+
204
+ @staticmethod
205
+ def SDL(s_loc:list,dir:list,l:float,n:int=1,mat:int=1,sect:int=1,angle:float=0,id:int=0): #CHANGE TO TUPLE
206
+ beam_nodes =[]
207
+ beam_obj = []
208
+ s_locc = np.array(s_loc)
209
+ unit_vec = np.array(dir)/np.linalg.norm(dir)
210
+
211
+ for i in range(n+1):
212
+ locc = s_locc+i*l*unit_vec/n
213
+ Enode=Node(locc[0].item(),locc[1].item(),locc[2].item())
214
+ beam_nodes.append(Enode.ID)
215
+
216
+ for i in range(n):
217
+ if id == 0 : id_new = 0
218
+ else: id_new = id+i
219
+ beam_obj.append(Element.Beam(beam_nodes[i],beam_nodes[i+1],mat,sect,angle,id_new))
220
+
221
+ return beam_obj
222
+
223
+
224
+ @staticmethod
225
+ def SE(s_loc:list,e_loc:list,n:int=1,mat:int=1,sect:int=1,angle:float=0,id:int=0):
226
+ beam_nodes =[]
227
+ beam_obj = []
228
+ i_loc = np.linspace(s_loc,e_loc,n+1)
229
+ for i in range(n+1):
230
+ Enode=Node(i_loc[i][0].item(),i_loc[i][1].item(),i_loc[i][2].item())
231
+ beam_nodes.append(Enode.ID)
232
+
233
+ for i in range(n):
234
+ if id == 0 : id_new = 0
235
+ else: id_new = id+i
236
+ beam_obj.append(Element.Beam(beam_nodes[i],beam_nodes[i+1],mat,sect,angle,id_new))
237
+
238
+ return beam_obj
239
+
240
+ class Truss(_common):
241
+ def __init__(self, i: int, j: int, mat: int = 1, sect: int = 1, angle: float = 0, id: int = 0):
242
+ """
243
+ Creates a TRUSS element
244
+
245
+ Parameters:
246
+ i: Start node ID
247
+ j: End node ID
248
+ mat: Material property number (default 1)
249
+ sect: Section property number (default 1)
250
+ angle: Beta angle for section orientation in degrees (default 0.0)
251
+ id: Element ID (default 0 for auto-increment)
252
+
253
+ Examples:
254
+ ```python
255
+ # Simple truss member
256
+ Element.Truss(1, 2)
257
+
258
+ # Truss with specific material and section
259
+ Element.Truss(1, 2, mat=3, sect=2)
260
+
261
+ # Diagonal truss member
262
+ Element.Truss(3, 4, mat=1, sect=1, id=50)
263
+ ```
264
+ """
265
+ self.ID = id
266
+ self.TYPE = 'TRUSS'
267
+ self.MATL = mat
268
+ self.SECT = sect
269
+ self.NODE = [i, j]
270
+ self.ANGLE = angle
271
+ _ADD(self)
272
+
273
+ @staticmethod
274
+ def SDL(s_loc:list,dir:list,l:float,n:int=1,mat:int=1,sect:int=1,angle:float=0,id:int=0):
275
+ beam_nodes =[]
276
+ beam_obj =[]
277
+ s_locc = np.array(s_loc)
278
+ unit_vec = np.array(dir)/np.linalg.norm(dir)
279
+
280
+ for i in range(n+1):
281
+ locc = s_locc+i*l*unit_vec/n
282
+ Enode=Node(locc[0].item(),locc[1].item(),locc[2].item())
283
+ beam_nodes.append(Enode.ID)
284
+
285
+ for i in range(n):
286
+ if id == 0 : id_new = 0
287
+ else: id_new = id+i
288
+ beam_obj.append(Element.Truss(beam_nodes[i],beam_nodes[i+1],mat,sect,angle,id_new))
289
+
290
+ return beam_obj
291
+
292
+
293
+ @staticmethod
294
+ def SE(s_loc:list,e_loc:list,n:int=1,mat:int=1,sect:int=1,angle:float=0,id:int=0):
295
+ beam_nodes =[]
296
+ beam_obj = []
297
+ i_loc = np.linspace(s_loc,e_loc,n+1)
298
+ for i in range(n+1):
299
+ Enode=Node(i_loc[i][0].item(),i_loc[i][1].item(),i_loc[i][2].item())
300
+ beam_nodes.append(Enode.ID)
301
+
302
+ for i in range(n):
303
+ if id == 0 : id_new = 0
304
+ else: id_new = id+i
305
+ beam_obj.append(Element.Truss(beam_nodes[i],beam_nodes[i+1],mat,sect,angle,id_new))
306
+
307
+ return beam_obj
308
+
309
+ class Plate(_common):
310
+ def __init__(self, nodes: list, stype: int = 1, mat: int = 1, sect: int = 1, angle: float = 0, id: int = 0):
311
+ """
312
+ Creates a PLATE element.
313
+
314
+ Parameters:
315
+ nodes: List of node IDs [n1, n2, n3] for triangular or [n1, n2, n3, n4] for quadrilateral
316
+ stype: Plate subtype (1=Thick plate, 2=Thin plate, 3=With drilling DOF) (default 1)
317
+ mat: Material property number (default 1)
318
+ sect: Section (thickness) property number (default 1)
319
+ angle: Material angle for orthotropic materials in degrees (default 0.0)
320
+ id: Element ID (default 0 for auto-increment)
321
+
322
+ Examples:
323
+ ```python
324
+ # Triangular thick plate
325
+ Element.Plate([1, 2, 3], stype=1, mat=1, sect=1)
326
+
327
+ # Quadrilateral thin plate
328
+ Element.Plate([1, 2, 3, 4], stype=2, mat=2, sect=1)
329
+
330
+ # Plate with drilling DOF for shell analysis
331
+ Element.Plate([5, 6, 7, 8], stype=3, mat=1, sect=2, angle=45.0)
332
+ ```
333
+ """
334
+ self.ID = id
335
+ self.TYPE = 'PLATE'
336
+ self.MATL = mat
337
+ self.SECT = sect
338
+ self.NODE = nodes
339
+ self.ANGLE = angle
340
+ self.STYPE = stype
341
+ _ADD(self)
342
+
343
+ class Tension(_common):
344
+ def __init__(self, i: int, j: int, stype: int, mat: int = 1, sect: int = 1, angle: float = 0, id: int = 0, non_len: float = None, cable_type: int = None, tens: float = None, t_limit: float = None):
345
+ """
346
+ Creates a TENSTR (Tension-only) element.
347
+
348
+ Parameters:
349
+ i: Start node ID
350
+ j: End node ID
351
+ stype: Tension element subtype (1=Tension-only, 2=Hook, 3=Cable)
352
+ mat: Material property number (default 1)
353
+ sect: Section property number (default 1)
354
+ angle: Beta angle for section orientation in degrees (default 0.0)
355
+ id: Element ID (default 0 for auto-increment)
356
+ non_len: Non-linear length parameter for Hook/Cable (default None)
357
+ cable_type: Cable type for stype=3 (1=Pretension, 2=Horizontal, 3=Lu) (default None)
358
+ tens: Initial tension force or allowable compression (default None)
359
+ t_limit: Tension limit value. If provided, the tension limit flag is set to True. (default None)
360
+
361
+ Examples:
362
+ ```python
363
+ # Simple tension-only member
364
+ Element.Tension(1, 2, stype=1)
365
+
366
+ # Tension-only with allowable compression and a tension limit
367
+ Element.Tension(1, 2, stype=1, tens=0.5, t_limit=-15)
368
+
369
+ # Hook element with slack length
370
+ Element.Tension(3, 4, stype=2, non_len=0.5)
371
+
372
+ # Cable with initial tension and catenary effects
373
+ Element.Tension(5, 6, stype=3, cable_type=3, tens=1000.0, non_len=0.1)
374
+ ```
375
+ """
376
+ self.ID = id
377
+ self.TYPE = 'TENSTR'
378
+ self.MATL = mat
379
+ self.SECT = sect
380
+ self.NODE = [i, j]
381
+ self.ANGLE = angle
382
+ self.STYPE = stype
383
+
384
+ # Handle subtype-specific parameters
385
+ if stype == 1: # Tension-only specific
386
+ if tens is not None:
387
+ self.TENS = tens
388
+ if t_limit is not None:
389
+ self.T_LIMIT = t_limit
390
+ self.T_bLMT = True
391
+
392
+ elif stype == 2: # Hook specific
393
+ if non_len is not None:
394
+ self.NON_LEN = non_len
395
+
396
+ elif stype == 3: # Cable specific
397
+ if cable_type is not None:
398
+ self.CABLE = cable_type
399
+ if non_len is not None:
400
+ self.NON_LEN = non_len
401
+ if tens is not None:
402
+ self.TENS = tens
403
+ _ADD(self)
404
+
405
+ class Compression(_common):
406
+ def __init__(self, i: int, j: int, stype: int, mat: int = 1, sect: int = 1, angle: float = 0, id: int = 0, tens: float = None, t_limit: float = None, non_len: float = None):
407
+ """
408
+ Creates a COMPTR (Compression-only) element.
409
+
410
+ Parameters:
411
+ i: Start node ID
412
+ j: End node ID
413
+ stype: Compression element subtype (1=Compression-only, 2=Gap)
414
+ mat: Material property number (default 1)
415
+ sect: Section property number (default 1)
416
+ angle: Beta angle for section orientation in degrees (default 0.0)
417
+ id: Element ID (default 0 for auto-increment)
418
+ tens: Allowable tension or initial compression force (default None)
419
+ t_limit: Compression limit value. If provided, the compression limit flag is set to True. (default None)
420
+ non_len: Non-linear length parameter for gap (default None)
421
+
422
+ Examples:
423
+ ```python
424
+ # Simple compression-only member
425
+ Element.Compression(1, 2, stype=1)
426
+
427
+ # Compression-only with tension limit and buckling limit
428
+ Element.Compression(1, 2, stype=1, tens=27, t_limit=-15)
429
+
430
+ # Gap element with initial gap
431
+ Element.Compression(3, 4, stype=2, non_len=0.25)
432
+ ```
433
+ """
434
+ self.ID = id
435
+ self.TYPE = 'COMPTR'
436
+ self.MATL = mat
437
+ self.SECT = sect
438
+ self.NODE = [i, j]
439
+ self.ANGLE = angle
440
+ self.STYPE = stype
441
+
442
+ # Handle subtype-specific parameters
443
+ if stype == 1: # Compression-only specific
444
+ if tens is not None:
445
+ self.TENS = tens
446
+ if t_limit is not None:
447
+ self.T_LIMIT = t_limit
448
+ self.T_bLMT = True
449
+
450
+ elif stype == 2: # Gap specific
451
+ if non_len is not None:
452
+ self.NON_LEN = non_len
453
+ _ADD(self)
454
+
455
+ class Solid(_common):
456
+ def __init__(self, nodes: list, mat: int = 1, sect: int = 0, id: int = 0):
457
+ """
458
+ Creates a SOLID element for 3D analysis.
459
+
460
+ Parameters:
461
+ nodes: List of node IDs defining the solid element
462
+ - 4 nodes: Tetrahedral element
463
+ - 6 nodes: Pentahedral element
464
+ - 8 nodes: Hexahedral element
465
+ mat: Material property number (default 1)
466
+ id: Element ID (default 0 for auto-increment)
467
+
468
+ Examples:
469
+ ```python
470
+ # Tetrahedral solid element
471
+ Element.Solid([1, 2, 3, 4], mat=1)
472
+
473
+ # Wedge solid element
474
+ Element.Solid([1, 2, 3, 4, 5, 6], mat=2)
475
+
476
+ # Hexahedral solid element
477
+ Element.Solid([1, 2, 3, 4, 5, 6, 7, 8], mat=1, id=200)
478
+ ```
479
+ """
480
+ if len(nodes) not in [4, 6, 8]:
481
+ raise ValueError("Solid element must have 4, 6, or 8 nodes.")
482
+ self.ID = id
483
+ self.TYPE = 'SOLID'
484
+ self.MATL = mat
485
+ self.SECT = sect # Solid elements don't use section properties
486
+ self.NODE = nodes
487
+ _ADD(self)
488
+
489
+
490
+
491
+
492
+
493
+
494
+
495
+
496
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: midas_civil
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: Python library for MIDAS Civil NX
5
5
  Author: Sumit Shekhar
6
6
  Author-email: sumit.midasit@gmail.com
@@ -5,7 +5,7 @@ with open('README.md','r') as f:
5
5
 
6
6
 
7
7
  setup(name='midas_civil',
8
- version='0.0.7',
8
+ version='0.0.8',
9
9
  description='Python library for MIDAS Civil NX',
10
10
  author='Sumit Shekhar',
11
11
  author_email='sumit.midasit@gmail.com',
@@ -1,275 +0,0 @@
1
- from ._mapi import *
2
- from ._node import *
3
-
4
- import numpy as np
5
-
6
- def _ADD(self):
7
- # Commom HERE ---------------------------------------------
8
- id = int(self.ID)
9
- if Element.ids == []:
10
- count = 1
11
- else:
12
- count = max(Element.ids)+1
13
-
14
- if id==0 :
15
- self.ID = count
16
- Element.elements.append(self)
17
- Element.ids.append(int(self.ID))
18
- elif id in Element.ids:
19
- self.ID=int(id)
20
- print(f'⚠️ Element with ID {id} already exist! It will be replaced.')
21
- index=Element.ids.index(id)
22
- Element.elements[index]=self
23
- else:
24
- self.ID=id
25
- Element.elements.append(self)
26
- Element.ids.append(int(self.ID))
27
- # Common END -------------------------------------------------------
28
-
29
-
30
- def _updateElem(self):
31
- js2s = {'Assign':{self.ID : _Obj2JS(self)}}
32
- MidasAPI('PUT','/db/elem',js2s)
33
- return js2s
34
-
35
-
36
-
37
-
38
- def _Obj2JS(obj):
39
-
40
- js={}
41
-
42
- if obj.TYPE == 'BEAM':
43
- #--- BEAM ---------------------------------------
44
- js = {
45
- "TYPE": obj.TYPE,
46
- "MATL": obj.MATL,
47
- "SECT": obj.SECT,
48
- "NODE": [
49
- obj.NODE[0],
50
- obj.NODE[1]
51
- ],
52
- "ANGLE": obj.ANGLE
53
- }
54
- elif obj.TYPE == 'TRUSS':
55
- #--- TRUSS ---------------------------------------
56
- js = {
57
- "TYPE": obj.TYPE,
58
- "MATL": obj.MATL,
59
- "SECT": obj.SECT,
60
- "NODE": [
61
- obj.NODE[0],
62
- obj.NODE[1]
63
- ],
64
- "ANGLE": obj.ANGLE
65
- }
66
-
67
- elif obj.TYPE == 'PLATE':
68
- #--- PLATE ---------------------------------------
69
- js = {
70
- "TYPE": obj.TYPE,
71
- "MATL": obj.MATL,
72
- "SECT": obj.SECT,
73
- "NODE": obj.NODE,
74
- "ANGLE": obj.ANGLE,
75
- "STYPE": obj.STYPE
76
- }
77
-
78
- return js
79
-
80
- def _JS2Obj(id,js):
81
- type = js['TYPE']
82
- matl = js['MATL']
83
- sect = js['SECT']
84
- node = js['NODE']
85
- angle = js['ANGLE']
86
-
87
- if type == 'BEAM':
88
- Element.Beam(node[0],node[1],matl,sect,angle,id)
89
- elif type == 'TRUSS':
90
- Element.Truss(node[0],node[1],matl,sect,angle,id)
91
- elif type == 'PLATE':
92
- stype = js['STYPE']
93
- Element.Plate(node,stype,matl,sect,angle,id)
94
-
95
-
96
-
97
- class _common:
98
- def __str__(self):
99
- return str(f'ID = {self.ID} \nJSON : {_Obj2JS(self)}\n')
100
-
101
- def update(self):
102
- return _updateElem(self)
103
-
104
-
105
-
106
-
107
-
108
-
109
- #6 Class to create elements
110
- class Element:
111
- """ Use Beam or Truss function"""
112
- elements = []
113
- ids = []
114
-
115
- @classmethod
116
- def json(cls):
117
- json = {"Assign":{}}
118
- for elem in cls.elements:
119
- js = _Obj2JS(elem)
120
- json["Assign"][elem.ID] = js
121
- return json
122
-
123
- @classmethod
124
- def create(cls):
125
- if cls.elements!=[]:
126
- MidasAPI("PUT","/db/ELEM",Element.json())
127
-
128
- @staticmethod
129
- def get():
130
- return MidasAPI("GET","/db/ELEM")
131
-
132
- @staticmethod
133
- def sync():
134
- a = Element.get()
135
- if a != {'message': ''}:
136
- if list(a['ELEM'].keys()) != []:
137
- Element.elements = []
138
- Element.ids=[]
139
- for elem_id in a['ELEM'].keys():
140
- _JS2Obj(elem_id,a['ELEM'][elem_id])
141
-
142
- @staticmethod
143
- def delete():
144
- MidasAPI("DELETE","/db/ELEM")
145
- Element.elements=[]
146
- Element.ids=[]
147
-
148
-
149
-
150
- class Beam(_common):
151
-
152
- def __init__(self,i:int,j:int,mat:int=1,sect:int=1,angle:float=0,id:int=0):
153
- self.ID = id
154
- self.TYPE = 'BEAM'
155
- self.MATL = mat
156
- self.SECT = sect
157
- self.NODE=[i,j]
158
- self.ANGLE = angle
159
-
160
- _ADD(self)
161
-
162
- @staticmethod
163
- def SDL(s_loc:list,dir:list,l:float,n:int=1,mat:int=1,sect:int=1,angle:float=0,id:int=0): #CHANGE TO TUPLE
164
- beam_nodes =[]
165
- beam_obj = []
166
- s_locc = np.array(s_loc)
167
- unit_vec = np.array(dir)/np.linalg.norm(dir)
168
-
169
- for i in range(n+1):
170
- locc = s_locc+i*l*unit_vec/n
171
- Enode=Node(locc[0].item(),locc[1].item(),locc[2].item())
172
- beam_nodes.append(Enode.ID)
173
-
174
- for i in range(n):
175
- if id == 0 : id_new = 0
176
- else: id_new = id+i
177
- beam_obj.append(Element.Beam(beam_nodes[i],beam_nodes[i+1],mat,sect,angle,id_new))
178
-
179
- return beam_obj
180
-
181
-
182
- @staticmethod
183
- def SE(s_loc:list,e_loc:list,n:int=1,mat:int=1,sect:int=1,angle:float=0,id:int=0):
184
- beam_nodes =[]
185
- beam_obj = []
186
- i_loc = np.linspace(s_loc,e_loc,n+1)
187
- for i in range(n+1):
188
- Enode=Node(i_loc[i][0].item(),i_loc[i][1].item(),i_loc[i][2].item())
189
- beam_nodes.append(Enode.ID)
190
-
191
- for i in range(n):
192
- if id == 0 : id_new = 0
193
- else: id_new = id+i
194
- beam_obj.append(Element.Beam(beam_nodes[i],beam_nodes[i+1],mat,sect,angle,id_new))
195
-
196
- return beam_obj
197
-
198
-
199
-
200
-
201
-
202
- class Truss(_common):
203
-
204
- def __init__(self,i:int,j:int,mat:int=1,sect:int=1,angle:float=0,id:int=0):
205
- self.ID = id
206
- self.TYPE = 'TRUSS'
207
- self.MATL = mat
208
- self.SECT = sect
209
- self.NODE=[i,j]
210
- self.ANGLE = angle
211
-
212
- _ADD(self)
213
-
214
-
215
-
216
- @staticmethod
217
- def SDL(s_loc:list,dir:list,l:float,n:int=1,mat:int=1,sect:int=1,angle:float=0,id:int=0):
218
- beam_nodes =[]
219
- beam_obj =[]
220
- s_locc = np.array(s_loc)
221
- unit_vec = np.array(dir)/np.linalg.norm(dir)
222
-
223
- for i in range(n+1):
224
- locc = s_locc+i*l*unit_vec/n
225
- Enode=Node(locc[0].item(),locc[1].item(),locc[2].item())
226
- beam_nodes.append(Enode.ID)
227
-
228
- for i in range(n):
229
- if id == 0 : id_new = 0
230
- else: id_new = id+i
231
- beam_obj.append(Element.Truss(beam_nodes[i],beam_nodes[i+1],mat,sect,angle,id_new))
232
-
233
- return beam_obj
234
-
235
-
236
- @staticmethod
237
- def SE(s_loc:list,e_loc:list,n:int=1,mat:int=1,sect:int=1,angle:float=0,id:int=0):
238
- beam_nodes =[]
239
- beam_obj = []
240
- i_loc = np.linspace(s_loc,e_loc,n+1)
241
- for i in range(n+1):
242
- Enode=Node(i_loc[i][0].item(),i_loc[i][1].item(),i_loc[i][2].item())
243
- beam_nodes.append(Enode.ID)
244
-
245
- for i in range(n):
246
- if id == 0 : id_new = 0
247
- else: id_new = id+i
248
- beam_obj.append(Element.Truss(beam_nodes[i],beam_nodes[i+1],mat,sect,angle,id_new))
249
-
250
- return beam_obj
251
-
252
-
253
-
254
- class Plate(_common):
255
-
256
- def __init__(self,nodes:list,stype:int=1,mat:int=1,sect:int=1,angle:float=0,id:int=0):
257
- self.ID = id
258
- self.TYPE = 'PLATE'
259
- self.MATL = mat
260
- self.SECT = sect
261
- self.NODE=nodes
262
- self.ANGLE = angle
263
- self.STYPE = stype
264
-
265
- _ADD(self)
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
File without changes
File without changes
File without changes