- assembly draft
This commit is contained in:
@@ -368,8 +368,16 @@ class Connector:
|
||||
# Which body/face this connector was placed on (renderer obj_id).
|
||||
source_obj_id: str = ""
|
||||
|
||||
# Future: connected to another Connector's id.
|
||||
# connected_to: Optional[str] = None
|
||||
# --- Rigid-group pairing (set when two connectors are mated) ---
|
||||
# The id of the partner AssemblyComponent this connector is mated to.
|
||||
# The FIRST-picked component is the grounded reference of the pair;
|
||||
# 'is_grounded' marks that side so the move handler knows which half
|
||||
# is the fixed frame of the rigid group.
|
||||
partner_ac_id: Optional[str] = None
|
||||
# The id of the partner Connector on the partner component.
|
||||
partner_connector_id: Optional[str] = None
|
||||
# True on the first-picked (grounded) connector of a mated pair.
|
||||
is_grounded: bool = False
|
||||
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
modified_at: datetime = field(default_factory=datetime.now)
|
||||
@@ -430,6 +438,25 @@ class AssemblyComponent:
|
||||
return False
|
||||
|
||||
|
||||
@dataclass
|
||||
class AssemblyConnection:
|
||||
"""A mated connector pair linking two AssemblyComponents.
|
||||
|
||||
Records which component is the grounded reference (``first_ac_id``) and
|
||||
which was solved against it (``second_ac_id``), plus the partner
|
||||
connector ids so the linkage can be followed / removed symmetrically.
|
||||
Used by the assembly-move handler to propagate translations across the
|
||||
rigid group.
|
||||
"""
|
||||
|
||||
id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
||||
first_ac_id: str = "" # grounded reference side
|
||||
second_ac_id: str = "" # solved side
|
||||
first_connector_id: Optional[str] = None
|
||||
second_connector_id: Optional[str] = None
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Assembly:
|
||||
"""
|
||||
@@ -447,9 +474,66 @@ class Assembly:
|
||||
components: Dict[str, AssemblyComponent] = field(default_factory=dict)
|
||||
active_assembly_component: Optional[str] = None
|
||||
|
||||
# Mated connector pairs — each entry links two AssemblyComponents so the
|
||||
# assembly-move handler can propagate rigid-group translations. The
|
||||
# 'first_ac_id' side is the grounded reference of the pair.
|
||||
connections: List["AssemblyConnection"] = field(default_factory=list)
|
||||
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
modified_at: datetime = field(default_factory=datetime.now)
|
||||
|
||||
def add_connection(self, first_ac_id: str, second_ac_id: str) -> "AssemblyConnection":
|
||||
"""Record a mated connector pair between two component instances.
|
||||
|
||||
The first-picked component (``first_ac_id``) is treated as the
|
||||
grounded reference of the pair. Returns the AssemblyConnection for
|
||||
further bookkeeping (e.g. attaching partner connector ids).
|
||||
"""
|
||||
conn = AssemblyConnection(
|
||||
first_ac_id=first_ac_id,
|
||||
second_ac_id=second_ac_id,
|
||||
)
|
||||
self.connections.append(conn)
|
||||
self.modified_at = datetime.now()
|
||||
return conn
|
||||
|
||||
def remove_connections_for(self, ac_id: str) -> None:
|
||||
"""Drop every connection that involves *ac_id* (e.g. on removal)."""
|
||||
self.connections = [
|
||||
c for c in self.connections
|
||||
if c.first_ac_id != ac_id and c.second_ac_id != ac_id
|
||||
]
|
||||
|
||||
def get_rigid_group(self, ac_id: str) -> List[str]:
|
||||
"""Return ids of all components rigidly linked to *ac_id* (BFS).
|
||||
|
||||
Includes *ac_id* itself. Two components are linked when a mated
|
||||
connector pair (in ``connections``) joins them; linkage is
|
||||
transitive, so the whole connected subgraph forms one rigid group.
|
||||
"""
|
||||
if ac_id not in self.components:
|
||||
return []
|
||||
# Build adjacency from the connection list.
|
||||
adj: Dict[str, List[str]] = {}
|
||||
for c in self.connections:
|
||||
adj.setdefault(c.first_ac_id, []).append(c.second_ac_id)
|
||||
adj.setdefault(c.second_ac_id, []).append(c.first_ac_id)
|
||||
seen: List[str] = []
|
||||
queue: List[str] = [ac_id]
|
||||
while queue:
|
||||
cur = queue.pop(0)
|
||||
if cur in seen:
|
||||
continue
|
||||
seen.append(cur)
|
||||
for nb in adj.get(cur, []):
|
||||
if nb not in seen:
|
||||
queue.append(nb)
|
||||
return seen
|
||||
|
||||
def is_grounded_reference(self, ac_id: str) -> bool:
|
||||
"""True if *ac_id* is the grounded (first-picked) side of any pair."""
|
||||
return any(c.first_ac_id == ac_id for c in self.connections)
|
||||
|
||||
def add_component_instance(
|
||||
self, component_id: str, name: Optional[str] = None
|
||||
) -> AssemblyComponent:
|
||||
@@ -472,6 +556,10 @@ class Assembly:
|
||||
"""Remove a component instance from the assembly."""
|
||||
if assembly_component_id in self.components:
|
||||
del self.components[assembly_component_id]
|
||||
# Also drop any mated-connector links that referenced this
|
||||
# instance — otherwise stale connection edges would remain in
|
||||
# the rigid-group graph and point at a missing component.
|
||||
self.remove_connections_for(assembly_component_id)
|
||||
if self.active_assembly_component == assembly_component_id:
|
||||
self.active_assembly_component = next(
|
||||
iter(self.components.keys()), None
|
||||
|
||||
Reference in New Issue
Block a user