np.clip(a,a_min,a_max,out=None)

本文详细介绍了numpy.clip函数的使用方法,该函数可以将数组中的元素限制在指定的最小值和最大值之间,超过范围的元素将被设置为边界值。这对于数据预处理和异常值处理非常有用。

clip这个函数将a数组中的元素限制在a_min, a_max之间;

a中元素大于a_max的就使得它等于a_max;

a中元素小于a_min,的就使得它等于a_min。

class PositionController: def __init__(self, dt=0.004): """初始化位置控制器 参数: dt: 控制周期(), 默认为4ms """ # 控制参数 # 控制参数 # 控制参数 self.dt = dt # 控制周期(4ms) self.pos_kp = 1.2 # 位置环比例系数 self.pos_ip = 0.5 # 位置环积分系数 self.pos_dp = 0.5 # 位置环微分系数 self.pos_integral = 0 # self.pos_prev_error = 0 self.reached_target = False # 是否到达目标位置 self.position_tolerance = 0.1 # 0.01 位置死区 self.velocity_tolerance = 0.1 # 速度死区 self.dec_mode = False self.stopSignal = False # 停止信号 self.stopEdge = False # 停止信号上升 # 没用到 self.vel_kp = 0 # 速度环比例系数 self.vel_ip = 0 # 速度环积分系数 self.vel_dp = 0.1 # 速度环微分系数 self.vel_integral = 0 self.vel_prev_error = 0 # self.vel_kp = 1.2 # 速度环比例系数 self.max_accel = 5000000.0 # 最大加速度(mm/s²) self.max_decel = 5000000.0 # 最大减速度(mm/s²) self.stop_decel = 5000000.0 # 最大减速度(mm/s²) self.max_vel = 500.0 # 最大速度(mm/s) self.decModeMaxVel = self.max_vel # 停止衰减速度 # 系统状态 self.actual_pos = 0 # 实际位置(mm) self.actual_vel = 0.0 # 实际速度(mm/s) self.target_pos = 0.0 # 目标位置(mm) self.target_vel = 0.0 # 目标速度(mm/s) self.dec = 0 # 数据记录 self.time_log = np.array([], dtype=np.float32) self.target_pos_log = np.array([], dtype=np.float32) self.actual_pos_log = np.array([], dtype=np.float32) self.target_vel_log = np.array([], dtype=np.float32) self.actual_vel_log = np.array([], dtype=np.float32) def update_target(self, new_pos, new_vel=None): """动态更新目标位置和速度 参数: new_pos: 新的目标位置(mm) new_vel: 新的目标速度(mm/s), 可选 """ # 之前的位置误差 ord_error = abs(self.target_pos - self.actual_pos) # 新的目标位置 self.target_pos = new_pos # np.array(new_pos) # 新的位置误差 new_error = abs(self.target_pos - self.actual_pos) # 误差变大 if new_error>ord_error: self.dec_mode = False # 重新判断减速距离 if self.stopSignal is False: # 停止信号刷新 self.stopEdge = False # 重新计算停止距离 if new_vel is not None: self.max_vel = abs(new_vel) def position_control(self): """位置环控制计算""" # 计算位置误差 pos_error = self.target_pos - self.actual_pos # PID计算 self.pos_integral += pos_error * self.dt derivative = (pos_error - self.pos_prev_error) / self.dt # 控制输出 output = (self.pos_kp * pos_error + self.pos_ip * self.pos_integral + self.pos_dp * derivative) # 位置环输出(作为速度指令) return output # 更新状态 #self.pos_prev_error = pos_error # 位置环输出(作为速度指令) # vel_cmd = self.pos_kp * pos_error vel_cmd = output # 速度限幅 # max_vel = min(self.target_vel, self.max_vel) # vel_cmd = np.clip(vel_cmd, -self.max_vel, self.max_vel) # vel_cmd = np.clip(vel_cmd, -max_vel, max_vel) return vel_cmd def calculate_acceleration(self, vel_cmd): """计算加速度命令""" # 计算速度误差 vel_error = vel_cmd - self.actual_vel # 计算停止距离 if self.dec_mode is False: stop_distance = self.actual_vel ** 2 / (2 * self.max_decel) pos_error = self.target_pos - self.actual_pos # 到减速模式,减到底, if abs(pos_error) <= abs(stop_distance) and abs(pos_error) > 0.001: self.dec_mode = True if self.stopSignal: self.dec_mode = True if self.stopEdge is False: # self.decModeMaxVel = self.max_vel # stop_distance = self.actual_vel ** 2 / (2 * self.max_decel) stop_distance = self.actual_vel ** 2 / (2 * self.max_accel) self.stopEdge = True self.target_pos = self.actual_pos + stop_distance decel = -np.sign(self.actual_vel) * self.stop_decel # print(decel) return decel # 判断是否进入减速阶段 elif self.dec_mode: #False and abs(pos_error) <= abs(stop_distance) and abs(pos_error) > 0.001: if self.stopEdge is False: # self.decModeMaxVel = self.max_vel self.stopEdge = True self.dec_mode = True # 减速阶段: 应用最大减速度 # self.max_vel = 0 # decel = -np.sign(self.actual_vel) * self.max_decel decel = -np.sign(self.actual_vel) * self.max_accel # print(decel) return decel else: # 加速/匀速阶段 # 计算所需加速度 # self.dec_mode = False self.decModeMaxVel = self.max_vel accel = vel_error / self.dt # accel = output / self.dt # 加速度限幅 if accel > 0: return min(accel, self.max_accel) else: return max(accel, -self.max_decel) # return max(accel, -self.max_accel) # print( "sdf") # stop_distance = self.actual_vel ** 2 / (2 * self.max_decel) # pos_error = self.target_pos - self.actual_pos # # # 到减速模式,减到底, # if abs(pos_error) <= abs(stop_distance) and abs(pos_error) > 0.001: # self.dec_mode = True # return max(accel, -self.max_decel) def update_state(self, accel): """更新系统状态""" # 更新速度 self.actual_vel += accel * self.dt if self.dec_mode: # 减速模式 # 速度限幅 last_maxvel = self.decModeMaxVel # 速度限幅 self.decModeMaxVel = min(abs(self.actual_vel), abs(self.max_vel), self.decModeMaxVel) if last_maxvel == self.decModeMaxVel: # 等幅振荡 # 等幅振荡 等比例衰减 self.decModeMaxVel *= 0.9 # self.decModeMaxVel*0.03 # 更新最小速度 self.actual_vel = np.clip(self.actual_vel, -self.decModeMaxVel, self.decModeMaxVel) # 更新位置 # arange_dec = int(self.actual_vel * self.dt) arange_dec = self.actual_vel * self.dt self.actual_pos += arange_dec pos_error = abs(self.target_pos - self.actual_pos) v0 = abs(self.actual_vel) # 在死区范围内 done = pos_error<self.position_tolerance or v0<self.velocity_tolerance # print(done) return done, arange_dec, self.actual_vel else: # 更新位置 # 速度限幅 self.actual_vel = np.clip(self.actual_vel, -self.max_vel, self.max_vel) # 更新位置 # arange_dec = int(self.actual_vel * self.dt) arange_dec = self.actual_vel * self.dt self.actual_pos += arange_dec # self.actual_vel * self.dt # print(type(arange_dec)) return False, arange_dec, self.actual_vel def run_cycle(self, current_time): """运行一个控制周期""" # 位置环控制 vel_cmd = self.position_control() # 计算加速度 accel = self.calculate_acceleration(vel_cmd) # 更新系统状态 arange_dec = self.update_state(accel) # 记录数据 self.time_log = np.append(self.time_log, current_time) self.target_pos_log = np.append(self.target_pos_log, self.target_pos) # self.target_pos) self.actual_pos_log = np.append(self.actual_pos_log, self.actual_pos) self.target_vel_log = np.append(self.target_vel_log, self.max_vel) # self.max_vel) # self.target_vel_log = np.append(self.target_vel_log, self.dec) self.actual_vel_log = np.append(self.actual_vel_log, self.actual_vel) yield arange_dec # return arange_dec def visualize(self): # return """可视化位置和速度曲线""" plt.figure(figsize=(12, 8)) # 位置曲线 plt.subplot(2, 1, 1) plt.plot(self.time_log, self.target_pos_log, 'r-', label='目标位置') plt.plot(self.time_log, self.actual_pos_log, 'b-', label='实际位置') plt.xlabel('时间 (s)') plt.ylabel('位置 (mm)') plt.title('位置跟踪曲线') plt.legend() plt.grid(True) # 速度曲线 plt.subplot(2, 1, 2) plt.plot(self.time_log, self.target_vel_log, 'r-', label='目标速度') plt.plot(self.time_log, self.actual_vel_log, 'g-', label='实际速度') plt.xlabel('时间 (s)') plt.ylabel('速度 (mm/s)') plt.title('速度跟踪曲线') plt.legend() plt.grid(True) plt.tight_layout() plt.show()优化一下
09-04
class VelocityController: def __init__(self, dt=0.004): """初始化速度控制器 25US之下 参数: dt: 控制周期(), 默认为4ms """ # 控制参数 self.dt = 0.004 # 控制周期(4ms) # 速度环 self.vel_kp = np.array([18.5])# 18.5 # 速度环比例系数 self.vel_ip = np.array([1]) # 1 # 速度环积分系数 self.vel_dp = np.array([0.2]) # 0.20 # 速度环微分系数 self.vel_kf = np.array([1.2]) # 1.2 # 前馈补偿(基于目标变化率) self.prev_target = np.array([0]) # 0 # 前馈输入 self.vel_integral = np.array([0]) # 0 # 累计误差 self.vel_prev_error = np.array([0]) # # # 阻尼 self.init_vel = np.array([0]) # 用于计算阈值 阈值=|实际速度 - 目标速度| / |初始速度 - 目标速度| self.overshoot_threshold = np.array([0.49]) # 0.49 # 过冲检测阈值 (5%) (数值越小,代表越接近目标,最大是1) self.adaptive_factor = np.array([0.45]) # 0.45 # 自适应调节因子 在这个比例进行衰减,直到衰减系数到0 self.adaptive = False # 自适应 开关 # 加速类型 self.accel_type = "PID" # or "T" # 衰减极致 self.vel_min_kp = np.array([6])# 6 # 最小的 比例系数,如果衰减到0 将代表没有误差, self.vel_max_dp = np.array([1]) # 1 # 最大的 微分系数, 太大滞后严重 # 积分直接 = 0 (过大的比例已经会引起超调,再加入积分将更严重) self.pid_param = {"p": self.vel_kp, "i": self.vel_ip, "d": self.vel_dp, "f": self.vel_kf, "adaptive_factor": self.adaptive_factor} # self.vel_kp = 1.2 # 速度环比例系数 self.max_accel = np.array([10000000]) # 10000000 # 最大加速度(mm/s²) self.max_jerk = np.array([200000000]) # 200000000 # 最大加加速度 (m/s³) self.stop_decel = np.array([7000000]) # 7000000.0 # 最大减速度(mm/s²) 受限于最大加速度 # 停止(追踪速度到0) self.__stopSignal = False # 停止信号 self.stop_start_vel = np.array([0.0]) self.stop_phase = "NORMAL" # 减速阶段: NORMAL, DECEL, CRAWL self.crawl_threshold = 0 # 3000 # 进入低速爬行模式的速度阈值(m/s) self.position_tolerance = np.array([0.1]) # 0.1 # 位置死区 在速度环中无用 self.velocity_tolerance = np.array([0.1]) # 速度死区 self.stopModeMaxVel = np.array([0.0]) self.safety_distance = 2000 # 安全停止距离 # 系统状态 self.actual_pos = np.array([0.0]) # 0.0 # 实际位置(mm) self.actual_vel = np.array([0.0]) # 实际速度(mm/s) self.target_vel = np.array([0.0]) # 目标速度(mm/s) self.actual_acc = np.array([0.0]) # 实际加速度 # self.lock = threading.Lock() # 数据记录 self.time_log = np.array([], dtype=np.float32) self.target_pos_log = np.array([], dtype=np.float32) self.actual_pos_log = np.array([], dtype=np.float32) self.target_vel_log = np.array([], dtype=np.float32) self.actual_vel_log = np.array([], dtype=np.float32) self.actual_overshoot_log = np.array([], dtype=np.float32) self.actual_overshoot2_log = np.array([], dtype=np.float32) @property def stopSignal(self): return self.__stopSignal @stopSignal.setter def stopSignal(self, value): if (not self.__stopSignal) and value: # if hasattr(self, "current_time"): # print("stop curernt",self.current_time) self.target_vel = 0 # 将目标速度设为0 self.reset_pid() self.stop_phase = "NORMAL" self.stop_start_vel = abs(self.actual_vel) # 计算需要的减速度 (= u² + 2as, 设s=0.5m为安全距离) if self.stop_start_vel >= 0: required_decel = int((self.stop_start_vel ** 2) / (2 * self.safety_distance)) self.stop_decel = min(self.max_accel, required_decel) self.stopModeMaxVel = abs(self.actual_vel) # 衰减最大速度 # print("stop_decel", self.stop_decel) else: self.stop_decel = self.max_accel self.__stopSignal = value def update_target(self, new_vel=None, **kwargs): """动态更新目标位置和速度 参数: new_vel: 新的目标速度(mm/s), 可选 """ # 目标发生变化 if self.target_vel != new_vel: # 记录初始速度 self.init_vel = self.actual_vel # 重置pid self.reset_pid() if new_vel == 0: self.stopSignal = True else: self.stopSignal = False self.target_vel = new_vel def set_pid(self, p, i, d, f, adaptive_factor): """ 速度环 pid 参数 :param p: 速度环比例系数 :param i: 速度环积分系数 :param d: 速度环微分系数 :param f: 前馈补偿(基于目标变化率) :param adaptive_factor: 自适应调节因子 :return: """ self.vel_kp = p # 速度环比例系数 self.vel_ip = i # 速度环积分系数 self.vel_dp = d # 速度环微分系数 self.vel_kf = f # 前馈补偿(基于目标变化率) self.adaptive_factor = adaptive_factor # 自适应调节因子 self.pid_param = {"p": p, "i": i, "d": d, "f": f, "adaptive_factor": adaptive_factor} def reset_pid(self): """ 由于自动增加阻尼,会修改参数,每次更新速度会重置参数。 :return: """ self.vel_kp = self.pid_param["p"] # 速度环比例系数 self.vel_ip = self.pid_param["i"] # 速度环积分系数 self.vel_dp = self.pid_param["d"] # 速度环微分系数 self.vel_kf = self.pid_param["f"] # 前馈补偿(基于目标变化率) self.adaptive_factor = self.pid_param["adaptive_factor"] # 自适应调节因子 def detect_overshoot(self, current_vel, target_vel): """检测速度过冲并调整参数""" if abs(target_vel) > 0.1 or self.adaptive_factor > 0: # 忽略零速度附近 overshoot_ratio = abs(current_vel - target_vel) / abs(target_vel - self.init_vel) # print(overshoot_ratio) self.overshoot = overshoot_ratio if overshoot_ratio < self.overshoot_threshold: # self.overshoot_count += 1 # 自适应调整增益 if self.adaptive_factor < 0.01: self.adaptive_factor = 0 else: self.adaptive_factor *= 0.9 # 减小10% # 临时增加阻尼 self.vel_dp *= (1 + self.adaptive_factor) # 增加微分增益(Kd)以抑制超调 self.vel_kp *= (1 - self.adaptive_factor) # 减小比例增益(Kp)降低响应速度 self.vel_dp = min(self.vel_dp, self.vel_max_dp) self.vel_kp = max(self.vel_kp, self.vel_min_kp) self.vel_ip = 0 # 积分项直到0 return True return False def run_cycle(self, current_time=None): """运行一个控制周期""" # 硬件中的控制算法(模拟行为) self.current_time = current_time if self.__stopSignal: # 停止模式 - T型减速 current_vel = abs(self.actual_vel) # self.actual_vel -= np.sign(self.actual_vel) * self.stop_decel * self.dt # self.actual_vel = np.clip(self.actual_vel, -self.max_accel, self.max_accel) # 阶段1: 正常减速 (匀减速) if self.stop_phase == "NORMAL": # 应用固定减速度 self.actual_vel -= np.sign(self.actual_vel) * self.stop_decel * self.dt self.jerk = self.stop_decel / self.dt # 检查是否进入低速爬行阶段 if current_vel <= self.crawl_threshold: self.stop_phase = "CRAWL" # 阶段2: 低速爬行 (线性减速到零) elif self.stop_phase == "CRAWL": # 计算线性衰减因子 decay_factor = current_vel / self.crawl_threshold # 应用线性减速 # direction = 1 if self.actual_vel > 0 else -1 self.actual_vel -= np.sign(self.actual_vel) * self.stop_decel * decay_factor * self.dt self.jerk = self.stop_decel * decay_factor / self.dt # 检查是否完全停止 if abs(self.actual_vel) > 0.1: last_maxvel = self.stopModeMaxVel self.stopModeMaxVel = min(abs(self.actual_vel), self.stopModeMaxVel) if last_maxvel == self.stopModeMaxVel: # 等幅振荡 # 等幅振荡 等比例衰减 self.stopModeMaxVel *= 0.5 # self.decModeMaxVel * 0.03 # print(self.actual_vel) self.actual_vel = np.clip(self.actual_vel, -self.stopModeMaxVel, self.stopModeMaxVel) # self.actual_acc = (self.actual_vel - current_vel) / self.dt # self.actual_acc = np.clip(self.actual_acc, -self.stop_decel, self.stop_decel) # self.actual_vel = current_vel + self.actual_acc*self.dt if abs(self.actual_vel) <= self.velocity_tolerance: self.actual_acc = (self.actual_vel - current_vel) / self.dt self.actual_vel = 0.0 self.stop_phase = "NORMAL" arange_dec = self.actual_vel * self.dt self.actual_pos += arange_dec # if # return current_vel <= self.velocity_tolerance, arange_dec, self.actual_vel elif self.accel_type == "PID": vel_error = self.target_vel - self.actual_vel # 检测过冲并自适应调整 if self.adaptive: self.detect_overshoot(self.actual_vel, self.target_vel) else: self.overshoot = 0 self.vel_integral += vel_error * self.dt derivative = (vel_error - self.vel_prev_error) / self.dt # 控制输出 output = (self.vel_kp * vel_error + self.vel_ip * self.vel_integral + self.vel_dp * derivative) self.vel_prev_error = vel_error # 前馈通道, 应变目标速度的突变 feedforward = self.vel_kf * (self.target_vel - self.prev_target) / self.dt output += feedforward # 模拟加速度生成(带加加速度限制) # 加速度限制 desired_accel = np.clip(output, -self.max_accel, self.max_accel) # 加速度变化率限制(确保加速度连续) jerk = (desired_accel - self.actual_acc) / self.dt self.jerk = jerk = np.clip(jerk, -self.max_jerk, self.max_jerk) self.actual_acc += jerk * self.dt # 更新速度和位置 self.actual_vel += self.actual_acc * self.dt self.prev_target = self.target_vel # # 记录最大衰减速度 为减速过程做准备 # self.stopModeMaxVel = self.actual_vel # 更新 位置 arange_dec = self.actual_vel * self.dt self.actual_pos += arange_dec # return False, arange_dec, self.actual_vel elif self.accel_type == "T": # self.overshoot = 0 # T型加速控制策略 vel_error = self.target_vel - self.actual_vel # 确定加速度方向和大小 if abs(vel_error) > 0: # 确定加速度方向(加速或减速) accel_direction = np.sign(vel_error) # 1 if vel_error > 0 else -1 # 计算所需加速度(使用最大加速度/减速度) required_accel = abs(vel_error) / self.dt target_accel = min(required_accel, self.max_accel) * accel_direction # 更新加速度(带平滑过渡) if abs(self.actual_acc) < abs(target_accel): self.actual_acc += accel_direction * self.max_jerk * self.dt self.actual_acc = np.clip(self.actual_acc,-self.max_accel,self.max_accel) else: self.actual_acc = target_accel # 更新速度 self.actual_vel += self.actual_acc * self.dt # 防止超调 if (vel_error > 0 and self.actual_vel > self.target_vel) or \ (vel_error < 0 and self.actual_vel < self.target_vel): self.actual_vel = self.target_vel self.actual_acc = 0.0 # else: # self.actual_vel = 0 # 更新 位置 arange_dec = self.actual_vel * self.dt self.actual_pos += arange_dec # return False, arange_dec, self.actual_vel # else: # # 更新 位置 # # arange_dec = self.actual_vel * self.dt # # self.actual_pos += arange_dec # return True, 0, 0 # if not hasattr(self, "overshoot"): # self.overshoot = 0 # if not hasattr(self, "jerk"): # self.jerk = 0 # self.time_log = np.append(self.time_log, current_time) # self.target_pos_log = np.append(self.target_pos_log, self.actual_pos) # self.target_pos) # self.actual_pos_log = np.append(self.actual_pos_log, self.actual_acc) # self.target_vel_log = np.append(self.target_vel_log, self.target_vel) # self.max_vel) # # self.target_vel_log = np.append(self.target_vel_log, self.dec) # self.actual_vel_log = np.append(self.actual_vel_log, self.actual_vel) # # self.actual_overshoot_log = np.append(self.actual_overshoot_log, self.adaptive_factor) # self.actual_overshoot_log = np.append(self.actual_overshoot_log, self.jerk) # self.actual_overshoot2_log = np.append(self.actual_overshoot2_log, self.overshoot) # yield 0 # arange_dec # return arange_dec def visualize(self): return """可视化位置和速度曲线""" plt.figure(figsize=(12, 8)) # 位置曲线 plt.subplot(3, 1, 1) # plt.plot(self.time_log, self.target_pos_log, 'r-', label='实际位置') # plt.plot(self.time_log, self.actual_pos_log, 'b-', label='实际位置') plt.plot(self.time_log, self.actual_overshoot_log, 'r-', label='衰减因子') plt.plot(self.time_log, self.actual_overshoot2_log, 'y-', label='过冲因子') plt.xlabel('时间 (s)') plt.ylabel('位置 (mm)') plt.title('位置跟踪曲线') plt.legend() plt.grid(True) plt.subplot(3, 1, 2) # plt.plot(self.time_log, self.target_vel_log, 'r-', label='目标速度') plt.plot(self.time_log, self.actual_pos_log, 'g-', label='实际加速度') # plt.plot(self.time_log, self.actual_overshoot_log, 'r-', label='过冲因子') plt.xlabel('时间 (s)') plt.ylabel('加速度 (mm/s2)') plt.title('加速度曲线') plt.legend() plt.grid(True) # 速度曲线 plt.subplot(3, 1, 3) plt.plot(self.time_log, self.target_vel_log, 'r-', label='目标速度') plt.plot(self.time_log, self.actual_vel_log, 'g-', label='实际速度') plt.xlabel('时间 (s)') plt.ylabel('速度 (mm/s)') plt.title('速度跟踪曲线') plt.legend() plt.grid(True) plt.tight_layout() plt.show() 但是这个使用了numpy 数组却比纯python 类型快
08-30
# Boids Simulation for Blender 4.5.3 - With Random Scale bl_info = { "name": "Boids Simulation (With Random Scale)", "author": "AI Assistant", "version": (2, 3), "blender": (4, 5, 3), "location": "View3D > Sidebar > Boids Tab", "description": "Flocking simulation with per-instance random scale.", "category": "Animation", } import bpy import numpy as np from scipy.spatial.distance import squareform, pdist from mathutils import Vector from bpy.props import FloatProperty, IntProperty, PointerProperty # 全局变量 boid_objects = [] # 实例对象列表 is_running = False # 是否实时运行模拟 sim = None # 当前模拟器实例 baked_frames = [] # 记录已烘焙的帧范围 [(start, end), ...] class BoidsSimulation: def __init__(self, N, template_obj, bounds, center_obj=None): self.N = N self.template_obj = template_obj self.width, self.height, self.depth = bounds self.center_obj = center_obj # 可选:控制体积位置的对象 # 初始化位置与速度 self.pos = np.random.uniform( low=[-self.width/2, -self.height/2, -self.depth/2], high=[self.width/2, self.height/2, self.depth/2], size=(N, 3) ) angles_xy = 2 * np.pi * np.random.rand(N) angles_z = np.pi * np.random.rand(N) - np.pi / 2 self.vel = np.array([ np.cos(angles_z) * np.cos(angles_xy), np.cos(angles_z) * np.sin(angles_xy), np.sin(angles_z) ]).T self.vel *= 1.0 # 可调参数 self.minDist = 3.0 self.maxDist = 8.0 self.separation_weight = 1.0 self.alignment_weight = 1.0 self.cohesion_weight = 1.0 self.max_speed = 0.5 self.noise_strength = 0.1 self.boundary_damping = 0.95 self.collision_distance = 1.5 self.scale_randomness = 0.0 # 默认不随机缩放 def get_center_offset(self): """返回当前中心对象的世界位置偏移""" if self.center_obj and self.center_obj.name in bpy.data.objects: return Vector(self.center_obj.location) return Vector((0, 0, 0)) def step(self): N = self.N pos = self.pos.copy() vel = self.vel.copy() # 获取当前中心偏移 center_offset = np.array(self.get_center_offset()) world_pos = pos + center_offset dist_matrix = squareform(pdist(world_pos)) sep_vel = np.zeros_like(vel) ali_vel = np.zeros_like(vel) coh_vel = np.zeros_like(vel) # Rule 1: Separation close_neighbors = dist_matrix < self.minDist np.fill_diagonal(close_neighbors, False) for i in range(N): neighbors = close_neighbors[i] if neighbors.any(): avg_world_pos = np.mean(world_pos[neighbors], axis=0) sep_vel[i] = world_pos[i] - avg_world_pos # Rule 2: Alignment align_mask = (dist_matrix < self.maxDist) & (dist_matrix >= self.minDist) np.fill_diagonal(align_mask, False) for i in range(N): neighbors = align_mask[i] if neighbors.any(): avg_vel = np.mean(vel[neighbors], axis=0) ali_vel[i] = avg_vel # Rule 3: Cohesion cohes_mask = (dist_matrix < self.maxDist) & (dist_matrix >= self.minDist) np.fill_diagonal(cohes_mask, False) for i in range(N): neighbors = cohes_mask[i] if neighbors.any(): center_world = np.mean(world_pos[neighbors], axis=0) coh_vel[i] = center_world - world_pos[i] def safe_normalize(v, eps=1e-6): norm = np.linalg.norm(v) return v / norm if norm > eps else np.zeros(3) total_delta = np.zeros_like(vel) if self.separation_weight > 0: sep_vel = np.array([safe_normalize(v) for v in sep_vel]) * self.separation_weight total_delta += sep_vel if self.alignment_weight > 0: ali_vel = np.array([safe_normalize(v) for v in ali_vel]) * self.alignment_weight total_delta += ali_vel if self.cohesion_weight > 0: coh_vel = np.array([safe_normalize(v) for v in coh_vel]) * self.cohesion_weight total_delta += coh_vel noise = (np.random.rand(N, 3) - 0.5) * self.noise_strength total_delta += noise vel += total_delta speeds = np.linalg.norm(vel, axis=1) too_fast = speeds > self.max_speed if too_fast.any(): vel[too_fast] = (vel[too_fast].T / speeds[too_fast] * self.max_speed).T pos += vel # 边界限制 current_center = self.get_center_offset() x_min, x_max = current_center.x - self.width / 2, current_center.x + self.width / 2 y_min, y_max = current_center.y - self.height / 2, current_center.y + self.height / 2 z_min, z_max = current_center.z - self.depth / 2, current_center.z + self.depth / 2 damping = self.boundary_damping x_low = pos[:, 0] + current_center.x < x_min x_high = pos[:, 0] + current_center.x > x_max vel[x_low | x_high, 0] *= -damping y_low = pos[:, 1] + current_center.y < y_min y_high = pos[:, 1] + current_center.y > y_max vel[y_low | y_high, 1] *= -damping z_low = pos[:, 2] + current_center.z < z_min z_high = pos[:, 2] + current_center.z > z_max vel[z_low | z_high, 2] *= -damping pos[:, 0] = np.clip(pos[:, 0], -self.width/2, self.width/2) pos[:, 1] = np.clip(pos[:, 1], -self.height/2, self.height/2) pos[:, 2] = np.clip(pos[:, 2], -self.depth/2, self.depth/2) self.pos = pos self.vel = vel def create_boid_collection(): coll_name = "Boids" if coll_name not in bpy.data.collections: coll = bpy.data.collections.new(coll_name) bpy.context.scene.collection.children.link(coll) return bpy.data.collections[coll_name] def initialize_simulation(): global sim, boid_objects scene = bpy.context.scene props = scene.boids_props template_obj = props.template_object if not template_obj or template_obj.type != 'MESH': raise Exception("请先选择一个有效的网格对象作为模板") count = max(1, props.count) width = props.sim_width height = props.sim_height depth = props.sim_depth clear_simulation() coll = create_boid_collection() mesh = template_obj.data # 预生成随机缩放值 scale_factor = props.random_scale_factor scales = 1.0 + (np.random.rand(count) - 0.5) * 2 * scale_factor # 范围: [1-scale_factor, 1+scale_factor] for i in range(count): obj_name = f"Boid_{i:03d}" obj = bpy.data.objects.new(obj_name, mesh) obj.location = (0, 0, 0) obj.rotation_mode = 'QUATERNION' obj.rotation_quaternion.identity() obj.scale = (scales[i], scales[i], scales[i]) # 均匀缩放 obj.data.materials.clear() for mat in template_obj.data.materials: obj.data.materials.append(mat) coll.objects.link(obj) boid_objects.append(obj) bounds = (width, height, depth) center_obj = props.center_object sim = BoidsSimulation(N=count, template_obj=template_obj, bounds=bounds, center_obj=center_obj) sim.scale_randomness = scale_factor # 存储用于调试或扩展 print(f"✅ 已创建 {count} 个 '{template_obj.name}' 的实例,带随机缩放 [1±{scale_factor:.2f}]") print(f" 缩放范围: {1-scale_factor:.2f} ~ {1+scale_factor:.2f}") def clear_simulation(): global boid_objects, sim for obj in boid_objects: if obj.name in bpy.data.objects: bpy.data.objects.remove(obj, do_unlink=True) boid_objects.clear() sim = None def update_boids(scene): if not is_running or sim is None: return # 同步 UI 参数 props = scene.boids_props sim.separation_weight = props.separation_weight sim.alignment_weight = props.alignment_weight sim.cohesion_weight = props.cohesion_weight sim.max_speed = props.max_speed sim.noise_strength = props.noise_strength sim.minDist = props.min_distance sim.maxDist = props.max_distance sim.collision_distance = props.collision_distance sim.center_obj = props.center_object sim.step() center_offset = sim.get_center_offset() for i, obj in enumerate(boid_objects): local_pos = sim.pos[i] world_pos = Vector(local_pos) + center_offset obj.location = world_pos vel_vec = Vector(sim.vel[i]) if vel_vec.length > 0.01: rot = vel_vec.to_track_quat('Z', 'Y') obj.rotation_quaternion = rot for area in bpy.context.screen.areas: if area.type == 'VIEW_3D': area.tag_redraw() # ------------------------------------------------------------------------ # Operators # ------------------------------------------------------------------------ class Boids_OT_Start(bpy.types.Operator): bl_idname = "boids.start" bl_label = "Start Simulation" bl_options = {'REGISTER'} def execute(self, context): global is_running try: initialize_simulation() is_running = True if update_boids not in bpy.app.handlers.frame_change_pre: bpy.app.handlers.frame_change_pre.append(update_boids) if not context.screen.is_animation_playing: bpy.ops.screen.animation_play() except Exception as e: self.report({'ERROR'}, str(e)) return {'CANCELLED'} return {'FINISHED'} class Boids_OT_Stop(bpy.types.Operator): bl_idname = "boids.stop" bl_label = "Stop Simulation" def execute(self, context): global is_running is_running = False return {'FINISHED'} class Boids_OT_Clear(bpy.types.Operator): bl_idname = "boids.clear" bl_label = "Clear Simulation" def execute(self, context): clear_simulation() global is_running is_running = False return {'FINISHED'} class Boids_OT_Bake(bpy.types.Operator): """将当前模拟结果烘焙为关键帧""" bl_idname = "boids.bake" bl_label = "Bake Simulation" def execute(self, context): scene = context.scene props = scene.boids_props start_frame = props.bake_start_frame end_frame = props.bake_end_frame if not sim: self.report({'ERROR'}, "请先点击 Start 初始化模拟") return {'CANCELLED'} old_frame = scene.frame_current temp_sim = BoidsSimulation( sim.N, sim.template_obj, (sim.width, sim.height, sim.depth), center_obj=props.center_object ) temp_sim.pos[:] = sim.pos.copy() temp_sim.vel[:] = sim.vel.copy() # 清除旧动画数据 for obj in boid_objects: if obj.animation_data and obj.animation_data.action: bpy.data.actions.remove(obj.animation_data.action) obj.animation_data_create() # 开始烘焙 for frame in range(start_frame, end_frame + 1): scene.frame_set(frame) temp_sim.separation_weight = props.separation_weight temp_sim.alignment_weight = props.alignment_weight temp_sim.cohesion_weight = props.cohesion_weight temp_sim.max_speed = props.max_speed temp_sim.noise_strength = props.noise_strength temp_sim.minDist = props.min_distance temp_sim.maxDist = props.max_distance temp_sim.collision_distance = props.collision_distance temp_sim.center_obj = props.center_object temp_sim.step() center_offset = temp_sim.get_center_offset() for i, obj in enumerate(boid_objects): world_pos = Vector(temp_sim.pos[i]) + center_offset obj.location = world_pos vel_vec = Vector(temp_sim.vel[i]) if vel_vec.length > 0.01: rot = vel_vec.to_track_quat('Z', 'Y') obj.rotation_quaternion = rot obj.keyframe_insert(data_path="location", frame=frame) obj.keyframe_insert(data_path="rotation_quaternion", frame=frame) baked_frames.append((start_frame, end_frame)) scene.frame_set(old_frame) self.report({'INFO'}, f"✅ 烘焙完成: 帧 {start_frame} 到 {end_frame}") return {'FINISHED'} class Boids_OT_ClearBake(bpy.types.Operator): bl_idname = "boids.clear_bake" bl_label = "Clear Bake Cache" def execute(self, context): for obj in boid_objects: if obj.animation_data and obj.animation_data.action: bpy.data.actions.remove(obj.animation_data.action) obj.animation_data_clear() baked_frames.clear() self.report({'INFO'}, "🗑️ 已清除所有烘焙关键帧") return {'FINISHED'} class Boids_OT_ResetParams(bpy.types.Operator): """重置所有参数为默认值""" bl_idname = "boids.reset_params" bl_label = "Reset Parameters" bl_description = "Reset all settings to their default values" def execute(self, context): props = context.scene.boids_props # 恢复所有属性到默认值 props.template_object = None props.center_object = None props.count = 50 props.sim_width = 20.0 props.sim_height = 20.0 props.sim_depth = 20.0 props.min_distance = 3.0 props.max_distance = 8.0 props.collision_distance = 1.5 props.separation_weight = 1.0 props.alignment_weight = 1.0 props.cohesion_weight = 1.0 props.max_speed = 0.5 props.noise_strength = 0.1 props.bake_start_frame = 1 props.bake_end_frame = 250 props.random_scale_factor = 0.0 # 新增:重置随机缩放 self.report({'INFO'}, "🔄 参数已重置为默认值") return {'FINISHED'} # ------------------------------------------------------------------------ # Panel # ------------------------------------------------------------------------ class Boids_PT_Panel(bpy.types.Panel): bl_label = "Boids Simulation" bl_idname = "VIEW3D_PT_boids" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "Boids" def draw(self, context): layout = self.layout props = context.scene.boids_props layout.label(text="Boid Template:") layout.prop(props, "template_object", text="") layout.separator() layout.label(text="Center Object (Optional):") layout.prop(props, "center_object", text="") layout.label(text="Leave empty for origin-centered volume.") layout.separator() layout.label(text="Simulation Volume:") row = layout.row(align=True) row.prop(props, "sim_width", text="Width") row = layout.row(align=True) row.prop(props, "sim_height", text="Height") row = layout.row(align=True) row.prop(props, "sim_depth", text="Depth") layout.separator() layout.prop(props, "count") layout.separator() layout.prop(props, "random_scale_factor", text="Random Scale Factor") # 新增:随机缩放 layout.label(text="Scale range: [1±factor]", icon='INFO') layout.separator() layout.prop(props, "min_distance", text="Separation Min Dist") layout.prop(props, "max_distance", text="Alignment/Cohesion Max Dist") layout.prop(props, "collision_distance", text="Collision Distance") layout.separator() layout.label(text="Behavior Weights:") layout.prop(props, "separation_weight", text="Separation") layout.prop(props, "alignment_weight", text="Alignment") layout.prop(props, "cohesion_weight", text="Cohesion") layout.prop(props, "max_speed") layout.prop(props, "noise_strength") layout.separator() layout.label(text="Baking Settings:") row = layout.row() row.prop(props, "bake_start_frame") row.prop(props, "bake_end_frame") layout.operator("boids.bake", icon='RENDER_STILL') layout.operator("boids.clear_bake", icon='X') layout.separator() row = layout.row() row.operator("boids.start", text="Start", icon='PLAY') row.operator("boids.stop", text="Stop", icon='PAUSE') layout.operator("boids.clear", text="Clear", icon='TRASH') layout.separator() layout.operator("boids.reset_params", text="Reset Parameters", icon='LOOP_BACK') layout.separator() status = "运行中" if is_running else "已停止" icon = 'RENDER_STILL' if is_running else 'CANCEL' layout.label(text=f"状态: {status}", icon=icon) # ------------------------------------------------------------------------ # Properties # ------------------------------------------------------------------------ class BoidsProperties(bpy.types.PropertyGroup): template_object: PointerProperty( name="模板对象", description="用作 boid 实例的 3D 对象", type=bpy.types.Object, poll=lambda self, obj: obj.type == 'MESH' ) center_object: PointerProperty( name="中心对象", description="此对象定义模拟区域的中心位置。移动它可带动整个 flock 区域。", type=bpy.types.Object ) count: IntProperty( name="数量", description="生成的 boid 数量", default=50, min=1, max=1000 ) sim_width: FloatProperty(name="宽度", default=20.0, min=1.0, max=100.0) sim_height: FloatProperty(name="高度", default=20.0, min=1.0, max=100.0) sim_depth: FloatProperty(name="深度", default=20.0, min=1.0, max=100.0) random_scale_factor: FloatProperty( name="随机缩放强度", description="每个实例的缩放将在 [1 - factor, 1 + factor] 范围内随机", default=0.0, min=0.0, max=1.0, precision=2, subtype='FACTOR' ) min_distance: FloatProperty( name="最小距离", default=3.0, min=0.1, max=20.0, options={'ANIMATABLE'} ) max_distance: FloatProperty( name="最大作用距离", default=8.0, min=0.1, max=30.0, options={'ANIMATABLE'} ) collision_distance: FloatProperty( name="碰撞距离", description="当两个 boid 距离小于此值时视为发生碰撞", default=1.5, min=0.0, max=10.0, options={'ANIMATABLE'} ) separation_weight: FloatProperty( name="分离权重", default=1.0, min=0.0, max=5.0, options={'ANIMATABLE'} ) alignment_weight: FloatProperty( name="对齐权重", default=1.0, min=0.0, max=5.0, options={'ANIMATABLE'} ) cohesion_weight: FloatProperty( name="内聚权重", default=1.0, min=0.0, max=5.0, options={'ANIMATABLE'} ) max_speed: FloatProperty( name="最大速度", default=0.5, min=0.01, max=5.0, options={'ANIMATABLE'} ) noise_strength: FloatProperty( name="噪声强度", default=0.1, min=0.0, max=1.0, options={'ANIMATABLE'} ) bake_start_frame: IntProperty( name="烘焙起始帧", default=1, min=1, max=10000 ) bake_end_frame: IntProperty( name="烘焙结束帧", default=250, min=1, max=10000 ) # ------------------------------------------------------------------------ # Registration # ------------------------------------------------------------------------ _classes = ( BoidsProperties, Boids_OT_Start, Boids_OT_Stop, Boids_OT_Clear, Boids_OT_Bake, Boids_OT_ClearBake, Boids_OT_ResetParams, Boids_PT_Panel, ) def register(): for cls in _classes: bpy.utils.register_class(cls) bpy.types.Scene.boids_props = PointerProperty(type=BoidsProperties) if update_boids not in bpy.app.handlers.frame_change_pre: bpy.app.handlers.frame_change_pre.append(update_boids) def unregister(): if update_boids in bpy.app.handlers.frame_change_pre: bpy.app.handlers.frame_change_pre.remove(update_boids) for cls in reversed(_classes): bpy.utils.unregister_class(cls) if hasattr(bpy.types.Scene, "boids_props"): del bpy.types.Scene.boids_props if __name__ == "__main__": register() 修改以上代码,修改随机参数值限定在010之间,给我完整无误的代码。
最新发布
11-14
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值