- assembly draft

This commit is contained in:
bklronin
2026-07-05 10:16:49 +02:00
parent 9f10a5c5e5
commit b595b88e04
5 changed files with 1335 additions and 126 deletions
+14 -4
View File
@@ -4,11 +4,12 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="- sketch enhacements">
<list default="true" id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="- UI refinement, button position ui file as source no dirty drafting anymore">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gui.ui" beforeDir="false" afterPath="$PROJECT_DIR$/gui.ui" afterDir="false" />
<change beforePath="$PROJECT_DIR$/gui_ui.py" beforeDir="false" afterPath="$PROJECT_DIR$/gui_ui.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/fluency/main.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/main.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/fluency/rendering/occ_renderer.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/rendering/occ_renderer.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/fluency/models/data_model.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/models/data_model.py" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -313,7 +314,15 @@
<option name="project" value="LOCAL" />
<updated>1783159860774</updated>
</task>
<option name="localTasksCounter" value="26" />
<task id="LOCAL-00026" summary="- UI refinement, button position ui file as source no dirty drafting anymore">
<option name="closed" value="true" />
<created>1783174566362</created>
<option name="number" value="00026" />
<option name="presentableId" value="LOCAL-00026" />
<option name="project" value="LOCAL" />
<updated>1783174566362</updated>
</task>
<option name="localTasksCounter" value="27" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -355,6 +364,7 @@
<MESSAGE value="- Basic operations" />
<MESSAGE value="- removed cadquery deoendency" />
<MESSAGE value="- sketch enhacements" />
<option name="LAST_COMMIT_MESSAGE" value="- sketch enhacements" />
<MESSAGE value="- UI refinement, button position ui file as source no dirty drafting anymore" />
<option name="LAST_COMMIT_MESSAGE" value="- UI refinement, button position ui file as source no dirty drafting anymore" />
</component>
</project>
+105 -80
View File
@@ -15,72 +15,6 @@
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="12" column="2">
<widget class="QGroupBox" name="compos_in_assembly_box">
<property name="minimumSize">
<size>
<width>0</width>
<height>50</height>
</size>
</property>
<property name="title">
<string>Components in Assembly</string>
</property>
</widget>
</item>
<item row="12" column="3">
<widget class="QGroupBox" name="joint_tools">
<property name="minimumSize">
<size>
<width>0</width>
<height>50</height>
</size>
</property>
<property name="title">
<string>Joint Tools</string>
</property>
<layout class="QGridLayout" name="gridLayout_10">
<item row="0" column="0">
<widget class="QPushButton" name="pb_add_connector">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="text">
<string>+ Cnct</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="pb_remove_connector">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="text">
<string>- Cnct</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QGroupBox" name="groupBox_5">
<property name="sizePolicy">
@@ -695,19 +629,6 @@
</layout>
</widget>
</item>
<item row="12" column="1">
<widget class="QGroupBox" name="assembly_box">
<property name="minimumSize">
<size>
<width>0</width>
<height>50</height>
</size>
</property>
<property name="title">
<string>Assembly</string>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="11">
<widget class="QTabWidget" name="InputTab">
<property name="sizePolicy">
@@ -1031,7 +952,7 @@
<property name="title">
<string>Drawing</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0,0,0">
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0,0">
<property name="leftMargin">
<number>10</number>
</property>
@@ -1149,6 +1070,110 @@
</layout>
</widget>
</item>
<item row="12" column="1" colspan="2">
<widget class="QGroupBox" name="assembly_box">
<property name="minimumSize">
<size>
<width>0</width>
<height>50</height>
</size>
</property>
<property name="title">
<string>Assembly</string>
</property>
</widget>
</item>
<item row="11" column="3" rowspan="2">
<widget class="QGroupBox" name="joint_tools">
<property name="minimumSize">
<size>
<width>0</width>
<height>50</height>
</size>
</property>
<property name="title">
<string>Joint Tools</string>
</property>
<layout class="QGridLayout" name="gridLayout_10">
<item row="0" column="2">
<widget class="QPushButton" name="pb_remove_connector">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="text">
<string>- Cnct</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="pb_add_connector">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="text">
<string>+ Cnct</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="pb_add_connector_2">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="text">
<string>+Jnt</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="pb_add_connector_3">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="text">
<string>-Jnt</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
+48 -39
View File
@@ -37,34 +37,6 @@ class Ui_fluencyCAD(object):
self.centralwidget.setObjectName(u"centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setObjectName(u"gridLayout")
self.compos_in_assembly_box = QGroupBox(self.centralwidget)
self.compos_in_assembly_box.setObjectName(u"compos_in_assembly_box")
self.compos_in_assembly_box.setMinimumSize(QSize(0, 50))
self.gridLayout.addWidget(self.compos_in_assembly_box, 12, 2, 1, 1)
self.joint_tools = QGroupBox(self.centralwidget)
self.joint_tools.setObjectName(u"joint_tools")
self.joint_tools.setMinimumSize(QSize(0, 50))
self.gridLayout_10 = QGridLayout(self.joint_tools)
self.gridLayout_10.setObjectName(u"gridLayout_10")
self.pb_add_connector = QPushButton(self.joint_tools)
self.pb_add_connector.setObjectName(u"pb_add_connector")
self.pb_add_connector.setMinimumSize(QSize(50, 50))
self.pb_add_connector.setMaximumSize(QSize(50, 50))
self.gridLayout_10.addWidget(self.pb_add_connector, 0, 0, 1, 1)
self.pb_remove_connector = QPushButton(self.joint_tools)
self.pb_remove_connector.setObjectName(u"pb_remove_connector")
self.pb_remove_connector.setMinimumSize(QSize(50, 50))
self.pb_remove_connector.setMaximumSize(QSize(50, 50))
self.gridLayout_10.addWidget(self.pb_remove_connector, 0, 1, 1, 1)
self.gridLayout.addWidget(self.joint_tools, 12, 3, 1, 1)
self.groupBox_5 = QGroupBox(self.centralwidget)
self.groupBox_5.setObjectName(u"groupBox_5")
sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
@@ -384,12 +356,6 @@ class Ui_fluencyCAD(object):
self.gridLayout.addWidget(self.groupBox_4, 2, 3, 1, 1)
self.assembly_box = QGroupBox(self.centralwidget)
self.assembly_box.setObjectName(u"assembly_box")
self.assembly_box.setMinimumSize(QSize(0, 50))
self.gridLayout.addWidget(self.assembly_box, 12, 1, 1, 1)
self.InputTab = QTabWidget(self.centralwidget)
self.InputTab.setObjectName(u"InputTab")
sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
@@ -625,6 +591,48 @@ class Ui_fluencyCAD(object):
self.gridLayout.addWidget(self.groupBox_2, 2, 0, 1, 1)
self.assembly_box = QGroupBox(self.centralwidget)
self.assembly_box.setObjectName(u"assembly_box")
self.assembly_box.setMinimumSize(QSize(0, 50))
self.gridLayout.addWidget(self.assembly_box, 12, 1, 1, 2)
self.joint_tools = QGroupBox(self.centralwidget)
self.joint_tools.setObjectName(u"joint_tools")
self.joint_tools.setMinimumSize(QSize(0, 50))
self.gridLayout_10 = QGridLayout(self.joint_tools)
self.gridLayout_10.setObjectName(u"gridLayout_10")
self.pb_remove_connector = QPushButton(self.joint_tools)
self.pb_remove_connector.setObjectName(u"pb_remove_connector")
self.pb_remove_connector.setMinimumSize(QSize(50, 50))
self.pb_remove_connector.setMaximumSize(QSize(50, 50))
self.gridLayout_10.addWidget(self.pb_remove_connector, 0, 2, 1, 1)
self.pb_add_connector = QPushButton(self.joint_tools)
self.pb_add_connector.setObjectName(u"pb_add_connector")
self.pb_add_connector.setMinimumSize(QSize(50, 50))
self.pb_add_connector.setMaximumSize(QSize(50, 50))
self.gridLayout_10.addWidget(self.pb_add_connector, 0, 1, 1, 1)
self.pb_add_connector_2 = QPushButton(self.joint_tools)
self.pb_add_connector_2.setObjectName(u"pb_add_connector_2")
self.pb_add_connector_2.setMinimumSize(QSize(50, 50))
self.pb_add_connector_2.setMaximumSize(QSize(50, 50))
self.gridLayout_10.addWidget(self.pb_add_connector_2, 1, 1, 1, 1)
self.pb_add_connector_3 = QPushButton(self.joint_tools)
self.pb_add_connector_3.setObjectName(u"pb_add_connector_3")
self.pb_add_connector_3.setMinimumSize(QSize(50, 50))
self.pb_add_connector_3.setMaximumSize(QSize(50, 50))
self.gridLayout_10.addWidget(self.pb_add_connector_3, 1, 2, 1, 1)
self.gridLayout.addWidget(self.joint_tools, 11, 3, 2, 1)
fluencyCAD.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(fluencyCAD)
self.menubar.setObjectName(u"menubar")
@@ -658,10 +666,6 @@ class Ui_fluencyCAD(object):
self.actionNew_Project.setText(QCoreApplication.translate("fluencyCAD", u"New", None))
self.actionLoad_Project.setText(QCoreApplication.translate("fluencyCAD", u"Load", None))
self.actionRecent.setText(QCoreApplication.translate("fluencyCAD", u"Recent", None))
self.compos_in_assembly_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Components in Assembly", None))
self.joint_tools.setTitle(QCoreApplication.translate("fluencyCAD", u"Joint Tools", None))
self.pb_add_connector.setText(QCoreApplication.translate("fluencyCAD", u"+ Cnct", None))
self.pb_remove_connector.setText(QCoreApplication.translate("fluencyCAD", u"- Cnct", None))
self.groupBox_5.setTitle(QCoreApplication.translate("fluencyCAD", u"Snapping Points", None))
self.label.setText(QCoreApplication.translate("fluencyCAD", u"Snp Dst", None))
self.pb_snap_vert.setText(QCoreApplication.translate("fluencyCAD", u"Vert", None))
@@ -739,7 +743,6 @@ class Ui_fluencyCAD(object):
self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", None))
self.pb_export_step.setText(QCoreApplication.translate("fluencyCAD", u"STEP", None))
self.pb_export_iges.setText(QCoreApplication.translate("fluencyCAD", u"IGES", None))
self.assembly_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Assembly", None))
self.InputTab.setTabText(self.InputTab.indexOf(self.sketch_tab), QCoreApplication.translate("fluencyCAD", u"Sketch", None))
self.groupBox_7.setTitle(QCoreApplication.translate("fluencyCAD", u"Executive", None))
self.pushButton_5.setText(QCoreApplication.translate("fluencyCAD", u"Load Code", None))
@@ -796,6 +799,12 @@ class Ui_fluencyCAD(object):
self.pb_offset_tool.setToolTip(QCoreApplication.translate("fluencyCAD", u"Offset selected sketch face (duplicate + offset boundary)", None))
#endif // QT_CONFIG(tooltip)
self.pb_offset_tool.setText(QCoreApplication.translate("fluencyCAD", u"Offst", None))
self.assembly_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Assembly", None))
self.joint_tools.setTitle(QCoreApplication.translate("fluencyCAD", u"Joint Tools", None))
self.pb_remove_connector.setText(QCoreApplication.translate("fluencyCAD", u"- Cnct", None))
self.pb_add_connector.setText(QCoreApplication.translate("fluencyCAD", u"+ Cnct", None))
self.pb_add_connector_2.setText(QCoreApplication.translate("fluencyCAD", u"+Jnt", None))
self.pb_add_connector_3.setText(QCoreApplication.translate("fluencyCAD", u"-Jnt", None))
self.menuFile.setTitle(QCoreApplication.translate("fluencyCAD", u"File", None))
self.menuSettings.setTitle(QCoreApplication.translate("fluencyCAD", u"Settings", None))
# retranslateUi
+941 -3
View File
File diff suppressed because it is too large Load Diff
+227
View File
@@ -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: