fix: enable added week deletion and preserve learning concepts when cloning courses

This commit is contained in:
2026-09-22 15:32:22 +09:00
parent 1d959e2c51
commit 33aac68f16
18 changed files with 633 additions and 61 deletions
+28 -4
View File
@@ -27,6 +27,7 @@ from nav_msgs.msg import OccupancyGrid, Odometry, Path as NavPath
from nav2_msgs.action import NavigateToPose
from pydantic import BaseModel
from rclpy.action import ActionClient
from rclpy.clock import Clock, ClockType
from rclpy.executors import MultiThreadedExecutor
from rclpy.node import Node
from rclpy.parameter import Parameter
@@ -132,6 +133,7 @@ def resolve_2d_transform(transforms: dict, target_frame: str, source_frame: str)
class VelocityPayload(BaseModel):
linear: float = 0.0
angular: float = 0.0
deadman: bool = False
class SteeringPayload(BaseModel):
@@ -257,6 +259,8 @@ class SlamNode(Node):
if self.color_point_cloud_topic not in (self.point_cloud_topic, self.fallback_point_cloud_topic):
self.create_subscription(PointCloud2, self.color_point_cloud_topic, self._on_color_point_cloud, qos_profile_sensor_data)
self.create_subscription(CompressedImage, self.camera_image_topic, self._on_camera_image, qos_profile_sensor_data)
if not os.environ.get("CAMERA_IMAGE_TOPIC"):
self.create_subscription(CompressedImage, "/image_raw/compressed", self._on_camera_image, qos_profile_sensor_data)
self.create_subscription(Odometry, self.icp_odom_topic, self._on_icp_odom, best_effort_qos)
self.create_subscription(Odometry, self.icp_odom_topic, self._on_icp_odom, reliable_qos)
self.create_subscription(OccupancyGrid, self.map_topic, self._on_map, map_qos)
@@ -268,6 +272,10 @@ class SlamNode(Node):
self.create_subscription(TFMessage, "/tf", self._on_tf, qos_profile_sensor_data)
self.create_subscription(TFMessage, "/tf_static", self._on_tf, tf_static_qos)
self._cmd_vel_pub = self.create_publisher(Twist, self.cmd_vel_topic, 10)
self._teleop_lock = threading.Lock()
self._teleop_deadline = None
self._teleop_timer = self.create_timer(
0.1, self._check_teleop_timeout, clock=Clock(clock_type=ClockType.STEADY_TIME))
self._steering_pub = self.create_publisher(Float64, self.steering_topic, 10)
self._initial_pose_pub = self.create_publisher(PoseWithCovarianceStamped, "/initialpose", 10)
self._navigate_to_pose_client = ActionClient(self, NavigateToPose, self.navigate_to_pose_action)
@@ -817,13 +825,27 @@ class SlamNode(Node):
}
self._tf_count += 1
def publish_cmd_vel(self, linear: float, angular: float):
def publish_cmd_vel(self, linear: float, angular: float, deadman: bool = False):
if self.read_only:
raise RuntimeError("읽기 전용 3D SLAM 화면에서는 차량 제어를 지원하지 않습니다.")
if not math.isfinite(linear) or not math.isfinite(angular):
raise RuntimeError("Invalid velocity")
if deadman:
linear = max(-0.1, min(0.1, linear))
angular = max(-0.5, min(0.5, angular))
msg = Twist()
msg.linear.x = float(linear)
msg.angular.z = float(angular)
self._cmd_vel_pub.publish(msg)
with self._teleop_lock:
self._teleop_deadline = time.monotonic() + 0.5 if deadman and (linear or angular) else None
self._cmd_vel_pub.publish(msg)
def _check_teleop_timeout(self):
# Only stop expired manual commands, never idle autonomous navigation.
with self._teleop_lock:
if self._teleop_deadline is not None and time.monotonic() >= self._teleop_deadline:
self._teleop_deadline = None
self._cmd_vel_pub.publish(Twist())
def set_simulator_steering(self, angle: float) -> None:
if self.read_only:
@@ -1192,6 +1214,7 @@ class SlamNode(Node):
return {
"ok": True,
"teleop_deadman": not self.read_only,
"topics": {
"odom": self.odom_topic,
"scan": self.scan_topic,
@@ -1549,7 +1572,7 @@ def debug_topics():
def cmd_vel(payload: VelocityPayload):
if not slam_node:
raise HTTPException(status_code=503, detail="ROS node is not ready")
slam_node.publish_cmd_vel(payload.linear, payload.angular)
slam_node.publish_cmd_vel(payload.linear, payload.angular, payload.deadman)
return {"ok": True}
@@ -1643,11 +1666,12 @@ async def ws_control(websocket: WebSocket):
velocity = VelocityPayload(
linear=payload.get("linear", 0.0),
angular=payload.get("angular", 0.0),
deadman=payload.get("deadman", False),
)
except (TypeError, ValueError):
continue
try:
slam_node.publish_cmd_vel(velocity.linear, velocity.angular)
slam_node.publish_cmd_vel(velocity.linear, velocity.angular, velocity.deadman)
except RuntimeError:
continue
except WebSocketDisconnect: