sgio 0.3.0__py3-none-any.whl → 0.3.2__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.
sgio/model/solid.py CHANGED
@@ -1,82 +1,216 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass, field
4
- from typing import Iterable
5
- from numbers import Number
6
-
7
- def initConstantName():
8
- return ['e1', 'e2', 'e3', 'g12', 'g13', 'g23', 'nu12', 'nu13', 'nu23']
9
-
10
- def initConstantLabel():
11
- return ['E1', 'E2', 'E3', 'G12', 'G13', 'G23', 'nu12', 'nu13', 'nu23']
12
-
13
- def initStrengthName():
14
- return ['x1t', 'x2t', 'x3t', 'x1c', 'x2c', 'x3c', 'x23', 'x13', 'x12']
15
-
16
- def initStrengthLabel():
17
- return ['X1t', 'X2t', 'X3t', 'X1c', 'X2c', 'X3c', 'X23', 'X13', 'X12']
18
-
19
- @dataclass
20
- class CauchyContinuumProperty:
21
- density:float = 0
22
-
23
- isotropy:int = 0
24
- """Isotropy type.
25
-
26
- * 0: Isotropic
27
- * 1: Orthotropic
28
- * 2: Anisotropic
3
+ from typing import Iterable, Optional, List, Literal, Sequence, Union, cast
4
+
5
+ FloatSequence = Sequence[float]
6
+ MatrixSequence = Sequence[Sequence[float]]
7
+ ElasticInput = Union[FloatSequence, MatrixSequence]
8
+ from pydantic import BaseModel, Field, field_validator, ConfigDict
9
+
10
+ class CauchyContinuumModel(BaseModel):
11
+ """Cauchy continuum model with Pydantic validation.
12
+
13
+ This implementation follows the beam model pattern and provides
14
+ built-in validation for all material properties.
15
+
16
+ Attributes
17
+ ----------
18
+ dim : int
19
+ Dimension of the model (always 3 for 3D continuum)
20
+ label : str
21
+ Model label identifier
22
+ model_name : str
23
+ Human-readable model name
24
+ strain_name : List[str]
25
+ Names of strain components
26
+ stress_name : List[str]
27
+ Names of stress components
28
+ name : str
29
+ Material name
30
+ id : Optional[int]
31
+ Material ID
32
+ isotropy : Literal[0, 1, 2]
33
+ Material isotropy type: 0=Isotropic, 1=Orthotropic, 2=Anisotropic
34
+ density : float
35
+ Material density (must be >= 0)
36
+ temperature : float
37
+ Temperature
38
+ e1, e2, e3 : Optional[float]
39
+ Young's moduli in material directions 1, 2, 3 (must be > 0 if set)
40
+ g12, g13, g23 : Optional[float]
41
+ Shear moduli in material planes 12, 13, 23 (must be > 0 if set)
42
+ nu12, nu13, nu23 : Optional[float]
43
+ Poisson's ratios in material planes 12, 13, 23 (must be in range (-1, 0.5))
44
+ stff : Optional[List[List[float]]]
45
+ 6x6 stiffness matrix
46
+ cmpl : Optional[List[List[float]]]
47
+ 6x6 compliance matrix
48
+ x1t, x2t, x3t : Optional[float]
49
+ Tensile strengths in directions 1, 2, 3
50
+ x1c, x2c, x3c : Optional[float]
51
+ Compressive strengths in directions 1, 2, 3
52
+ x23, x13, x12 : Optional[float]
53
+ Shear strengths in planes 23, 13, 12
54
+ strength_measure : int
55
+ Strength measure type: 0=stress, 1=strain
56
+ strength_constants : Optional[List[float]]
57
+ Array of strength constants
58
+ char_len : float
59
+ Characteristic length
60
+ cte : Optional[List[float]]
61
+ Thermal expansion coefficients (6 components)
62
+ specific_heat : float
63
+ Specific heat capacity
64
+ failure_criterion : int
65
+ Failure criterion identifier
66
+ d_thetatheta : float
67
+ Thermal property
68
+ f_eff : float
69
+ Effective property
29
70
  """
30
71
 
31
- # Stiffness properties
32
- stff:Iterable[Iterable[float]] = None
33
- cmpl:Iterable[Iterable[float]] = None
34
-
35
- constant_name:Iterable[str] = field(default_factory=initConstantName)
36
- constant_label:Iterable[str] = field(default_factory=initConstantLabel)
37
-
38
- e1:float = None
39
- e2:float = None
40
- e3:float = None
41
- g12:float = None
42
- g13:float = None
43
- g23:float = None
44
- nu12:float = None
45
- nu13:float = None
46
- nu23:float = None
72
+ # Class attributes (model metadata)
73
+ dim: int = 3
74
+ label: str = 'sd1'
75
+ model_name: str = 'Cauchy continuum model'
76
+ strain_name: List[str] = ['e11', 'e22', 'e33', 'e23', 'e13', 'e12']
77
+ stress_name: List[str] = ['s11', 's22', 's33', 's23', 's13', 's12']
78
+
79
+ # Basic properties
80
+ name: str = Field(default='', description="Material name")
81
+ id: Optional[int] = Field(default=None, description="Material ID")
82
+
83
+ # Material type
84
+ isotropy: Literal[0, 1, 2] = Field(
85
+ default=0,
86
+ description="Isotropy type: 0=Isotropic, 1=Orthotropic, 2=Anisotropic"
87
+ )
88
+
89
+ # Inertial properties
90
+ density: float = Field(default=0, ge=0, description="Material density")
91
+ temperature: float = Field(default=0, description="Temperature")
92
+
93
+ # Elastic constants (orthotropic)
94
+ e1: Optional[float] = Field(default=None, gt=0, description="Young's modulus E1")
95
+ e2: Optional[float] = Field(default=None, gt=0, description="Young's modulus E2")
96
+ e3: Optional[float] = Field(default=None, gt=0, description="Young's modulus E3")
97
+ g12: Optional[float] = Field(default=None, gt=0, description="Shear modulus G12")
98
+ g13: Optional[float] = Field(default=None, gt=0, description="Shear modulus G13")
99
+ g23: Optional[float] = Field(default=None, gt=0, description="Shear modulus G23")
100
+ nu12: Optional[float] = Field(default=None, description="Poisson's ratio nu12")
101
+ nu13: Optional[float] = Field(default=None, description="Poisson's ratio nu13")
102
+ nu23: Optional[float] = Field(default=None, description="Poisson's ratio nu23")
103
+
104
+ # Stiffness/compliance matrices
105
+ stff: Optional[List[List[float]]] = Field(
106
+ default=None,
107
+ description="6x6 stiffness matrix"
108
+ )
109
+ cmpl: Optional[List[List[float]]] = Field(
110
+ default=None,
111
+ description="6x6 compliance matrix"
112
+ )
47
113
 
48
114
  # Strength properties
49
- strength_name:Iterable[str] = field(default_factory=initStrengthName)
50
- strength_label:Iterable[str] = field(default_factory=initStrengthLabel)
115
+ x1t: Optional[float] = Field(default=None, ge=0, description="Tensile strength X1t")
116
+ x2t: Optional[float] = Field(default=None, ge=0, description="Tensile strength X2t")
117
+ x3t: Optional[float] = Field(default=None, ge=0, description="Tensile strength X3t")
118
+ x1c: Optional[float] = Field(default=None, ge=0, description="Compressive strength X1c")
119
+ x2c: Optional[float] = Field(default=None, ge=0, description="Compressive strength X2c")
120
+ x3c: Optional[float] = Field(default=None, ge=0, description="Compressive strength X3c")
121
+ x23: Optional[float] = Field(default=None, ge=0, description="Shear strength X23")
122
+ x13: Optional[float] = Field(default=None, ge=0, description="Shear strength X13")
123
+ x12: Optional[float] = Field(default=None, ge=0, description="Shear strength X12")
124
+
125
+ strength_measure: int = Field(
126
+ default=0,
127
+ description="Strength measure: 0=stress, 1=strain"
128
+ )
129
+ strength_constants: Optional[List[float]] = Field(
130
+ default=None,
131
+ description="Strength constants array"
132
+ )
133
+ char_len: float = Field(default=0, ge=0, description="Characteristic length")
134
+
135
+ # Thermal properties
136
+ cte: Optional[List[float]] = Field(
137
+ default=None,
138
+ description="Thermal expansion coefficients (6 components)"
139
+ )
140
+ specific_heat: float = Field(default=0, ge=0, description="Specific heat capacity")
141
+
142
+ # Failure properties
143
+ failure_criterion: int = Field(default=0, description="Failure criterion ID")
144
+ d_thetatheta: float = Field(default=0, description="Thermal property")
145
+ f_eff: float = Field(default=0, description="Effective property")
146
+
147
+ # Pydantic configuration
148
+ model_config = ConfigDict(
149
+ arbitrary_types_allowed=True,
150
+ validate_assignment=True,
151
+ )
152
+
153
+ def __init__(self, name: str = '', **data):
154
+ """Initialize model with optional positional name argument for backward compatibility.
51
155
 
52
- x1t:float = None # xt
53
- x2t:float = None # yt
54
- x3t:float = None # zt
55
- x1c:float = None # xc
56
- x2c:float = None # yc
57
- x3c:float = None # zc
58
- x23:float = None # r
59
- x13:float = None # t
60
- x12:float = None # s
61
-
62
- strength_measure = 0 # 0: stress, 1: strain
63
-
64
- strength_constants:Iterable[float] = None
156
+ Parameters
157
+ ----------
158
+ name : str, optional
159
+ Material name (can be positional for backward compatibility)
160
+ **data
161
+ Additional keyword arguments for other fields
162
+ """
163
+ if name:
164
+ data['name'] = name
165
+ super().__init__(**data)
166
+
167
+ # Field validators
168
+ @field_validator('stff', 'cmpl')
169
+ @classmethod
170
+ def validate_6x6_matrix(cls, v):
171
+ """Validate that stiffness/compliance matrices are 6x6."""
172
+ if v is not None:
173
+ if not isinstance(v, list) or len(v) != 6:
174
+ raise ValueError('Matrix must be 6x6 (6 rows)')
175
+ for i, row in enumerate(v):
176
+ if not isinstance(row, list) or len(row) != 6:
177
+ raise ValueError(f'Row {i} must have 6 columns')
178
+ return v
65
179
 
66
- char_len:float = 0
180
+ @field_validator('nu12', 'nu13', 'nu23')
181
+ @classmethod
182
+ def validate_poisson_ratio(cls, v):
183
+ """Validate that Poisson's ratios are in valid range."""
184
+ if v is not None and not (-1 < v < 0.5):
185
+ raise ValueError('Poisson ratio must be in range (-1, 0.5)')
186
+ return v
67
187
 
