- assembly draft
This commit is contained in:
+941
-3
File diff suppressed because it is too large
Load Diff
@@ -336,6 +336,160 @@ class Component:
|
||||
self.modified_at = datetime.now()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Connector:
|
||||
"""
|
||||
A connection point on an assembly component instance.
|
||||
|
||||
Stores the position and orientation of a connection point
|
||||
(e.g. a hole center, face midpoint, or edge point) that will
|
||||
later be used by the SolveSpace solver to mate components.
|
||||
The *normal* defines the connection axis direction (e.g. the
|
||||
hole axis for a screw connection).
|
||||
"""
|
||||
|
||||
id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
||||
name: str = "Untitled Connector"
|
||||
|
||||
# 3D position of the connection point (world coords).
|
||||
position: Tuple[float, float, float] = (0.0, 0.0, 0.0)
|
||||
# Normal direction of the connection (e.g. hole axis).
|
||||
normal: Tuple[float, float, float] = (0.0, 0.0, 1.0)
|
||||
# In-plane X direction for defining the reference frame.
|
||||
x_dir: Tuple[float, float, float] = (1.0, 0.0, 0.0)
|
||||
|
||||
# Rotation around the normal axis (degrees).
|
||||
axis_rotation: float = 0.0
|
||||
# Offset distance along the normal.
|
||||
offset: float = 0.0
|
||||
|
||||
# Which AssemblyComponent this connector belongs to.
|
||||
assembly_component_id: str = ""
|
||||
# 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
|
||||
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
modified_at: datetime = field(default_factory=datetime.now)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AssemblyComponent:
|
||||
"""
|
||||
An instance of a component within an assembly.
|
||||
|
||||
References a component in the project and stores its relative
|
||||
position and rotation for placement within the assembly.
|
||||
Holds connectors that define connection points for the
|
||||
SolveSpace solver.
|
||||
"""
|
||||
|
||||
id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
||||
component_id: str = ""
|
||||
name: str = "Untitled Instance"
|
||||
|
||||
# Position and orientation relative to the assembly origin.
|
||||
position: np.ndarray = field(default_factory=lambda: np.array([0.0, 0.0, 0.0]))
|
||||
rotation: np.ndarray = field(default_factory=lambda: np.eye(3))
|
||||
|
||||
# Connectors defined on this component instance.
|
||||
connectors: Dict[str, Connector] = field(default_factory=dict)
|
||||
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
modified_at: datetime = field(default_factory=datetime.now)
|
||||
|
||||
def add_connector(
|
||||
self,
|
||||
position: Tuple[float, float, float],
|
||||
normal: Tuple[float, float, float],
|
||||
x_dir: Tuple[float, float, float],
|
||||
source_obj_id: str = "",
|
||||
name: Optional[str] = None,
|
||||
) -> Connector:
|
||||
"""Add a connector to this component instance."""
|
||||
conn = Connector(
|
||||
name=name or f"Connector {len(self.connectors) + 1}",
|
||||
position=position,
|
||||
normal=normal,
|
||||
x_dir=x_dir,
|
||||
assembly_component_id=self.id,
|
||||
source_obj_id=source_obj_id,
|
||||
)
|
||||
self.connectors[conn.id] = conn
|
||||
self.modified_at = datetime.now()
|
||||
return conn
|
||||
|
||||
def remove_connector(self, connector_id: str) -> bool:
|
||||
"""Remove a connector from this component instance."""
|
||||
if connector_id in self.connectors:
|
||||
del self.connectors[connector_id]
|
||||
self.modified_at = datetime.now()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@dataclass
|
||||
class Assembly:
|
||||
"""
|
||||
An assembly of multiple component instances.
|
||||
|
||||
An assembly groups component instances with relative positions,
|
||||
ready for constraint solving and joint definition between them.
|
||||
Components can be instanced multiple times, each at a different
|
||||
position and rotation.
|
||||
"""
|
||||
|
||||
id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
||||
name: str = "Untitled Assembly"
|
||||
|
||||
components: Dict[str, AssemblyComponent] = field(default_factory=dict)
|
||||
active_assembly_component: Optional[str] = None
|
||||
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
modified_at: datetime = field(default_factory=datetime.now)
|
||||
|
||||
def add_component_instance(
|
||||
self, component_id: str, name: Optional[str] = None
|
||||
) -> AssemblyComponent:
|
||||
"""Add a component instance to the assembly.
|
||||
|
||||
Returns the newly created AssemblyComponent. The same
|
||||
component can be added multiple times (multiple instances).
|
||||
"""
|
||||
ac = AssemblyComponent(
|
||||
component_id=component_id,
|
||||
name=name or f"Instance {len(self.components) + 1}",
|
||||
)
|
||||
self.components[ac.id] = ac
|
||||
if self.active_assembly_component is None:
|
||||
self.active_assembly_component = ac.id
|
||||
self.modified_at = datetime.now()
|
||||
return ac
|
||||
|
||||
def remove_component_instance(self, assembly_component_id: str) -> bool:
|
||||
"""Remove a component instance from the assembly."""
|
||||
if assembly_component_id in self.components:
|
||||
del self.components[assembly_component_id]
|
||||
if self.active_assembly_component == assembly_component_id:
|
||||
self.active_assembly_component = next(
|
||||
iter(self.components.keys()), None
|
||||
)
|
||||
self.modified_at = datetime.now()
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_active_instance(self) -> Optional[AssemblyComponent]:
|
||||
"""Get the currently active assembly component instance."""
|
||||
if (
|
||||
self.active_assembly_component
|
||||
and self.active_assembly_component in self.components
|
||||
):
|
||||
return self.components[self.active_assembly_component]
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Project:
|
||||
"""
|
||||
@@ -351,12 +505,85 @@ class Project:
|
||||
components: Dict[str, Component] = field(default_factory=dict)
|
||||
active_component: Optional[str] = None
|
||||
|
||||
assemblies: Dict[str, Assembly] = field(default_factory=dict)
|
||||
active_assembly: Optional[str] = None
|
||||
|
||||
kernel: OCGeometryKernel = field(default_factory=OCGeometryKernel)
|
||||
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
modified_at: datetime = field(default_factory=datetime.now)
|
||||
file_path: Optional[str] = None
|
||||
|
||||
# ── Component helpers ──
|
||||
|
||||
def add_component(self, component: Optional[Component] = None) -> Component:
|
||||
"""Add a component to the project."""
|
||||
if component is None:
|
||||
component = Component(name=f"Component {len(self.components) + 1}")
|
||||
self.components[component.id] = component
|
||||
if self.active_component is None:
|
||||
self.active_component = component.id
|
||||
self.modified_at = datetime.now()
|
||||
return component
|
||||
|
||||
def remove_component(self, component_id: str) -> bool:
|
||||
"""Remove a component from the project."""
|
||||
if component_id in self.components:
|
||||
del self.components[component_id]
|
||||
if self.active_component == component_id:
|
||||
self.active_component = next(iter(self.components.keys()), None)
|
||||
self.modified_at = datetime.now()
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_active_component(self) -> Optional[Component]:
|
||||
"""Get the currently active component."""
|
||||
if self.active_component and self.active_component in self.components:
|
||||
return self.components[self.active_component]
|
||||
return None
|
||||
|
||||
def set_active_component(self, component_id: Optional[str]) -> None:
|
||||
"""Set the active component."""
|
||||
self.active_component = component_id
|
||||
self.modified_at = datetime.now()
|
||||
|
||||
# ── Assembly helpers ──
|
||||
|
||||
def add_assembly(self, assembly: Optional[Assembly] = None) -> Assembly:
|
||||
"""Add an assembly to the project."""
|
||||
if assembly is None:
|
||||
assembly = Assembly(name=f"Assembly {len(self.assemblies) + 1}")
|
||||
self.assemblies[assembly.id] = assembly
|
||||
if self.active_assembly is None:
|
||||
self.active_assembly = assembly.id
|
||||
self.modified_at = datetime.now()
|
||||
return assembly
|
||||
|
||||
def remove_assembly(self, assembly_id: str) -> bool:
|
||||
"""Remove an assembly from the project."""
|
||||
if assembly_id in self.assemblies:
|
||||
del self.assemblies[assembly_id]
|
||||
if self.active_assembly == assembly_id:
|
||||
self.active_assembly = next(iter(self.assemblies.keys()), None)
|
||||
self.modified_at = datetime.now()
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_active_assembly(self) -> Optional[Assembly]:
|
||||
"""Get the currently active assembly."""
|
||||
if self.active_assembly and self.active_assembly in self.assemblies:
|
||||
return self.assemblies[self.active_assembly]
|
||||
return None
|
||||
|
||||
def set_active_assembly(self, assembly_id: Optional[str]) -> None:
|
||||
"""Set the active assembly."""
|
||||
self.active_assembly = assembly_id
|
||||
self.modified_at = datetime.now()
|
||||
|
||||
def get_component_by_id(self, component_id: str) -> Optional[Component]:
|
||||
"""Look up a component by id across all project components."""
|
||||
return self.components.get(component_id)
|
||||
|
||||
def add_component(self, component: Optional[Component] = None) -> Component:
|
||||
"""Add a component to the project."""
|
||||
if component is None:
|
||||
|
||||
Reference in New Issue
Block a user