- assembly draft

This commit is contained in:
bklronin
2026-07-05 19:36:27 +02:00
parent b595b88e04
commit 3a169007f7
4 changed files with 1357 additions and 134 deletions
+90 -2
View File
@@ -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