68
- cte:Iterable[float] = None
69
- specific_heat:float = 0
188
+ @field_validator('cte')
189
+ @classmethod
190
+ def validate_cte(cls, v):
191
+ """Validate that CTE has 6 components if provided."""
192
+ if v is not None:
193
+ if not isinstance(v, list) or len(v) != 6:
194
+ raise ValueError('CTE must have 6 components')
195
+ return v
70
196
 
71
197
  def __repr__(self) -> str:
198
+ """String representation of the model."""
199
+ constant_name = ['e1', 'e2', 'e3', 'g12', 'g13', 'g23', 'nu12', 'nu13', 'nu23']
200
+ constant_label = ['E1', 'E2', 'E3', 'G12', 'G13', 'G23', 'nu12', 'nu13', 'nu23']
201
+ strength_name = ['x1t', 'x2t', 'x3t', 'x1c', 'x2c', 'x3c', 'x23', 'x13', 'x12']
202
+ strength_label = ['X1t', 'X2t', 'X3t', 'X1c', 'X2c', 'X3c', 'X23', 'X13', 'X12']
203
+
72
204
  s = [
205
+ self.model_name,
206
+ '-' * len(self.model_name),
73
207
  f'density = {self.density}',
74
208
  f'isotropy = {self.isotropy}'
75
209
  ]
76
210
 
77
- s.append('-'*20)
211
+ s.append('-' * 20)
78
212
  s.append('stiffness matrix')
79
- if not self.stff is None:
213
+ if self.stff is not None:
80
214
  for i in range(6):
81
215
  _row = []
82
216
  for j in range(6):
@@ -86,7 +220,7 @@ class CauchyContinuumProperty:
86
220
  s.append('NONE')
87
221
 
88
222
  s.append('compliance matrix')
89
- if not self.cmpl is None:
223
+ if self.cmpl is not None:
90
224
  for i in range(6):
91
225
  _row = []
92
226
  for j in range(6):
@@ -95,449 +229,280 @@ class CauchyContinuumProperty:
95
229
  else:
96
230
  s.append('NONE')
97
231
 
98
- s.append('-'*20)
232
+ s.append('-' * 20)
99
233
  s.append('engineering constants')
100
- for _label, _name in zip(self.constant_label, self.constant_name):
101
- _value = eval(f'self.{_name}')
234
+ for _label, _name in zip(constant_label, constant_name):
235
+ _value = getattr(self, _name)
102
236
  s.append(f'{_label} = {_value}')
103
237
 
104
- s.append('-'*20)
238
+ s.append('-' * 20)
105
239
  s.append('strength')
106
- for _label, _name in zip(self.strength_label, self.strength_name):
107
- _value = eval(f'self.{_name}')
240
+ for _label, _name in zip(strength_label, strength_name):
241
+ _value = getattr(self, _name)
108
242
  s.append(f'{_label} = {_value}')
109
243
 
110
- s.append('-'*20)
244
+ s.append('-' * 20)
111
245
  s.append('cte')
112
- if not self.cte is None:
246
+ if self.cte is not None:
113
247
  s.append(' '.join(list(map(str, self.cte))))
