mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
250 lines
9.2 KiB
Python
250 lines
9.2 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import weakref
|
|
from datetime import datetime
|
|
from typing import Literal, Optional
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, model_validator
|
|
|
|
from type_hints import *
|
|
|
|
|
|
def _lower_first(s: str) -> str:
|
|
return s[:1].lower() + s[1:] if s else s
|
|
|
|
|
|
class CaseInsensitiveModel(BaseModel):
|
|
model_config = ConfigDict(populate_by_name=True, alias_generator=_lower_first)
|
|
|
|
|
|
class BsddDictionary(CaseInsensitiveModel):
|
|
OrganizationCode: str
|
|
DictionaryCode: str
|
|
DictionaryVersion: str
|
|
LanguageIsoCode: LANGUAGE_ISO_CODE
|
|
LanguageOnly: bool
|
|
UseOwnUri: bool
|
|
DictionaryName: Optional[str] = None
|
|
DictionaryUri: Optional[str] = None
|
|
License: Optional[str] = "MIT"
|
|
LicenseUrl: Optional[str] = None
|
|
ChangeRequestEmailAddress: Optional[str] = None
|
|
ModelVersion: Optional[str] = "2.0"
|
|
MoreInfoUrl: Optional[str] = None
|
|
QualityAssuranceProcedure: Optional[str] = None
|
|
QualityAssuranceProcedureUrl: Optional[str] = None
|
|
ReleaseDate: Optional[datetime] = None
|
|
Status: Optional[STATUS] = None
|
|
Classes: list[BsddClass] = Field(default_factory=list)
|
|
Properties: list[BsddProperty] = Field(default_factory=list)
|
|
|
|
@classmethod
|
|
def load(cls, path) -> BsddDictionary:
|
|
"""Load from a JSON file and validate via the normalizer above."""
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
raw = json.load(f)
|
|
# The model_validator(before) handles list/dict/nested shapes
|
|
return cls.model_validate(raw)
|
|
|
|
def save(self, path):
|
|
with open(path, "w") as file:
|
|
json.dump(self.model_dump(mode="json", exclude_none=True), file)
|
|
|
|
# add Parent to children after loading
|
|
def model_post_init(self, context):
|
|
for c in self.Classes:
|
|
c._set_parent(self)
|
|
for p in self.Properties:
|
|
p._set_parent(self)
|
|
|
|
|
|
class BsddClass(CaseInsensitiveModel):
|
|
Code: str
|
|
Name: str
|
|
ClassType: CLASS_TYPE = "Class"
|
|
Definition: Optional[str] = None
|
|
Description: Optional[str] = None
|
|
ParentClassCode: Optional[str | None] = None
|
|
RelatedIfcEntityNamesList: Optional[list[str]] = None
|
|
Synonyms: Optional[list[str]] = None
|
|
ActivationDateUtc: Optional[datetime] = None
|
|
ReferenceCode: Optional[str] = None
|
|
CountriesOfUse: Optional[list[COUNTRY_CODE]] = None
|
|
CountryOfOrigin: Optional[COUNTRY_CODE] = None
|
|
CreatorLanguageIsoCode: Optional[LANGUAGE_ISO_CODE] = None
|
|
DeActivationDateUtc: Optional[datetime] = None
|
|
DeprecationExplanation: Optional[str] = None
|
|
DocumentReference: Optional[str] = None
|
|
OwnedUri: Optional[str] = None
|
|
ReplacedObjectCodes: Optional[list[str]] = None
|
|
ReplacingObjectCodes: Optional[list[str]] = None
|
|
RevisionDateUtc: Optional[datetime] = None
|
|
RevisionNumber: Optional[int] = None
|
|
Status: Optional[CLASS_STATUS] = None
|
|
SubdivisionsOfUse: Optional[list[str]] = None
|
|
Uid: Optional[str] = None
|
|
VersionDateUtc: Optional[datetime] = None
|
|
VersionNumber: Optional[int] = None
|
|
VisualRepresentationUri: Optional[str] = None
|
|
ClassProperties: list[BsddClassProperty] = Field(default_factory=list)
|
|
ClassRelations: list[BsddClassRelation] = Field(default_factory=list)
|
|
|
|
_parent_ref: Optional[weakref.ReferenceType[BsddDictionary]] = PrivateAttr(default=None)
|
|
|
|
def _set_parent(self, parent: BsddDictionary) -> None:
|
|
self._parent_ref = weakref.ref(parent)
|
|
|
|
def parent(self) -> Optional[BsddDictionary]:
|
|
return self._parent_ref() if self._parent_ref is not None else None
|
|
|
|
def model_post_init(self, context):
|
|
for c in self.ClassProperties:
|
|
c._set_parent(self)
|
|
for cr in self.ClassRelations:
|
|
cr._set_parent(self)
|
|
|
|
|
|
class BsddClassRelation(CaseInsensitiveModel):
|
|
RelationType: CLASS_RELATION_TYPE
|
|
RelatedClassUri: str
|
|
RelatedClassName: Optional[str] = None
|
|
Fraction: Optional[float] = None
|
|
OwnedUri: Optional[str] = None
|
|
|
|
def _set_parent(self, parent: BsddClass) -> None:
|
|
self._parent_ref = weakref.ref(parent)
|
|
|
|
def parent(self) -> Optional[BsddClass]:
|
|
return self._parent_ref() if self._parent_ref is not None else None
|
|
|
|
|
|
class BsddAllowedValue(CaseInsensitiveModel):
|
|
Code: str
|
|
Value: str
|
|
Description: Optional[str] = None
|
|
Uri: Optional[str] = None
|
|
SortNumber: Optional[int] = None
|
|
OwnedUri: Optional[str] = None
|
|
|
|
|
|
class BsddClassProperty(CaseInsensitiveModel):
|
|
Code: str
|
|
PropertyCode: Optional[str] = None
|
|
PropertyUri: Optional[str] = None
|
|
Description: Optional[str] = None
|
|
PropertySet: Optional[str] = None
|
|
Unit: Optional[str] = None
|
|
PredefinedValue: Optional[str] = None
|
|
IsRequired: Optional[bool] = None
|
|
IsWritable: Optional[bool] = None
|
|
MaxExclusive: Optional[float] = None
|
|
MaxInclusive: Optional[float] = None
|
|
MinExclusive: Optional[float] = None
|
|
MinInclusive: Optional[float] = None
|
|
Pattern: Optional[str] = None
|
|
OwnedUri: Optional[str] = None
|
|
PropertyType: Optional[Literal["Property", "Dependency"]] = None
|
|
SortNumber: Optional[int] = None
|
|
Symbol: Optional[str] = None
|
|
AllowedValues: list[BsddAllowedValue] = Field(default_factory=list)
|
|
_parent_ref: Optional[weakref.ReferenceType[BsddClass]] = PrivateAttr(default=None)
|
|
|
|
def _set_parent(self, parent: BsddClass) -> None:
|
|
self._parent_ref = weakref.ref(parent)
|
|
|
|
def parent(self) -> Optional[BsddClass]:
|
|
return self._parent_ref() if self._parent_ref is not None else None
|
|
|
|
@model_validator(mode="after")
|
|
def _validate_property_code_or_uri(self):
|
|
"""
|
|
only one of PropertyCode or PropertyUri must be set (XOR)
|
|
"""
|
|
# normalize whitespace
|
|
code = self.PropertyCode.strip() if self.PropertyCode and isinstance(self.PropertyCode, str) else None
|
|
uri = self.PropertyUri.strip() if self.PropertyUri and isinstance(self.PropertyUri, str) else None
|
|
|
|
# XOR: exactly one must be provided
|
|
if bool(code) == bool(uri):
|
|
raise ValueError("Exactly one of PropertyCode or PropertyUri must be provided (not both, not neither)")
|
|
|
|
# assign normalized values back
|
|
object.__setattr__(self, "PropertyCode", code)
|
|
object.__setattr__(self, "PropertyUri", uri)
|
|
return self
|
|
|
|
|
|
class BsddProperty(CaseInsensitiveModel):
|
|
Code: str
|
|
Name: str
|
|
Definition: Optional[str] = None
|
|
Description: Optional[str] = None
|
|
DataType: Optional[DATATYPE_TYPE] = None
|
|
Units: Optional[list[UNITS_TYPE]] = None
|
|
Example: Optional[str] = None
|
|
ActivationDateUtc: Optional[datetime] = None
|
|
ConnectedPropertyCodes: Optional[list[str]] = None
|
|
CountriesOfUse: Optional[list[COUNTRY_CODE]] = None
|
|
CountryOfOrigin: Optional[COUNTRY_CODE] = None
|
|
CreatorLanguageIsoCode: Optional[LANGUAGE_ISO_CODE] = None
|
|
DeActivationDateUtc: Optional[datetime] = None
|
|
DeprecationExplanation: Optional[str] = None
|
|
Dimension: Optional[str] = None
|
|
DimensionLength: Optional[int] = None
|
|
DimensionMass: Optional[int] = None
|
|
DimensionTime: Optional[int] = None
|
|
DimensionElectricCurrent: Optional[int] = None
|
|
DimensionThermodynamicTemperature: Optional[int] = None
|
|
DimensionAmountOfSubstance: Optional[int] = None
|
|
DimensionLuminousIntensity: Optional[int] = None
|
|
DocumentReference: Optional[DOCUMENT_TYPE] = None
|
|
DynamicParameterPropertyCodes: Optional[list[str]] = None
|
|
IsDynamic: Optional[bool] = None
|
|
MaxExclusive: Optional[float] = None
|
|
MaxInclusive: Optional[float] = None
|
|
MinExclusive: Optional[float] = None
|
|
MinInclusive: Optional[float] = None
|
|
MethodOfMeasurement: Optional[str] = None
|
|
OwnedUri: Optional[str] = None
|
|
Pattern: Optional[str] = None
|
|
PhysicalQuantity: Optional[str] = None
|
|
PropertyValueKind: Optional[PROPERTY_VALUE_KIND_TYPE] = None
|
|
ReplacedObjectCodes: Optional[list[str]] = None
|
|
ReplacingObjectCodes: Optional[list[str]] = None
|
|
RevisionDateUtc: Optional[datetime] = None
|
|
RevisionNumber: Optional[int] = None
|
|
Status: Optional[PROPERTY_STATUS] = None
|
|
SubdivisionsOfUse: Optional[list[str]] = None
|
|
TextFormat: Optional[str] = None
|
|
Uid: Optional[str] = None
|
|
VersionDateUtc: Optional[datetime] = None
|
|
VersionNumber: Optional[int] = None
|
|
VisualRepresentationUri: Optional[str] = None
|
|
PropertyRelations: list[BsddPropertyRelation] = Field(default_factory=list)
|
|
AllowedValues: list[BsddAllowedValue] = Field(default_factory=list)
|
|
_parent_ref: Optional[weakref.ReferenceType[BsddDictionary]] = PrivateAttr(default=None)
|
|
|
|
def _set_parent(self, parent: BsddDictionary) -> None:
|
|
self._parent_ref = weakref.ref(parent)
|
|
|
|
def parent(self) -> Optional[BsddDictionary]:
|
|
return self._parent_ref() if self._parent_ref is not None else None
|
|
|
|
def model_post_init(self, context):
|
|
for pr in self.PropertyRelations:
|
|
pr._set_parent(self)
|
|
|
|
|
|
class BsddPropertyRelation(CaseInsensitiveModel):
|
|
RelatedPropertyName: Optional[str] = None
|
|
RelatedPropertyUri: str
|
|
RelationType: PROPERTY_RELATION_TYPE
|
|
OwnedUri: Optional[str] = None
|
|
|
|
def _set_parent(self, parent: BsddProperty) -> None:
|
|
self._parent_ref = weakref.ref(parent)
|
|
|
|
def parent(self) -> Optional[BsddProperty]:
|
|
return self._parent_ref() if self._parent_ref is not None else None
|