See this article for an introduction to Named Groups.
When developing code to deal with Named Groups,
I found that NamedGroup class instances do not copy successfully.
What you think should be a copy of a NamedGroup is something that crashes MicroStation.
I developed class NamedGroupProxy to act as a proxy for a NamedGroup:
it is a Python class that follows Python conventions and enables the examples to work as expected.
My NamedGroupProxy class provides a way to preserve Named Group properties as you use them in your code.
It steps past a number of problems raised by the implementation of the MicroStation Python NamedGroup class.
It helps when you want to serialize Named Group data, perhaps to a JSON file, including handling the opaque
NamedGroupFlags class.
The NamedGroup has problems.
It is supposed to be a wrapper around the C++ NamedGroup, but it misbehaves.
By 'misbehave' I mean that, when you copy a NamedGroup, it may cause a MicroStation crash.
In my examples, I convert a NamedGroupCollection to a Python list of NamedGroupProxy (list:[NamedGroupProxy])
as soon as possible.
Subsequently, we work with those NamedGroupProxys.
In my examples I write NamedGroup data to a JSON file and read from a JSON file.
JSON understands only
simple data types.
One member of a NamedGroup is NamedGroupFlags, which is not a type that JSON understands.
We must translate to something that JSON does understand, such as an int.
Two functions are used to translate between NamedGroupFlags and an int …
NamedGroupFlags_to_int
int_to_NamedGroupFlags
Those functions were kindly provided by YongAn Fu, a Bentley Systems staff member who supports MicroStation developers. You'll find his posts on the MicroStation Programming Forum.
They are implemented as members of the
NamedGroupProxy class.
I wanted the NamedGroupProxy class to be sortable and to able to participate in a Python
set.
I added the following comparison and hash methods, sometimes called
magic methods
or
dunder methods,
to enable that requirement …
__hash__ to participate in a set
__eq__ for sorting
__lt__ for sorting
The entire class is
decorated
with
@functools.total_ordering.
That decorator supplies the remaining comparator classes automatically.
Most NamedGroup methods return a
NamedGroupStatus code.
Method discuss_status takes that code and explains it using human-comprehensible words.
You can obtain this source code from the download page.
from MSPyDgnPlatform import *
from MSPyMstnPlatform import *
from collections import namedtuple
import functools
@functools.total_ordering
class NamedGroupProxy():
'''
Python class acts as a proxy for a NamedGroup instance.
Remarks: A NamedGroup instance doesn't copy and crashes MicroStation. The proxy contains the essential information and is a normal Python class.
Created: 03-Feb-2026
Updated: 19-Feb-2026
The primary purpose of this class is to interpret NamedGroup string (WString) values as Python strings,
and preserve those values when the proxy is copied.
That avoids the undesired behaviour of a NamedGroup, which misbehaves when copied.
The cause of that behaviour is to do with translation of the underlying C++ code.
When serializing NamedGroupFlags there is an explicit conversion function for NamedGroupFlags.
NamedGroupFlags.__str()__ doesn't behave as it should.
We store the flags as a Python int and convert to/from NamedGroupFlags as required.
Decorator @total_ordering automatically creates all comparison operators that are not defined here (==, <).
'''
def __init__(self, name: str, descr: str, type: str, flags: int, dgn_model: DgnModel = ISessionMgr.ActiveDgnModelRef):
self._dgn_model = dgn_model
self._name = name
self._flags = flags
self._description = descr
self._type = type
self._present = False # Used when reading from JSON, informs us when this group is already defined in a DGN file
# Class methods
@classmethod
def from_named_group(cls, ng: NamedGroup, dgn_model: DgnModel = ISessionMgr.ActiveDgnModelRef):
assert ng is not None, "NamedGroupProxy.from_named_group - ng is None"
return cls(str(ng.GetName()), str(ng.GetDescription()), str(ng.GetType()), NamedGroupProxy.NamedGroupFlags_to_int (ng.GetFlags()), dgn_model)
@classmethod
def from_name_and_model(cls, name: str, dgn_model: DgnModel = ISessionMgr.ActiveDgnModelRef):
'''
Attempt to find the NamedGroup with given name in the specified DGN model.
Returns: freshly-minted NamedGroupProxy.
'''
groups = NamedGroupCollection(dgn_model)
assert groups is not None, "NamedGroupProxy.from_name_and_model - groups is None"
ng = groups.FindByName(name)
assert ng is not None, f"NamedGroupProxy.from_name_and_model - NamedGroup '{name}' not found"
return NamedGroupProxy.from_named_group (ng, dgn_model)
def discuss_status(self, status: NamedGroupStatus)->NamedGroupStatus:
'''
Many NamedGroup functions return a eNG_Success value. This method renders that status into human-readable text.
Returns: The same NamedGroupStatus received from the caller.
'''
if eNG_Success == status:
#print("Named Group operation succeeded")
pass
elif eNG_ClosedGroup == status:
print(f"Can't add new Named Group '{self._name}' to existing closed group")
elif eNG_FarReferenceDisallowed == status:
print("Named Group far reference not allowed")
elif eNG_DuplicateMember == status:
print(f"Operation would create a duplicate group member '{self._name}'")
elif eNG_BadMember == status:
print(f"Bad Named Group member '{self._name}'")
elif eNG_NameTooLong == status:
print(f"Named Group name '{self._name}' is too long ")
elif eNG_NameTooShort == status:
print(f"Named Group name '{self._name}' is too short ")
elif eNG_DescriptionTooLong == status:
print(f"Named Group description '{self._name}' is too long ")
elif eNG_TypeTooLong == status:
print(f"Named Group type '{self._name}' is too long ")
elif eNG_CircularDependency == status:
print(f"Named Group '{name}' has circular dependency")
elif eNG_CantCreateSubgroup == status:
print(f"Can't create sub-group of Named Group '{self._name}' has circular dependency")
elif eNG_BadArg == status:
print("Named Group bad argument")
elif eNG_NotNamedGroupElement == status:
print(f"'{self._name}' is not a Named Group element")
elif eNG_FileReadOnly == status:
print("File is read-only")
elif eNG_ExistsNotOverwriting == status:
print(f"'{self._name}' exists: not overwriting")
elif eNG_NameNotUnique == status:
print(f"'{self._name}' is not unique")
elif eNG_NotFound == status:
print(f"'{self._name}' not found")
elif eNG_NotPersistent == status:
print(f"'{name}' not persistent")
elif eNG_OperationInProgress == status:
print("Named Group operation in progress")
elif eNG_IsFarReference == status:
print("is far reference")
return status
def create_named_group(self, dgn_model: DgnModel = ISessionMgr.ActiveDgnModelRef)->(int, NamedGroup):
'''
Create a Named Group.
Returns: (NamedGroupStatus, NamedGroup)
'''
(status, new_ng) = NamedGroup.Create(self._name, self._description, NamedGroupProxy.int_to_NamedGroupFlags(self._flags), dgn_model)
self.discuss_status(status, self._name)
return (status, new_ng)
def add_to_file(self, dgn_model: DgnModel = ISessionMgr.ActiveDgnModelRef)->NamedGroupStatus:
'''
Create a Named Group and persist it in the specified DGN file.
Returns: NamedGroupStatus
'''
status, ng = self.create_named_group(dgn_model)
self.discuss_status(status, self._name)
if eNG_Success == status:
_OVERWRITE_EXISTING = True
_DONT_OVERWRITE_EXISTING = not _OVERWRITE_EXISTING
status = ng.WriteToFile(_DONT_OVERWRITE_EXISTING)
self.discuss_status(status, self._name)
return status
# Equality tests enable you to sort a list of NamedGroupProxy or compare two NamedGroupProxy instances.
def __eq__(self, other):
'''
Equality test implements case-insensitive text comparison of _name.
'''
return self._name.casefold() == other._name.casefold()
def __lt__(self, other):
'''
Less-than test implements case-insensitive text comparison of _name.
'''
return self._name.casefold() < other._name.casefold()
# Make instances hashable, so that they can participate in some Python operations e.g. set
def __hash__(self):
'''
Each Named Group muat have a unique name, meaning it is suitable for hashing.
'''
return hash(self._name)
# Properties
@property
def present(self)->bool:
'''
Returns the state of the 'present' flag.
'''
return self._present
def set_present(self, state: bool = True):
'''
Set the state of the 'present' flag.
'''
self._present = state
@property
def name(self)->str:
'''
Get the name of a NamedGroup.
Remarks: The text attributes of a NamedGroup are returns as Bentley.WString, which we convert to a Pything string.
'''
return self._name
@property
def description(self)->str:
'''
Get the description of a NamedGroup.
Remarks: The text attributes of a NamedGroup are returns as Bentley.WString, which we convert to a Pything string.
'''
return str(self._description)
@property
def flags(self)->int:
'''
Get the flags of a NamedGroup.
Remarks: The flag attributes of a NamedGroup.
'''
return self._flags
@property
def named_group_type(self)->str:
'''
Get the user-assigned type of a NamedGroup.
Remarks: The text attributes of a NamedGroup are returns as Bentley.WString, which we convert to a Pything string.
'''
return self._type
# NamedGroupFlag translation
@classmethod
def NamedGroupFlags_to_int(cls, flags: NamedGroupFlags) -> int:
'''
Class method interprets NamedGroupFlags as an integer.
'''
value = 0
if flags.AllowDuplicates:
value |= 1 << 0
if flags.ExclusiveMembers:
value |= 1 << 1
if flags.AllowFarReferences:
value |= 1 << 2
if flags.Closed:
value |= 1 << 3
if flags.SelectMembers:
value |= 1 << 4
if flags.Anonymous:
value |= 1 << 5
return value
@classmethod
def int_to_NamedGroupFlags(cls, value: int) -> NamedGroupFlags:
'''
Class method interprets an integer as NamedGroupFlags.
'''
flags = NamedGroupFlags()
flags.AllowDuplicates = bool(value & (1 << 0))
flags.ExclusiveMembers = bool(value & (1 << 1))
flags.AllowFarReferences = bool(value & (1 << 2))
flags.Closed = bool(value & (1 << 3))
flags.SelectMembers = bool(value & (1 << 4))
flags.Anonymous = bool(value & (1 << 5))
return flags
def __str__(self)->str:
summary = str(f"Name: '{self.name}' Descr: '{self.description}' Type: '{self.named_group_type}' Flags: {self._flags} Present: {self._present} " )
return summary
For links to Python examples that show how NamedGroupProxy is used, see LA Solutions'
NamedGroup Examples.
Post questions about MicroStation programming to the MicroStation Programming Forum.