114
248
  else:
115
249
  s.append('NONE')
116
250
 
117
- return '\n'.join(s)
118
-
119
-
120
-
121
-
122
- class CauchyContinuumModel:
123
- """Cauchy continuum model
124
- """
125
-
126
- dim = 3
127
- model_name = 'Cauchy continuum model'
128
- strain_name = ['e11', 'e22', 'e33', 'e23', 'e13', 'e12']
129
- stress_name = ['s11', 's22', 's33', 's23', 's13', 's12']
130
-
131
- def __init__(self, name:str=''):
132
-
133
- self.name = name
134
- self.id = None
135
-
136
- self.property = CauchyContinuumProperty()
137
-
138
- # Inertial
139
- # --------
140
-
141
- # self.density : float = None
142
- self.temperature : float = 0
143
-
144
- # self.strength_constants : Iterable = None
145
- self.failure_criterion = 0
146
-
147
- # Thermal
148
- # -------
149
-
150
- # self.cte : Iterable[float] = None
151
- # self.specific_heat : float = 0
152
-
153
- self.d_thetatheta : float = 0
154
- self.f_eff : float = 0
155
-
156
-
157
- def __repr__(self) -> str:
158
- s = [
159
- self.model_name,
160
- '-'*len(self.model_name),
161
- # f'density = {self.density}',
162
- # f'isotropy = {self.isotropy}'
163
- ]
164
-
165
- s.append(str(self.property))
166
-
167
251
  s.append(f'failure criterion = {self.failure_criterion}')
168
252
 
169
253
  return '\n'.join(s)
170
254
 
171
-
172
- def __eq__(self, m2):
173
- return self.property == m2.property
174
-
175
-
176
- def __call__(self, x):
177
- return
178
-
179
-
180
- def get(self, name:str):
181
- """Get material properties.
255
+ def __eq__(self, other):
256
+ """Check equality based on all material properties."""
257
+ if not isinstance(other, CauchyContinuumModel):
258
+ return False
259
+ return self.model_dump() == other.model_dump()
260
+
261
+ def __call__(self, x):
262
+ """Placeholder for model evaluation."""
263
+ return
264
+
265
+ # Property aliases -----------------------------------------------------
266
+
267
+ @property
268
+ def e(self) -> Optional[float]:
269
+ """Legacy alias for the isotropic Young's modulus."""
270
+
271
+ return self.e1
272
+
273
+ @e.setter
274
+ def e(self, value: float) -> None:
275
+ self.e1 = float(value)
276
+
277
+ @property
278
+ def nu(self) -> Optional[float]:
279
+ """Legacy alias for the isotropic Poisson ratio."""
280
+
281
+ return self.nu12
282
+
283
+ @nu.setter
284
+ def nu(self, value: float) -> None:
285
+ self.nu12 = float(value)
286
+
287
+ # Backward compatibility methods
288
+ def get(self, name: str):
289
+ """Get material properties (backward compatibility with old API).
182
290
 
183
291
  Parameters
184
- -----------
292
+ ----------
185
293
  name : str
186
- Name of the property that will be returned.
294
+ Name of the property to retrieve
187
295
 
188
296
  Returns
189
- ---------
190
- int or float or :obj:`Iterable`:
191
- Value of the specified beam property.
192
-
193
- Notes
194
297
  -------
298
+ int or float or List
299
+ Value of the specified property
195
300
 
196
- .. list-table:: Properties
197
- :header-rows: 1
198
-
199
- * - ``name``
200
- - Description
201
- * - density
202
- - Density of the material
203
- * - temperature
204
- -
205
- * - isotropy
206
- - Isotropy of the material
207
- * - e
208
- - Young's modulus of isotropic materials
209
- * - nu
210
- - Poisson's ratio of isotropic materials
211
- * - e1 | e2 | e3
212
- - Modulus of elasticity in material direction 1, 2, or 3
213
- * - nu12 | nu13 | nu23
214
- - Poisson's ratio in material plane 1-2, 1-3, or 2-3
215
- * - g12 | g13 | g23
216
- - Shear modulus in material plane 1-2, 1-3, or 2-3
217
- * - c
218
- - 6x6 stiffness matrix
219
- * - cij (i, j = 1 to 6)
220
- - Component (i, j) of the stiffness matrix
221
- * - s
222
- - 6x6 compliance matrix
223
- * - sij (i, j = 1 to 6)
224
- - Component (i, j) of the compliance matrix
225
- * - strength
226
- -
227
- * - alpha
228
- - CTE of isotropic materials
229
- * - alpha11 | alpha22 | alpha33 | alpha12 | alpha13 | alpha23
230
- - The 11, 22, 33, 12, 13, or 23 component of CTE
231
- * - specific_heat
232
- -
301
+ Notes
302
+ -----
303
+ Supported property names:
304
+ - density, temperature, isotropy
305
+ - e, nu (isotropic shortcuts for e1, nu12)
306
+ - e1, e2, e3, g12, g13, g23, nu12, nu13, nu23
307
+ - c, s (stiffness/compliance matrices)
308
+ - cij, sij (matrix components, i,j=1..6)
309
+ - x (shortcut for x1t)
310
+ - x1t, x2t, x3t, x1c, x2c, x3c, x23, x13, x12
311
+ - strength, char_len, failure_criterion
312
+ - cte, alpha, alphaij (thermal expansion)
313
+ - specific_heat
233
314
  """
234
-
235
315
  v = None
236
316
 
237
317
  if name == 'density':
238
- v = self.property.density
318
+ v = self.density
239
319
  elif name == 'temperature':
240
320
  v = self.temperature
241
321
  elif name == 'isotropy':
242
- v = self.property.isotropy
322
+ v = self.isotropy
243
323
 
244
324
  # Stiffness
245
325
  elif name == 'e':
246
- v = self.property.e1
326
+ v = self.e1
247
327
  elif name == 'nu':
248
- v = self.property.nu12
328
+ v = self.nu12
249
329
  elif name in ['e1', 'e2', 'e3', 'g12', 'g13', 'g23', 'nu12', 'nu13', 'nu23']:
250
- v = eval(f'self.property.{name}')
330
+ v = getattr(self, name)
251
331
 
252
332
  elif name == 'c':
253
- v = self.property.stff
333
+ v = self.stff
254
334
  elif name == 's':
255
- v = self.property.cmpl
335
+ v = self.cmpl
256
336
 
257
337
  # Strength
258
338
  elif name == 'x':
259
- v = self.property.x1t
339
+ v = self.x1t
260
340
  elif name in ['x1t', 'x2t', 'x3t', 'x1c', 'x2c', 'x3c', 'x23', 'x13', 'x12']:
261
- v = eval(f'self.property.{name}')
341
+ v = getattr(self, name)
262
342
  elif name == 'strength':
263
- v = self.property.strength_constants
343
+ v = self.strength_constants
264
344
  elif name == 'char_len':
265
- v = self.property.char_len
345
+ v = self.char_len
266
346
  elif name == 'failure_criterion':
267
347
  v = self.failure_criterion
268
348
 
269
349
  # Thermal
270
350
  elif name == 'cte':
271
- v = self.property.cte
351
+ v = self.cte
272
352
  elif name == 'specific_heat':
273
- v = self.property.specific_heat
353
+ v = self.specific_heat
274
354
 
275
355
  elif name.startswith('alpha'):
276
356
  if name == 'alpha':
277
- v = self.property.cte[0]
357
+ v = self.cte[0] if self.cte is not None else None
278
358
  else:
279
359
  _ij = name[-2:]
280
360
  for _k, __ij in enumerate(['11', '22', '33', '23', '13', '12']):
281
361
  if _ij == __ij:
282
- v = self.property.cte[_k]
362
+ v = self.cte[_k] if self.cte is not None else None
283
363
  break
284
- elif name.startswith('c'):
364
+ elif name.startswith('c') and len(name) == 3:
285
365
  _i = int(name[1]) - 1
286
366
  _j = int(name[2]) - 1
287
- v = self.property.stff[_i][_j]
288
- elif name.startswith('s'):
367
+ v = self.stff[_i][_j] if self.stff is not None else None
368
+ elif name.startswith('s') and len(name) == 3:
289
369
  _i = int(name[1]) - 1
290
370
  _j = int(name[2]) - 1
291
- v = self.property.cmpl[_i][_j]
371
+ v = self.cmpl[_i][_j] if self.cmpl is not None else None
292
372
 
293
373
  return v
294
374
 
295
-
296
- def set(self, name:str, value, **kwargs):
297
- """Set material properties.
375
+ def set(self, name: str, value, **kwargs):
376
+ """Set material properties (backward compatibility with old API).
298
377
 
299
378
  Parameters
300
- ------------
379
+ ----------
301
380
  name : str
