integrate ganjihong engine refactor

This commit is contained in:
2026-09-09 18:41:56 +08:00
parent cf9cd0366a
commit 21a5185926
116 changed files with 3596 additions and 12259 deletions
+63 -9
View File
@@ -12,7 +12,7 @@ from typing import TYPE_CHECKING, Any, Callable
from ..extents import _extent_vectors_from_normal, _normal_from_sketch
from ..runtime_base import ExtentVector, FeatureExecutionError
from ..specs import AxisSpec, HoleSpec, PlaneSpec, Vector3, pattern_instance_member_id, transform_copy_member_id, vector_add, vector_cross, vector_dot, vector_scale, vector_subtract, vector_unit
from ..topology import FeaturePlanNode, FeatureResult, RuntimeDiagnostic, SelectorResolution, TopologyDelta
from ..topology import FeaturePlanNode, FeatureResult, RuntimeDiagnostic, SelectorResolution, TopologyDelta, TopologyRecord
if TYPE_CHECKING: # pragma: no cover - import for type checkers only
from ..session import ExecutionSession
@@ -343,6 +343,8 @@ def _register_added_solid(
session: "ExecutionSession",
node: FeaturePlanNode,
solid: Any,
*,
topology_delta: TopologyDelta | None = None,
) -> None:
"""Register an additive primitive solid (box/cyl/sphere/thread/gear/rack/bend).
@@ -356,10 +358,56 @@ def _register_added_solid(
if node.params.get("result_mode") == "new_body":
combined = session.adapter.combine(session.body, solid)
members = {**session.body_members, node.feature_id: solid}
session.register_body(node.feature_id, combined, replay_node=node, body_members=members)
session.register_body(
node.feature_id, combined, replay_node=node, body_members=members,
topology_delta=topology_delta,
)
return
fused = session.adapter.fuse(session.body, solid)
session.register_body(node.feature_id, fused, replay_node=node)
if session.body is None:
session.register_body(node.feature_id, solid, replay_node=node, topology_delta=topology_delta)
return
if topology_delta is None:
session.register_body(node.feature_id, session.adapter.fuse(session.body, solid), replay_node=node)
return
# Preserve primitive output roles only through the exact OCC fuse history.
# The transient records provide source handles, not selectable snapshots.
role_records = _direct_output_role_records(session, node, solid, topology_delta)
fused, fuse_delta = session.adapter.fuse_with_topology_delta(session.body, solid)
session.register_body(
node.feature_id, fused, replay_node=node, topology_delta=fuse_delta,
topology_predecessors=role_records,
)
def _direct_output_role_records(
session: "ExecutionSession",
node: FeaturePlanNode,
solid: Any,
topology_delta: TopologyDelta,
) -> list[TopologyRecord]:
"""Attach only builder-proven output roles to a transient primitive snapshot."""
records = session.adapter.topology_records(solid, node.feature_id, f"transient:{node.feature_id}")
result: list[TopologyRecord] = []
for record in records:
roles = {
relation.output_role
for relation in topology_delta.relations
if relation.output_role is not None
and relation.kind == record.kind
and any(session.topology._same_topology_value(record.value, value) for value in relation.result_values)
}
if roles:
result.append(TopologyRecord(
record_id=record.record_id,
kind=record.kind,
feature_id=record.feature_id,
body_id=record.body_id,
geometry=record.geometry,
value=record.value,
owner_feature_ids=(node.feature_id,),
output_roles=tuple(sorted(roles)),
))
return result
def _host_plane(resolution: SelectorResolution) -> PlaneSpec:
@@ -415,10 +463,12 @@ def _selector_edges(node: FeaturePlanNode, session: "ExecutionSession", *, tange
edges: list[Any] = []
for item in resolved:
if item.record.kind == "edge":
edges.append(item.record.value)
elif item.record.kind == "face":
edges.extend(edge for edge in item.record.value.edges() if is_body_boundary(edge))
records = item.records or ((item.record,) if item.record is not None else ())
for record in records:
if record.kind == "edge":
edges.append(record.value)
elif record.kind == "face":
edges.extend(edge for edge in record.value.edges() if is_body_boundary(edge))
if not edges:
raise ValueError("selectors did not resolve any edges")
return session.adapter.tangent_edges(session.body, edges) if tangent_propagation else edges
@@ -432,7 +482,11 @@ def _shell_target(node: FeaturePlanNode, session: "ExecutionSession") -> tuple[A
failed = next((item for item in resolved if item.status != "resolved"), None)
if failed:
raise ValueError(failed.diagnostic.message if failed.diagnostic else "selector resolution failed")
records = [item.record for item in resolved if item.record is not None]
records = [
record
for item in resolved
for record in (item.records or ((item.record,) if item.record is not None else ()))
]
if not records or any(record.kind != "face" for record in records):
raise ValueError("shell selectors must resolve to faces")
target_ids = {record.body_id for record in records}
@@ -73,9 +73,9 @@ def _execute_cylinder(node: FeaturePlanNode, session: "ExecutionSession") -> Fea
raise ValueError("cylinder_add axis must define origin_mm and direction")
axis = AxisSpec.from_mapping(raw_axis) if isinstance(raw_axis, dict) else None
# 2. 由适配器创建原生圆柱(axis=None 即世界 +Z 过原点)。
solid = session.adapter.cylinder(radius, height, axis)
solid, topology_delta = session.adapter.cylinder_with_topology_delta(radius, height, axis)
# 3. 与当前主体做布尔并后登记为新主体,并返回该特征的结果对象。
_register_added_solid(session, node, solid)
_register_added_solid(session, node, solid, topology_delta=topology_delta)
return session.result(node)