302
- Name of the property that will be set.
303
-
304
- value : str or int or float
305
- Value of the property that will be set.
306
-
381
+ Name of the property to set
382
+ value : str or int or float or List
383
+ Value to set
384
+ **kwargs
385
+ Additional keyword arguments (e.g., input_type for elastic)
307
386
  """
308
- if name == 'isotropy':
309
- if isinstance(value, str):
310
- if value.startswith('iso'):
311
- self.property.isotropy = 0
312
- elif value.startswith('ortho') or value.startswith('eng') or value.startswith('lam'):
313
- self.property.isotropy = 1
314
- elif value.startswith('aniso'):
315
- self.property.isotropy = 2
316
- else:
317
- self.property.isotropy = int(value)
318
- elif isinstance(value, int):
319
- self.property.isotropy = value
320
-
321
- elif name == 'elastic':
322
- self.setElastic(value, **kwargs)
323
-
324
- elif name == 'strength_constants':
325
- if len(value) == 9:
326
- self.property.x1t = value[0]
327
- self.property.x2t = value[1]
328
- self.property.x3t = value[2]
329
- self.property.x1c = value[3]
330
- self.property.x2c = value[4]
331
- self.property.x3c = value[5]
332
- self.property.x23 = value[6]
333
- self.property.x13 = value[7]
334
- self.property.x12 = value[8]
335
-
336
- self.property.strength_constants = value
337
-
338
- else:
339
- exec(f'self.property.{name} = {value}')
340
-
341
- return
342
-
343
-
344
- def setElastic(self, consts:Iterable, input_type='', **kwargs):
345
- if self.property.isotropy == 0:
346
- self.property.e1 = float(consts[0])
347
- self.property.nu12 = float(consts[1])
348
-
349
- elif self.property.isotropy == 1:
350
- if input_type == 'lamina':
351
- self.property.e1 = float(consts[0])
352
- self.property.e2 = float(consts[1])
353
- self.property.g12 = float(consts[2])
354
- self.property.nu12 = float(consts[3])
355
- self.property.e3 = self.property.e2
356
- self.property.g13 = self.property.g12
357
- self.property.nu13 = self.property.nu12
358
- self.property.nu23 = 0.3
359
- self.property.g23 = self.property.e3 / (2.0 * (1 + self.property.nu23))
360
- elif input_type == 'engineering':
361
- self.property.e1, self.property.e2, self.property.e3 = list(map(float, consts[:3]))
362
- self.property.g12, self.property.g13, self.property.g23 = list(map(float, consts[3:6]))
363
- self.property.nu12, self.property.nu13, self.property.nu23 = list(map(float, consts[6:]))
364
- elif input_type == 'orthotropic': # TODO
365
- pass
366
- else:
367
- self.property.e1, self.property.e2, self.property.e3 = list(map(float, consts[:3]))
368
- self.property.g12, self.property.g13, self.property.g23 = list(map(float, consts[3:6]))
369
- self.property.nu12, self.property.nu13, self.property.nu23 = list(map(float, consts[6:]))
370
-
371
- elif self.property.isotropy == 2:
372
- if input_type == 'stiffness':
373
- self.property.stff = consts
374
- elif input_type == 'compliance':
375
- self.property.cmpl = consts
376
-
377
- return
378
-
379
-
380
-
381
-
382
-
383
-
384
-
385
-
386
-
387
-
388
- # Legacy
389
-
390
- # from .general import MaterialSection
391
-
392
- # class MaterialProperty(MaterialSection):
393
- # """
394
- # """
395
-
396
- # def __init__(self, name=''):
397
- # MaterialSection.__init__(self, 3)
398
- # self.name = name
399
-
400
- # self.temperature = 0
401
-
402
- # #: int: (continuum model) Isotropy type.
403
- # #: Isotropic (0), orthotropic (1), anisotropic (2).
404
- # self.isotropy = None
405
-
406
- # self.e1 = None
407
- # self.e2 = None
408
- # self.e3 = None
409
- # self.g12 = None
410
- # self.g13 = None
411
- # self.g23 = None
412
- # self.nu12 = None
413
- # self.nu13 = None
414
- # self.nu23 = None
415
-
416
- # self.strength = {}
417
-
418
- # self.cte = []
419
- # self.specific_heat = 0
420
-
421
- # self.d_thetatheta = 0
422
- # self.f_eff = 0
423
-
424
-
425
- # def __repr__(self):
426
- # s = [
427
- # f'density = {self.density}',
428
- # ]
429
-
430
- # if self.isotropy == 0:
431
- # s.append('isotropic')
432
- # s.append(f'E = {self.e1}, v = {self.nu12}')
433
- # elif self.isotropy == 1:
434
- # s.append('orthotropic')
435
- # s.append(f'E1 = {self.e1}, E2 = {self.e2}, E3 = {self.e3}')
436
- # s.append(f'G12 = {self.g12}, G13 = {self.g13}, G23 = {self.g23}')
437
- # s.append(f'v12 = {self.nu12}, v13 = {self.nu13}, v23 = {self.nu23}')
438
- # elif self.isotropy == 2:
439
- # s.append('anisotropic')
440
- # for i in range(6):
441
- # _row = []
442
- # for j in range(i, 6):
443
- # _row.append(f'C{i+1}{j+1} = {self.stff[i][j]}')
444
- # s.append(', '.join(_row))
445
-
446
- # return '\n'.join(s)
447
-
448
-
449
- # def summary(self):
450
- # stype = 'isotropic'
451
- # sprop = [['e = {0}'.format(self.e1),], ['nu = {0}'.format(self.nu12),]]
452
- # if self.isotropy == 1:
453
- # stype = 'orthotropic'
454
- # sprop = [
455
- # ['e1 = {0}'.format(self.e1), 'e2 = {0}'.format(self.e2), 'e3 = {0}'.format(self.e3)],
456
- # ['g12 = {0}'.format(self.g12), 'g13 = {0}'.format(self.g13), 'g23 = {0}'.format(self.g23)],
457
- # ['nu12 = {0}'.format(self.nu12), 'nu13 = {0}'.format(self.nu13), 'nu23 = {0}'.format(self.nu23)]
458
- # ]
459
- # elif self.isotropy == 2:
460
- # stype = 'anisotropic'
461
- # print('type:', stype)
462
- # print('density =', self.density)
463
- # print('elastic properties:')
464
- # for p in sprop:
465
- # print(', '.join(p))
466
- # return
467
-
468
-
469
- # def get(self, name):
470
- # r"""
471
- # """
472
- # v = None
473
-
474
- # if name == 'density':
475
- # v = self.density
476
-
477
- # elif name in ['e', 'e1', 'e2', 'e3', 'g12', 'g13', 'g23', 'nu', 'nu12', 'nu13', 'nu23']:
478
- # v = self.constants[name]
479
-
480
- # elif name in ['xt', 'yt', 'zt', 'xc', 'yc', 'zc', 'r', 't', 's']:
481
- # v = self.strength_constants[name]
482
- # # v = eval('self.{}'.format(name))
483
-
484
- # return v
485
-
486
-
487
- # def assignConstants(self, consts):
488
- # if len(consts) == 2:
489
- # self.isotropy = 0
490
- # self.e1 = float(consts[0])
491
- # self.nu12 = float(consts[1])
492
- # elif len(consts) == 9:
493
- # self.isotropy = 1
494
- # self.e1, self.e2, self.e3 = list(map(float, consts[:3]))
495
- # self.g12, self.g13, self.g23 = list(map(float, consts[3:6]))
496
- # self.nu12, self.nu13, self.nu23 = list(map(float, consts[6:]))
497
-
498
-
499
- # def setElasticProperty(self, consts, ctype):
500
- # if ctype == 'isotropic' or ctype == 0:
501
- # self.isotropy = 0
502
- # self.e1 = float(consts[0])
503
- # self.nu12 = float(consts[1])
504
- # elif ctype == 'lamina':
505
- # self.isotropy = 1
506
- # self.e1 = float(consts[0])
507
- # self.e2 = float(consts[1])
508
- # self.g12 = float(consts[2])
509
- # self.nu12 = float(consts[3])
510
- # self.e3 = self.e2
511
- # self.g13 = self.g12
512
- # self.nu13 = self.nu12
513
- # self.nu23 = 0.3
514
- # self.g23 = self.e3 / (2.0 * (1 + self.nu23))
515
- # elif ctype == 'engineering' or ctype == 1:
516
- # self.isotropy = 1
517
- # self.e1, self.e2, self.e3 = list(map(float, consts[:3]))
518
- # self.g12, self.g13, self.g23 = list(map(float, consts[3:6]))
519
- # self.nu12, self.nu13, self.nu23 = list(map(float, consts[6:]))
520
- # elif ctype == 'orthotropic':
521
- # self.isotropy = 1
522
- # elif ctype == 'anisotropic' or ctype == 2:
523
- # self.isotropy = 2
524
-
525
- # return
526
-
527
-
528
- # def setStrengthProperty(self, strength):
529
- # self.strength['constants'] = list(map(float, strength))
530
- # return
531
-
532
-
533
- # def setFailureCriterion(self, criterion):
534
- # if isinstance(criterion, str):
535
- # self.strength['criterion'] = self.FAILURE_CRITERION_NAME_TO_ID[criterion]
536
- # return
537
-
538
-
539
- # def setCharacteristicLength(self, char_len=0):
540
- # self.strength['chara_len'] = char_len
541
- # return
542
-
387
+ if name == 'isotropy':
388
+ iso_value: Optional[int] = None
389
+ if isinstance(value, str):
390
+ value_lower = value.lower()
391
+ if value_lower.startswith('iso'):
392
+ iso_value = 0
393
+ elif value_lower.startswith(('ortho', 'eng', 'lam')):
394
+ iso_value = 1
395
+ elif value_lower.startswith('aniso'):
396
+ iso_value = 2
397
+ else:
398
+ iso_value = int(value)
399
+ elif isinstance(value, int):
400
+ iso_value = value
401
+ else:
402
+ raise TypeError('Isotropy must be provided as string or integer')
403
+
404
+ if iso_value not in (0, 1, 2):
405
+ raise ValueError('Isotropy value must be 0, 1, or 2')
406
+
407
+ self.isotropy = cast(Literal[0, 1, 2], iso_value)
408
+
409
+ elif name == 'elastic':
410
+ self.setElastic(value, **kwargs)
411
+
412
+ elif name == 'strength_constants':
413
+ values = list(value)
414
+ if len(values) == 9:
415
+ self.x1t = values[0]
416
+ self.x2t = values[1]
417
+ self.x3t = values[2]
418
+ self.x1c = values[3]
419
+ self.x2c = values[4]
420
+ self.x3c = values[5]
421
+ self.x23 = values[6]
422
+ self.x13 = values[7]
423
+ self.x12 = values[8]
424
+
425
+ self.strength_constants = values
426
+
427
+ else:
428
+ # Direct attribute setting
429
+ setattr(self, name, value)
430
+
431
+ return
432
+
433
+ def setElastic(
434
+ self,
435
+ consts: ElasticInput,
436
+ input_type: str = '',
437
+ **kwargs,
438
+ ):
439
+ """Set elastic properties based on isotropy type.
440
+
441
+ Parameters
442
+ ----------
443
+ consts : Iterable
444
+ Elastic constants (format depends on isotropy and input_type)
445
+ input_type : str, optional
446
+ Type of input: 'lamina', 'engineering', 'orthotropic', 'stiffness', 'compliance'
447
+ **kwargs
448
+ Additional keyword arguments
449
+ """
450
+ if self.isotropy == 0:
451
+ # Isotropic: [E, nu]
452
+ seq = list(cast(FloatSequence, consts))
453
+ if len(seq) < 2:
454
+ raise ValueError('Isotropic elastic input requires [E, nu]')
455
+ self.e1 = float(seq[0])
456
+ self.nu12 = float(seq[1])
457
+
458
+ elif self.isotropy == 1:
459
+ # Orthotropic
460
+ seq = list(cast(FloatSequence, consts))
461
+ if input_type == 'lamina':
462
+ # [E1, E2, G12, nu12]
463
+ if len(seq) < 4:
464
+ raise ValueError('Lamina input requires 4 values [E1, E2, G12, nu12]')
465
+ self.e1 = float(seq[0])
466
+ self.e2 = float(seq[1])
467
+ self.g12 = float(seq[2])
468
+ self.nu12 = float(seq[3])
469
+ self.e3 = self.e2
470
+ self.g13 = self.g12
471
+ self.nu13 = self.nu12
472
+ self.nu23 = 0.3
473
+ self.g23 = self.e3 / (2.0 * (1 + self.nu23))
474
+ elif input_type == 'engineering':
475
+ # [E1, E2, E3, G12, G13, G23, nu12, nu13, nu23]
476
+ if len(seq) < 9:
477
+ raise ValueError('Engineering input requires 9 values')
478
+ self.e1, self.e2, self.e3 = list(map(float, seq[:3]))
479
+ self.g12, self.g13, self.g23 = list(map(float, seq[3:6]))
480
+ self.nu12, self.nu13, self.nu23 = list(map(float, seq[6:9]))
481
+ elif input_type == 'orthotropic':
482
+ # TODO: implement orthotropic input
483
+ pass
484
+ else:
485
+ # Default: same as engineering
486
+ if len(seq) < 9:
487
+ raise ValueError('Orthotropic input requires 9 values')
488
+ self.e1, self.e2, self.e3 = list(map(float, seq[:3]))
489
+ self.g12, self.g13, self.g23 = list(map(float, seq[3:6]))
490
+ self.nu12, self.nu13, self.nu23 = list(map(float, seq[6:9]))
491
+
492
+ elif self.isotropy == 2:
493
+ # Anisotropic: provide full matrices
494
+ matrix_input = cast(MatrixSequence, consts)
495
+ rows: List[List[float]] = []
496
+ for row in matrix_input:
497
+ rows.append([float(value) for value in row])
498
+ if input_type == 'stiffness':
499
+ self.stff = rows
500
+ elif input_type == 'compliance':
501
+ self.cmpl = rows
502
+
503
+ return
504
+
505
+
506
+ # Backward-compatible alias retained for downstream imports
507
+ CauchyContinuumModelNew = CauchyContinuumModel
543
508