转 第十五章 3D 基础 (1)(as3.0)

3D图形与运动
本文介绍了如何在Flash中实现3D图形及运动效果,包括速度、加速度、摩擦力、反弹等,通过透视法计算物体在屏幕上的位置与大小。

  前面我们做的一切都是二维的(有时只有一维),但是已经可以做出非常酷的东东了。
现在,将它们带入到下一个等级。
     创建 3D  图形总是那么另人兴奋。新加入的这个维度似乎将物体真正地带入到了生活
中。如何在 Flash  中实现 3D  在无数的书籍和教学软件中都有介绍。但是我不打算跳过这
些内容,我们会很快地将所有基础的知识讲完。随后,将前面章节中讨论的运动效果放到三
维空间中。说得详细些,将给大家介绍速度,加速度,摩擦力,反弹,屏幕环绕,缓动,弹
性运动,坐标旋转以及碰撞检测。
     现在,首先要关注 sprite 影片在 3D  空间中运动,使用透视法计算影片在屏幕上的大
小和位置。当然,sprite 本身是平面的,我们看不到它的背面,侧面,顶面或
两章,我们将学习到点,线,图形和立体图形的 3D  建模。 

第三维度及透视法
    在 3D 背后最重要的理论就是超出 x  和 y  存在的另一个维度。这是表示深度的维度,
通常记为 z。
    Flash  没有内置的 z 维度,但是要想在 ActionScript 中创建它也不是件难事。实际上,
远没有我们前面章节中的内容那么复杂!


     首先,需要确定 z 最是朝哪个方向的:向内或向外。回忆一下第三章讨论的坐标系统,
它比普通的坐标系统在某些地方是相反的。y  轴向下,而非向上,角度则是以顺时针方向而
定的,而非逆时针方向。
    因此,当物体远离或接近我们的时候,是否应该让物体 z  轴上位置增加?没有必要去
比较哪个更正确。事实上,这个课题已经被讨论许久了,人们甚至为了描述这两种方法分别
给它们取了名字:左手系统和右手系统。
     伸出您的右手,让拇指与食指构成一个 L 形,然后将中指弯曲 90  度,每个手指都将
指向一个维度。现在,将您的食指指向 x  轴的正半轴,中指指向 y  轴的正半轴。在右手坐
标系中,拇指的指向就是 z  轴的正半轴方向。对于 Flash 而言,意味着物体远离观察者时
轴将增大,临近观察者时 z  轴将减小,如图 15-1  所示。 
 转 <wbr>第十五章 <wbr>3D <wbr>基础 <wbr>(1)(as3.0)

图 15-1  右手坐标系
     如果我们用左手来试的话,得到的结果则是相反的。如图 15-2  所示,左手坐标系。



转 <wbr>第十五章 <wbr>3D <wbr>基础 <wbr>(1)(as3.0)

图 15-2  左手坐标系
     下面我们使用右手坐标系为例(图 15-1)。没有理由说不能使用左手坐标系,只不过让
轴向内看起来比较好。在 Flash  中创建第三维度(z)的下一个步骤是如何计算模拟透视。

 

透视法
     透视法是指如何表述物体接近或远离我们时的方法。换句话讲,如何让物体看起来更近
或更远。一幅美术作品中可能有大量的表现透视的技巧,这里我们只关注两点:
■  当物体离得远时,会变小。
■  当物体远离时,它们会聚集到一个消失点上。
     大家肯定见过火车驶向地平线时的景象。当我们在 z  轴上移动物体时,需要做两件事:
■  增大或减小物体的比率。
■  让物体接近或远离消失点。
      在二维系统中,我们可以使用屏幕的 x  和 y  坐标作为物体的 x  和 y  坐标。只需要
一对一地映射过来即可。但是在 3D  系统中就行不通了,因为两个物体可以有相同的 x, y

坐标,由于它们的深度不同,会使它们在屏幕上有不同的位置。因此,在 3D  空间中移动
每个物体都需要知道它们各自的 x, y, z  坐标,这是屏幕坐标不能做到的。现在就要用到这
三个量来描述虚拟空间的一个位置。透视法将告诉我们应该将物体放到屏幕的什么位置。
 

透视公式
     让物体的距离更远(增加 z),基本思想是想让它缩放比率接近0,让它的 x, y  坐标集
中到消失点的 0,0  处。幸好,缩放的比率与汇集的比率相同。因此,我们只需要根据给定
的距离计算出这个比率,然后在这两个地方使用它即可。图 15-3  帮助大家解释这个概念。

转 <wbr>第十五章 <wbr>3D <wbr>基础 <wbr>(1)(as3.0)

图 15-3  从侧面观察透示图
     我们距离对象有一段距离。有一个观察点:眼睛。有一个成象面,可以想象成电脑的屏
幕。对象与成象面之间有一段距离,这就是 z  的值。最后,距离观察点到成象面还有一段
距离。最后这点最为重要。虽然这段距离不完全等同于摄象机的焦距,但是与它基本相似
因此我通常用变量 fl [焦距:focal length]  表示。下面是这个公式:
       scale = fl / (fl + z)
scale  值通常是介于 0.0 到 1.0  之间的,这就是缩放和汇聚到消失点上的比率。然而,当 z
变为负数时,fl + z  接近 0  而缩放比例接近无穷大。
     拿到这个 scale  的值能做些什么呢?假设在处理一个影片(或 Sprite  的子类),我们将
这个值赋给影片的 scaleX  和 scaleY。然后再用这个因数乘以物体的 x,y  坐标,就可以算
出物体在屏幕上的 x,y 的位置。
     看一个例子。通常情况下 fl  的值在 200  到 300  之间。我们选用 250 这个值。如果 z
等于 0  ——换句话讲,物体就在成象面上---  那么 scale 就等于  250 / (250 + 0)。结果等
于 1.0。这就是 scaleX  和 scaleY  的值(别忘了对于 scaleX  和 scaleY  而言, 1.0  就意味
着 100%)。让物体的 x,y  坐标乘以 1.0,返回的结果不变,因此物体在屏幕上的位置就等
于它自身的 x  和 y。

    现在将物体向外移让 z  等于 250。则让 scale  等于 250 / (250 + 250),scaleX 和 scaleY
等于 0.5。同样也改变了物体在屏幕上的位置。如果原来物体在屏幕上的位置是 200, 300 
么现在就应该是 100, 150。因此,它向着消失点移动了一半的距离。(事实上,屏幕上的位
置是相对于消失点的位置而定的,大家马上会看到)。
     现在,将 z  向外移动到 9750。scale  变成 250 / 10000, scaleX  和 scaleY  等于 0.025。
物体将变成一个小点儿,并且非常接近消失点。
 OK,理论够了。来看代码。
 

ActionScript 透视
     各位也许猜到了,我还要使用 Ball  类。当然,您也可以自由地选择自己喜欢物体,但
是我只专注于代码,将那些酷酷的图形留给大家去做。我们用鼠标和键盘作为交互。使用鼠

标控制小球的 x,y  坐标,方向键的上下键来控制 z  轴的前后方向。注意,因为变量 x,y 
由 ActionScript  持有的,因此我们将使用 xpos, ypos, zpos  代表 3D  坐标。
文档类 Perspective1.as  的代码如下:
package {
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.events.KeyboardEvent;
 import flash.ui.Keyboard;
  public class Perspective1 extends Sprite {
   private var ball:Ball;
   private var xpos:Number = 0;
   private var ypos:Number = 0;
   private var zpos:Number = 0;
   private var fl:Number = 250;
   private var vpX:Number = stage.stageWidth / 2;
   private var vpY:Number = stage.stageHeight / 2;
   public function Perspective1() {
   init();
  }
   private function init():void {
   ball = new Ball();
   addChild(ball);

addEventListener(Event.ENTER_FRAME, onEnterFrame);
   stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
  }
   private function onEnterFrame(event:Event):void {
     xpos = mouseX - vpX;
     ypos = mouseY - vpY;
     var scale:Number = fl / (fl + zpos);
     ball.scaleX = ball.scaleY = scale;
     ball.x = vpX + xpos * scale;
     ball.y = vpY + ypos * scale;
  }
   private function onKeyDown(event:KeyboardEvent):void {
     if (event.keyCode == Keyboard.UP) {
    zpos += 5;
     } else if (event.keyCode == Keyboard.DOWN) {
    zpos -= 5;
   }
  }
 }
}

首先创建变量  xpos, ypos, zpos, fl。然后创建一个消失点(vanishing point)vpX, vpY。
记住当物体向远处运动一段距离后,就会聚在 0, 0  点。如果不进行偏移,所有物体都会向
屏幕左上角汇集,这并不我们想要的结果。将 vpX, vpY  设置为舞台的中心点,作为消失点。
     接下来,在 onEnterFrame 中设置 xpos  和 ypos  为鼠标与消失点的偏移位置。换句话
讲,如果鼠标在中心点右面 200  像素,x  就等于 200。如果在中心点左面 200  像素的位置,
则等于 -200。
     然后添加 keyDown  事件的侦听,用于改变 zpos。如果方向键上被按下 zpos  增加,

如果方向键上被按下则减小。这将使小球向着观察者更近或更远的方向运动。
     最后,使用刚刚介绍过的公式计算 scale,设置小球的位置与大小。注意小球在屏幕上
的位置 x,y 是根据消失点计算的,还要加上 xpos, ypos  与 scale  的乘积。因此,当 scale 变
得很小时,小球将汇集到消失点上。
     测试一下影片,开始看起来像一个简单的鼠标拖拽。这是因为 zpos  等于 0,scale 
于 1.0。所以注意不到透视的存在。当按下方向键上时,小球向内滑入一段距离,如图 15-4
所示。现在当我们移动鼠标时,小球也会随之移动,但是移动的距离很小,产生了视差效应。

转 <wbr>第十五章 <wbr>3D <wbr>基础 <wbr>(1)(as3.0)

图 15-4 ActionScript 透视
     大家也许注意到了,如果长期按住方向键下,小球会变得非常大。这是对的。如果拿起
一小块石子放到眼前,它就会像一块巨石一样大。如果继续按住方向键下,它将变成无限大,
然后又收缩回去,但是这时整个小球已经颠倒或反转过来了。小球跑到了观察点的后面。因
此,如果眼睛可以看到身背后的东西,我猜这一定是我们所看到的。
     用数字解释一下,当 zpos  等于  –fl  时,公式从  scale = fl / (fl + zpos) 变为  scale = fl /
0。在许多语言中,除以 0  会报错。在 Flash 中,将得到一个无限大的值。如果再将 zpos 减
小,那么就是用 fl  除以一个负数。scale  变为负数,这就是为什么小球会颠倒并反向运动
的原因。
     学会了吗?解决方法只需在小球在超过某一点时将其设置为不可见的。如果 zpos  小于
或等于  –fl,会出现问题,因此可以判断一下这个条件,并在下面这个 Perspective2.as 
的 enterFrame  函数中进行处理(其余部分与 Perspective1.as 完全相同):

private function onEnterFrame(event:Event):void {
  if (zpos > -fl) {
   xpos = mouseX - vpX;
    ypos = mouseY - vpY;
   var scale:Number = fl / (fl + zpos);
   ball.scaleX = ball.scaleY = scale;
   ball.x = vpX + xpos * scale;
   ball.y = vpY + ypos * scale;
  ball.visible = true;
  } else {
  ball.visible = false;
 }
}

     注意,如果小球不可见,我们就不必考虑缩放和位置问题了。同样还要注意如果小球处
于可见的范围,就要确保它是可见的。虽然可能略些多余的设置,但这是必要的。
     好的,现在我们已经学习了 3D  基础的框架。不是很痛苦吧?一定要测试一下这个影
片,能够很好地掌握它。试改变 fl 的值,观察不同的效果。这就相当于在改变照相机的镜
头。较高的 fl  值就像一个长焦镜头,给我们一个较小的观察空间,以及较少的可见的透视。
较小的 fl  值将给我们一个广角镜头,形成非常广阔的透视。
     本章剩下的部分都是前面章节中介绍过的不同的运动效果,只不过这次是三维的

速度与加速度

实现 3D  的速度与加速度超级简单。对于 2D  而言,我们用 vx  和 vy  变量表示两个
轴的速度。现在只需要再加入 vz  表示第三个轴即可。同样,如果有 ax 和 ay  作为加速度,
那么再添加一个 az  变量即可。
     我们可以将最后一个例子改为小行星太空船这样的游戏,不过是 3D  版的。将它变为
全键盘控制的。方向键可以提供 x,y 轴上的推进,再加入一对儿键 Shift 和 Ctrl  用于 z 
上的推进。
     以下是代码(同样可在 Velocity3D.as  中找到):
package {
 import flash.display.Sprite;
 import flash.events.Event;
 import flash.events.KeyboardEvent;
 import flash.ui.Keyboard;
  public class Velocity3D extends Sprite {
   private var ball:Ball;
   private var xpos:Number = 0;
   private var ypos:Number = 0;
   private var zpos:Number = 0;
   private var vx:Number = 0;
   private var vy:Number = 0;
   private var vz:Number = 0;
   private var friction:Number = .98;
   private var fl:Number = 250;
   private var vpX:Number = stage.stageWidth / 2;
   private var vpY:Number = stage.stageHeight / 2;
   public function Velocity3D() {
   init();
  }

private function init():void {
 ball = new Ball();
 addChild(ball);
 addEventListener(Event.ENTER_FRAME, onEnterFrame);
 stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}
private function onEnterFrame(event:Event):void {
 xpos += vx;
 ypos += vy;
 zpos += vz;
 vx *= friction;
 vy *= friction;
 vz *= friction;
  if (zpos > -fl) {
   var scale:Number = fl / (fl + zpos);
   ball.scaleX = ball.scaleY = scale;
   ball.x = vpX + xpos * scale;
   ball.y = vpY + ypos * scale;
  ball.visible = true;
 } else {
  ball.visible = false;
 }

}
   private function onKeyDown(event:KeyboardEvent):void {
   switch (event.keyCode) {
    case Keyboard.UP :
     vy -= 1;
     break;
    case Keyboard.DOWN :
     vy += 1;
     break;
    case Keyboard.LEFT :
     vx -= 1;
     break;
    case Keyboard.RIGHT :
     vx += 1;
     break;
    case Keyboard.SHIFT :
     vz += 1;
     break;
    case Keyboard.CONTROL :
     vz -= 1;
     break;
    default :
     break;
   }

 }
 }
}
     我们所要做的就是为每个轴加入速度和摩擦力。当六个键中有一个被按下,将会对速度
进行适当的增加或减少(记住加速度改变速度)。然后将速度加到每个轴上,最后计算摩擦
力。现在我们就有了带有加速度,速度和摩擦力的一个 3D  物体。哇,真是一举多得。说
过这很简单。
 

反弹
     本节我们将讨论平面反弹的问题--换句话讲,是与 x, y, z  轴充分结合的反弹,与 2D
的屏幕边界反弹相似。
 
 
 
单物体反弹
    3D  反弹,同样需要判断物体何时超出了边界,然后将物体调整到边界上,把相应轴上
的速度反转。3D  反弹唯一的不同之处在于如何确定边界。在 2D  中,一般都是用舞台的坐
标或其它一些可见的矩形区域。在 3D  中,就不那么简单了。这里没有真正的可见边界的
概念,除非在三维空间中绘制一个。我们将在下一章学习三维空间中的绘制,因此现在将在
不可见的随意放置的墙壁上进行反弹。
     我们设置的边界和以前相同,只不过现在要把它们放到三维空间中,也就意味着可以是
正的也可以是负的。还可以选择在 z  轴上设置边界。边界大概是这样:
private var top:Number = -250;

private var bottom:Number = 250;
private var left:Number = -250;
private var right:Number = 250;
private var front:Number = 250;
private var back:Number = -250;
     接下来,确定物体的新位置,需要判断是否所与这六个边界产生了碰撞。别忘了我们是
用物体一半的宽度来判断碰撞的,而这个值已经存在了 Ball  类名为 radius  的变量中。以
下是全部 3D  反弹的代码(可见 Bounce3D.as):
package {
 import flash.display.Sprite;
 import flash.events.Event;
  public class Bounce3D extends Sprite {
   private var ball:Ball;
   private var xpos:Number = 0;
   private var ypos:Number = 0;
   private var zpos:Number = 0;
   private var vx:Number = Math.random() * 10 - 5;
   private var vy:Number = Math.random() * 10 - 5;
   private var vz:Number = Math.random() * 10 - 5;
   private var fl:Number = 250;

private var vpX:Number = stage.stageWidth / 2;
   private var vpY:Number = stage.stageHeight / 2;
   private var top:Number = -100;
   private var bottom:Number = 100;
   private var left:Number = -100;
   private var right:Number = 100;
   private var front:Number = 100;
   private var back:Number = -100;
   public function Bounce3D() {
   init();
  }
   private function init():void {
   ball = new Ball(15);
   addChild(ball);
   addEventListener(Event.ENTER_FRAME, onEnterFrame);
  }
   private function onEnterFrame(event:Event):void {
   xpos += vx;
   ypos += vy;
   zpos += vz;
     var radius:Number = ball.radius;
     if (xpos + radius > right) {
      xpos = right - radius;
    vx *= -1;

} else if (xpos - radius < left) {
      xpos = left + radius;
    vx *= -1;
   }
     if (ypos + radius > bottom) { 
      ypos = bottom - radius;
    vy *= -1;
     } else if (ypos - radius < top) {
    ypos = top + radius;
    vy *= -1;
   }
     if (zpos + radius > front) {
       zpos = front - radius;
    vz *= -1;
     } else if (zpos - radius < back) {
    zpos = back + radius;
    vz *= -1;
   }
     if (zpos > -fl) {
      var scale:Number = fl / (fl + zpos);
      ball.scaleX = ball.scaleY = scale;
      ball.x = vpX + xpos * scale;
      ball.y = vpY + ypos * scale;
    ball.visible = true;
   } else {
    ball.visible = false;
   }
  }

 }
}
     注意,我删掉了所有按键处理的部分,只让小球以随机的速度在每个轴上运动。现在可
以看到小球按照我们旨意进行反弹,但是谁也说不上来反弹在什么东西上——正如我所说
的,这些是任意放置不可见的边界。

 

多物体反弹
     让更多的物体充满整个空间也是对我们看出这些墙壁会有些帮助。为了完成这个目的,
需要很多 Ball  类的实例。每个实例都要有自己的 xpos, ypos, zpos 以及每个轴的速度。为
了让主类(main class)的结构清晰,下面创建了一个新的类 Ball3D,来看一下:
package {
 import flash.display.Sprite;
  public class Ball3D extends Sprite {
   public var radius:Number;
   private var color:uint;
   public var xpos:Number = 0;
   public var ypos:Number = 0;
   public var zpos:Number = 0;
   public var vx:Number = 0;
   public var vy:Number = 0;
   public var vz:Number = 0;
   public var mass:Number = 1;
   public function Ball3D(radius:Number=40, color:uint=0xff0000) {
   this.radius = radius;

 this.color = color;
   init();
  }
   public function init():void {
   graphics.beginFill(color);
   graphics.drawCircle(0, 0, radius);
   graphics.endFill();
  }
 }
}
     我们看到,这里所做的就是加入了每个轴的位置和速度的属性。同样,将类中的属性设
置为 public 实在不是一个好的面向对象程序设计的习惯,但是现在我们只是为了能够简单
地说明公式才这么做的。在 MultiBounce3D.as 中,创建了 50  个新类的实例。每个实例都
有自己的  xpos, ypos, zpos, vx,vy, vz。 onEnterFrame 方法循环获得每个 Ball3D  的引用,然
后将它们传给 move  方法。这个方法与最初的 onEnterFrame  完成的功能相同。代码如下(可
在 MultiBounce3D.as  中找到):

package {
 import flash.display.Sprite;
 import flash.events.Event;
  public class MultiBounce3D extends Sprite {
   private var balls:Array;
   private var numBalls:uint = 50;
   private var fl:Number = 250;
   private var vpX:Number = stage.stageWidth / 2;
   private var vpY:Number = stage.stageHeight / 2;
   private var top:Number = -100;
   private var bottom:Number = 100;
   private var left:Number = -100;
   private var right:Number = 100;
   private var front:Number = 100;
   private var back:Number = -100;
   public function MultiBounce3D() {
   init();
  }

 private function init():void {
   balls = new Array();
     for (var i:uint = 0; i < numBalls; i++) {
      var ball:Ball3D = new Ball3D(15);
    balls.push(ball);
      ball.vx = Math.random() * 10 - 5;
      ball.vy = Math.random() * 10 - 5;
      ball.vz = Math.random() * 10 - 5;
    addChild(ball);
   }
   addEventListener(Event.ENTER_FRAME, onEnterFrame);
  }
   private function onEnterFrame(event:Event):void {
     for (var i:uint = 0; i < numBalls; i++) {
    var ball:Ball3D = balls[i];

   move(ball);
   }
  }
   private function move(ball:Ball3D):void {
     var radius:Number = ball.radius;
   ball.xpos += ball.vx;
   ball.ypos += ball.vy;
   ball.zpos += ball.vz;
     if (ball.xpos + radius > right) {
      ball.xpos = right - radius;
    ball.vx *= -1;
     } else if (ball.xpos - radius < left) {
      ball.xpos = left + radius;
    ball.vx *= -1;
   }
     if (ball.ypos + radius > bottom) {
      ball.ypos = bottom - radius;
    ball.vy *= -1;
     } else if (ball.ypos - radius < top) {
      ball.ypos = top + radius;
    ball.vy *= -1;

 }
     if (ball.zpos + radius > front) {
      ball.zpos = front - radius;
    ball.vz *= -1;
     } else if (ball.zpos - radius < back) {
    ball.zpos = back + radius;
    ball.vz *= -1;
   }
     if (ball.zpos > -fl) {
    var scale:Number = fl / (fl + ball.zpos);
      ball.scaleX = ball.scaleY = scale;
      ball.x = vpX + ball.xpos * scale;
      ball.y = vpY + ball.ypos * scale;
    ball.visible = true;
   } else {
    ball.visible = false;
   }
  }
 }
}

运行这个文件后,可以看到小球将六个边界内的大部空间都填满了,如图 15-5  所示,
这样我们就可以看出这个空间的形状了。
转 <wbr>第十五章 <wbr>3D <wbr>基础 <wbr>(1)(as3.0)

图 15-5 3D 小球反弹 


 

(如果要转载请注明出处http://blog.sina.com.cn/jooi,谢谢)

import pandas as pd import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.interpolate import griddata from matplotlib.colors import LinearSegmentedColormap, Normalize import os from matplotlib.colorbar import ColorbarBase def generate_multi_fsim_surfaces(): # 1. 数据加载部分 print("从Excel加载原始数据...") try: script_dir = os.path.dirname(os.path.abspath(__file__)) excel_path = os.path.join(script_dir, &#39;fsim_data.xlsx&#39;) df = pd.read_excel(excel_path) # 验证必要列存在 required_columns = [&#39;BOW&#39;, &#39;WAVE&#39;] if not all(col in df.columns for col in required_columns): missing = [col for col in required_columns if col not in df.columns] raise ValueError(f"Excel文件缺少必要的列: {missing}") # 计算BOW列的最小值和最大值 bow_min = df[&#39;BOW&#39;].min() bow_max = df[&#39;BOW&#39;].max() # 添加10%的边距 bow_range = bow_max - bow_min x_min = max(0.1, bow_min - bow_range * 0.1) # 确保最小值不小于0.1 x_max = bow_max + bow_range * 0.1 print(f"BOW数据范围: {bow_min:.2f} 到 {bow_max:.2f}") print(f"设置X轴范围: {x_min:.2f} 到 {x_max:.2f}") # 定义所有可能的曲面描述信息 column_descriptions = { &#39;Fsim_E1&#39;: &#39;0.5mm Edge deformation&#39;, &#39;Fsim_E2&#39;: &#39;1.0mm Edge deformation&#39;, &#39;Fsim_E3&#39;: &#39;1.5mm Edge deformation&#39;, &#39;Fsim_E4&#39;: &#39;1.0mm Edge deformation&#39;, &#39;Fsim_E5&#39;: &#39;2.5mm Edge deformation&#39;, &#39;Fsim_E6&#39;: &#39;3.0mm Edge deformation&#39;, &#39;Fsim_DP&#39;: &#39;recommended range&#39; } # 找出所有Fsim列并验证描述信息 fsim_columns = [col for col in df.columns if col.startswith(&#39;Fsim&#39;)] # 检查是否有未定义的列并添加默认描述 missing_descriptions = [col for col in fsim_columns if col not in column_descriptions] for col in missing_descriptions: column_descriptions[col] = f&#39;custom Fsim metric ({col})&#39; print(f"警告: 为未定义的列添加默认描述: {col} -> {column_descriptions[col]}") if not fsim_columns: raise ValueError("未找到任何Fsim列") print(f"找到 {len(fsim_columns)} 个Fsim列: {&#39;, &#39;.join(fsim_columns)}") points = df[[&#39;BOW&#39;, &#39;WAVE&#39;]].values fsim_values = {col: df[col].values for col in fsim_columns} # 计算全局Fsim范围 all_fsim_min = min(df[col].min() for col in fsim_columns) all_fsim_max = max(df[col].max() for col in fsim_columns) z_padding = (all_fsim_max - all_fsim_min) * 0.1 z_min = all_fsim_min - z_padding z_max = all_fsim_max + z_padding print(f"全局Fsim范围: {all_fsim_min:.4f} 到 {all_fsim_max:.4f}") print(f"设置Z轴范围: {z_min:.4f} 到 {z_max:.4f}") except FileNotFoundError: print(f"错误: 找不到数据文件 {excel_path}") print("请确保fsim_data.xlsx文件位于脚本同一目录下") return except Exception as e: print(f"加载数据时出错: {str(e)}") return # 2. 创建高分辨率网格 print("创建高分辨率网格...") # 使用动态计算的x轴范围 x_fine = np.linspace(x_min, x_max, 150) # 使用动态范围 y_fine = np.linspace(0.1, 1.0, 100) X_fine, Y_fine = np.meshgrid(x_fine, y_fine) # 3. 创建3D图表 print("创建3D图表...") fig = plt.figure(figsize=(18, 14)) ax = fig.add_axes([0.1, 0.07, 0.7, 0.83], projection=&#39;3d&#39;) # 4. 定义全局颜色映射 print("创建颜色映射...") global_cmap = LinearSegmentedColormap.from_list( "global_cmap", [(0.1, 0.2, 0.8), (0.2, 0.8, 0.4), (1.0, 0.9, 0.1)], N=512 ) global_norm = Normalize(vmin=all_fsim_min, vmax=all_fsim_max) alphas = [0.65, 0.55, 0.45, 0.35, 0.25, 0.15] # 5. 曲面绘制和标签添加 print(f"开始处理 {len(fsim_columns)} 个曲面并添加标签...") # 查找固定(BOW=1, WAVE=1)的网格索引 bow_target = 1.0 wave_target = 1.0 # 如果固定不在范围内,使用平均值 if bow_target < x_min or bow_target > x_max: bow_target = (x_min + x_max) / 2 print(f"警告: BOW固定超出范围,调整为中值: {bow_target:.2f}") bow_idx = np.abs(x_fine - bow_target).argmin() wave_idx = np.abs(y_fine - wave_target).argmin() # 预计算所有曲面在固定的Z值 fixed_point_z = {} for col_name in fsim_columns: Z_fine = griddata(points, fsim_values[col_name], (X_fine, Y_fine), method=&#39;cubic&#39;) fixed_point_z[col_name] = Z_fine[wave_idx, bow_idx] # 按固定Z值降序排列(从高到低)- 确保 Fsim_DP 最后绘制 fsim_columns_without_dp = [col for col in fsim_columns if col != &#39;Fsim_DP&#39;] sorted_columns = sorted(fsim_columns_without_dp, key=lambda col: fixed_point_z[col], reverse=True) # 如果有 Fsim_DP,则添加到列表末尾以确保最后绘制(最上层) if &#39;Fsim_DP&#39; in fsim_columns: sorted_columns.append(&#39;Fsim_DP&#39;) # 记录标签位置和颜色 label_positions = [] label_colors = {} # 计算最小间距防止标签重叠 min_spacing = 0.035 base_offset = 0.015 # 第一遍:确定所有标签位置 last_label_z = float(&#39;-inf&#39;) label_z_values = {} max_label_z = float(&#39;-inf&#39;) for col_name in sorted_columns: base_z = fixed_point_z[col_name] label_z = base_z + base_offset # 防止标签重叠 if label_z <= last_label_z + min_spacing: label_z = last_label_z + min_spacing last_label_z = label_z label_z_values[col_name] = label_z if label_z > max_label_z: max_label_z = label_z # 第二遍:绘制曲面和标签 for i, col_name in enumerate(sorted_columns): values = fsim_values[col_name] print(f"正在处理列: {col_name} ({i+1}/{len(sorted_columns)})") # 执行插值 print(" 执行插值...") Z_fine = griddata(points, values, (X_fine, Y_fine), method=&#39;cubic&#39;) # 曲面着色 - 为 Fsim_DP 使用单一红色 if col_name == &#39;Fsim_DP&#39;: facecolors = np.zeros((Z_fine.shape[0]-1, Z_fine.shape[1]-1, 4)) facecolors[:, :, 0] = 1.0 # R facecolors[:, :, 1] = 0.0 # G facecolors[:, :, 2] = 0.0 # B else: # 常规着色方式 Z_top_left = Z_fine[:-1, :-1] Z_top_right = Z_fine[:-1, 1:] Z_bottom_left = Z_fine[1:, :-1] Z_bottom_right = Z_fine[1:, 1:] Z_midpoints = (Z_top_left + Z_top_right + Z_bottom_left + Z_bottom_right) / 4.0 facecolors = global_cmap(global_norm(Z_midpoints)) # 设置透明度 alpha_idx = min(i, len(alphas)-1) alpha = alphas[alpha_idx] facecolors[:, :, 3] = alpha print(f" 渲染曲面 (透明度: {alpha:.2f})...") # 优化渲染密度 stride_x = 1 stride_y = 1 if len(x_fine) > 100: render_density = max(1, int(len(x_fine)/75)) stride_x = render_density stride_y = max(1, int(render_density/1.5)) # 绘制曲面 surf = ax.plot_surface( X_fine[::stride_y, ::stride_x], Y_fine[::stride_y, ::stride_x], Z_fine[::stride_y, ::stride_x], facecolors=facecolors[::stride_y, ::stride_x], shade=False, rcount=min(300, len(y_fine)), ccount=min(300, len(x_fine)), alpha=alpha, antialiased=True, edgecolor=&#39;black&#39;, linewidth=0.1, zorder=len(fsim_columns)-i ) # 添加曲面标签 print(" 添加曲面标签...") label_x = X_fine[wave_idx, bow_idx] label_y = Y_fine[wave_idx, bow_idx] base_z = fixed_point_z[col_name] # 为 Fsim_DP 使用红色标签,其他使用基于值的颜色 if col_name == &#39;Fsim_DP&#39;: text_color = (1.0, 0.0, 0.0) # 纯红色 else: normalized_value = global_norm(base_z) text_color = global_cmap(normalized_value)[:3] # RGB颜色 # 存储标签颜色 label_colors[col_name] = text_color # 使用计算的标签高度 label_z = label_z_values[col_name] # 添加标签 ax.text( label_x, label_y, label_z, " " + col_name, fontsize=11, color=text_color, fontweight=&#39;bold&#39;, ha=&#39;left&#39;, va=&#39;center&#39;, zorder=100 ) label_positions.append((label_x, label_y, label_z)) # 6. 调整Z轴上限以容纳标签 if max_label_z > z_max: print(f"调整Z轴上限以容纳标签: 原上限 {z_max:.4f}, 新上限 {max_label_z + 0.03:.4f}") z_max = max_label_z + 0.03 # 7. 添加全局注释(确保位置合适) print("添加全局注释...") annotation_x = 0.90 # 右侧位置 annotation_y = 0.95 # 顶部位置,避免遮挡 # 添加带颜色的注释文本 for i, col in enumerate(fsim_columns): # 使用预定义的描述信息 description = column_descriptions[col] text = f"{col}: {description}" text_y = annotation_y - i * 0.025 # 从上到下排列 # 使用对应的标签颜色 color = label_colors.get(col, &#39;darkblue&#39;) # 添加注释文本(无背景框) plt.figtext( annotation_x, text_y, text, fontsize=12, color=color, ha=&#39;right&#39;, va=&#39;top&#39;, # 顶部对齐 zorder=100, fontweight=&#39;bold&#39; ) # 8. 坐标轴设置 ax.set_xlabel(&#39;BOW ()&#39;, fontsize=15, labelpad=25) ax.set_ylabel(&#39;WAVE ()&#39;, fontsize=15, labelpad=25) ax.set_zlabel(&#39;Fsim&#39;, fontsize=15, labelpad=25) title = ax.set_title( f&#39;Multi-Fsim Analysis ({len(fsim_columns)} Surfaces)&#39;, fontsize=20, pad=15, y=0.98 ) # 自动计算合适的x轴刻度 num_ticks = min(6, max(3, int((x_max - x_min) / 0.5) + 1)) ax.set_xticks(np.linspace(x_min, x_max, num_ticks)) ax.set_yticks([0.2, 0.4, 0.6, 0.8, 1.0]) z_step = 0.05 z_min_rounded = np.floor(z_min / z_step) * z_step z_max_rounded = np.ceil(z_max / z_step) * z_step z_ticks = np.arange(z_min_rounded, z_max_rounded + z_step/2, z_step) if len(z_ticks) > 10: step_idx = max(1, int(len(z_ticks) / 5)) z_ticks = z_ticks[::step_idx] ax.set_zticks(z_ticks) ax.set_zticklabels([f"{z:.2f}" for z in z_ticks]) ax.tick_params(axis=&#39;both&#39;, which=&#39;major&#39;, labelsize=11, pad=10) # 9. 网格和平面设置 print("添加背景网格线...") grid_params = { &#39;visible&#39;: True, &#39;linestyle&#39;: &#39;--&#39;, &#39;linewidth&#39;: 0.5, &#39;alpha&#39;: 0.2, &#39;color&#39;: &#39;gray&#39; } ax.xaxis._axinfo["grid"].update(grid_params) ax.yaxis._axinfo["grid"].update(grid_params) ax.zaxis.axinfo["grid"].update(grid_params) ax.xaxis.pane.fill = False ax.yaxis.pane.fill = False ax.zaxis.pane.fill = False ax.xaxis.pane.set_edgecolor(&#39;lightgray&#39;) ax.yaxis.pane.set_edgecolor(&#39;lightgray&#39;) ax.zaxis.pane.set_edgecolor(&#39;lightgray&#39;) ax.xaxis.pane.set_alpha(0.05) ax.yaxis.pane.set_alpha(0.05) ax.zaxis.pane.set_alpha(0.05) # 10. 视图设置 print("配置轴测图投影...") ax.set_proj_type(&#39;ortho&#39;) ax.view_init(elev=32, azim=42) ax.set_box_aspect(aspect=(1, 1, 1)) # 关键修改:设置视图范围(移除了额外的固定边距) ax.set_xlim(x_min, x_max) # 直接使用动态计算的范围 ax.set_ylim(0.0, 1.1) ax.set_zlim(z_min, z_max) # 11. 添加全局颜色条 print("添加全局Fsim颜色条...") cax = fig.add_axes([0.85, 0.25, 0.02, 0.5]) cbar = ColorbarBase( cax, cmap=global_cmap, norm=global_norm, orientation=&#39;vertical&#39;, label=&#39;Fsim Value&#39; ) cbar.set_label(&#39;Fsim Value&#39;, fontsize=12, labelpad=15) cbar.ax.tick_params(labelsize=10) # 12. 保存和显示 print("保存多曲面轴测图...") plt.savefig(&#39;multi_fsim_surfaces.png&#39;, dpi=350, bbox_inches=&#39;tight&#39;) print("渲染完成,显示图表...") plt.tight_layout(pad=3.0) plt.show() if __import pandas as pd import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.interpolate import griddata from matplotlib.colors import LinearSegmentedColormap, Normalize import os from matplotlib.colorbar import ColorbarBase def generate_multi_fsim_surfaces(): # 1. 数据加载部分 print("从Excel加载原始数据...") try: script_dir = os.path.dirname(os.path.abspath(__file__)) excel_path = os.path.join(script_dir, &#39;fsim_data.xlsx&#39;) df = pd.read_excel(excel_path) # 验证必要列存在 required_columns = [&#39;BOW&#39;, &#39;WAVE&#39;] if not all(col in df.columns for col in required_columns): missing = [col for col in required_columns if col not in df.columns] raise ValueError(f"Excel文件缺少必要的列: {missing}") # 计算BOW列的最小值和最大值 bow_min = df[&#39;BOW&#39;].min() bow_max = df[&#39;BOW&#39;].max() # 添加10%的边距 bow_range = bow_max - bow_min x_min = max(0.1, bow_min - bow_range * 0.1) # 确保最小值不小于0.1 x_max = bow_max + bow_range * 0.1 print(f"BOW数据范围: {bow_min:.2f} 到 {bow_max:.2f}") print(f"设置X轴范围: {x_min:.2f} 到 {x_max:.2f}") # 定义所有可能的曲面描述信息 column_descriptions = { &#39;Fsim_E1&#39;: &#39;0.5mm Edge deformation&#39;, &#39;Fsim_E2&#39;: &#39;1.0mm Edge deformation&#39;, &#39;Fsim_E3&#39;: &#39;1.5mm Edge deformation&#39;, &#39;Fsim_E4&#39;: &#39;1.0mm Edge deformation&#39;, &#39;Fsim_E5&#39;: &#39;2.5mm Edge deformation&#39;, &#39;Fsim_E6&#39;: &#39;3.0mm Edge deformation&#39;, &#39;Fsim_DP&#39;: &#39;recommended range&#39; } # 找出所有Fsim列并验证描述信息 fsim_columns = [col for col in df.columns if col.startswith(&#39;Fsim&#39;)] # 检查是否有未定义的列并添加默认描述 missing_descriptions = [col for col in fsim_columns if col not in column_descriptions] for col in missing_descriptions: column_descriptions[col] = f&#39;custom Fsim metric ({col})&#39; print(f"警告: 为未定义的列添加默认描述: {col} -> {column_descriptions[col]}") if not fsim_columns: raise ValueError("未找到任何Fsim列") print(f"找到 {len(fsim_columns)} 个Fsim列: {&#39;, &#39;.join(fsim_columns)}") points = df[[&#39;BOW&#39;, &#39;WAVE&#39;]].values fsim_values = {col: df[col].values for col in fsim_columns} # 计算全局Fsim范围 all_fsim_min = min(df[col].min() for col in fsim_columns) all_fsim_max = max(df[col].max() for col in fsim_columns) z_padding = (all_fsim_max - all_fsim_min) * 0.1 z_min = all_fsim_min - z_padding z_max = all_fsim_max + z_padding print(f"全局Fsim范围: {all_fsim_min:.4f} 到 {all_fsim_max:.4f}") print(f"设置Z轴范围: {z_min:.4f} 到 {z_max:.4f}") except FileNotFoundError: print(f"错误: 找不到数据文件 {excel_path}") print("请确保fsim_data.xlsx文件位于脚本同一目录下") return except Exception as e: print(f"加载数据时出错: {str(e)}") return # 2. 创建高分辨率网格 print("创建高分辨率网格...") # 使用动态计算的x轴范围 x_fine = np.linspace(x_min, x_max, 150) # 使用动态范围 y_fine = np.linspace(0.1, 1.0, 100) X_fine, Y_fine = np.meshgrid(x_fine, y_fine) # 3. 创建3D图表 print("创建3D图表...") fig = plt.figure(figsize=(18, 14)) ax = fig.add_axes([0.1, 0.07, 0.7, 0.83], projection=&#39;3d&#39;) # 4. 定义全局颜色映射 print("创建颜色映射...") global_cmap = LinearSegmentedColormap.from_list( "global_cmap", [(0.1, 0.2, 0.8), (0.2, 0.8, 0.4), (1.0, 0.9, 0.1)], N=512 ) global_norm = Normalize(vmin=all_fsim_min, vmax=all_fsim_max) alphas = [0.65, 0.55, 0.45, 0.35, 0.25, 0.15] # 5. 曲面绘制和标签添加 print(f"开始处理 {len(fsim_columns)} 个曲面并添加标签...") # 查找固定(BOW=1, WAVE=1)的网格索引 bow_target = 1.0 wave_target = 1.0 # 如果固定不在范围内,使用平均值 if bow_target < x_min or bow_target > x_max: bow_target = (x_min + x_max) / 2 print(f"警告: BOW固定超出范围,调整为中值: {bow_target:.2f}") bow_idx = np.abs(x_fine - bow_target).argmin() wave_idx = np.abs(y_fine - wave_target).argmin() # 预计算所有曲面在固定的Z值 fixed_point_z = {} for col_name in fsim_columns: Z_fine = griddata(points, fsim_values[col_name], (X_fine, Y_fine), method=&#39;cubic&#39;) fixed_point_z[col_name] = Z_fine[wave_idx, bow_idx] # 按固定Z值降序排列(从高到低)- 确保 Fsim_DP 最后绘制 fsim_columns_without_dp = [col for col in fsim_columns if col != &#39;Fsim_DP&#39;] sorted_columns = sorted(fsim_columns_without_dp, key=lambda col: fixed_point_z[col], reverse=True) # 如果有 Fsim_DP,则添加到列表末尾以确保最后绘制(最上层) if &#39;Fsim_DP&#39; in fsim_columns: sorted_columns.append(&#39;Fsim_DP&#39;) # 记录标签位置和颜色 label_positions = [] label_colors = {} # 计算最小间距防止标签重叠 min_spacing = 0.035 base_offset = 0.015 # 第一遍:确定所有标签位置 last_label_z = float(&#39;-inf&#39;) label_z_values = {} max_label_z = float(&#39;-inf&#39;) for col_name in sorted_columns: base_z = fixed_point_z[col_name] label_z = base_z + base_offset # 防止标签重叠 if label_z <= last_label_z + min_spacing: label_z = last_label_z + min_spacing last_label_z = label_z label_z_values[col_name] = label_z if label_z > max_label_z: max_label_z = label_z # 第二遍:绘制曲面和标签 for i, col_name in enumerate(sorted_columns): values = fsim_values[col_name] print(f"正在处理列: {col_name} ({i+1}/{len(sorted_columns)})") # 执行插值 print(" 执行插值...") Z_fine = griddata(points, values, (X_fine, Y_fine), method=&#39;cubic&#39;) # 曲面着色 - 为 Fsim_DP 使用单一红色 if col_name == &#39;Fsim_DP&#39;: facecolors = np.zeros((Z_fine.shape[0]-1, Z_fine.shape[1]-1, 4)) facecolors[:, :, 0] = 1.0 # R facecolors[:, :, 1] = 0.0 # G facecolors[:, :, 2] = 0.0 # B else: # 常规着色方式 Z_top_left = Z_fine[:-1, :-1] Z_top_right = Z_fine[:-1, 1:] Z_bottom_left = Z_fine[1:, :-1] Z_bottom_right = Z_fine[1:, 1:] Z_midpoints = (Z_top_left + Z_top_right + Z_bottom_left + Z_bottom_right) / 4.0 facecolors = global_cmap(global_norm(Z_midpoints)) # 设置透明度 alpha_idx = min(i, len(alphas)-1) alpha = alphas[alpha_idx] facecolors[:, :, 3] = alpha print(f" 渲染曲面 (透明度: {alpha:.2f})...") # 优化渲染密度 stride_x = 1 stride_y = 1 if len(x_fine) > 100: render_density = max(1, int(len(x_fine)/75)) stride_x = render_density stride_y = max(1, int(render_density/1.5)) # 绘制曲面 surf = ax.plot_surface( X_fine[::stride_y, ::stride_x], Y_fine[::stride_y, ::stride_x], Z_fine[::stride_y, ::stride_x], facecolors=facecolors[::stride_y, ::stride_x], shade=False, rcount=min(300, len(y_fine)), ccount=min(300, len(x_fine)), alpha=alpha, antialiased=True, edgecolor=&#39;black&#39;, linewidth=0.1, zorder=len(fsim_columns)-i ) # 添加曲面标签 print(" 添加曲面标签...") label_x = X_fine[wave_idx, bow_idx] label_y = Y_fine[wave_idx, bow_idx] base_z = fixed_point_z[col_name] # 为 Fsim_DP 使用红色标签,其他使用基于值的颜色 if col_name == &#39;Fsim_DP&#39;: text_color = (1.0, 0.0, 0.0) # 纯红色 else: normalized_value = global_norm(base_z) text_color = global_cmap(normalized_value)[:3] # RGB颜色 # 存储标签颜色 label_colors[col_name] = text_color # 使用计算的标签高度 label_z = label_z_values[col_name] # 添加标签 ax.text( label_x, label_y, label_z, " " + col_name, fontsize=11, color=text_color, fontweight=&#39;bold&#39;, ha=&#39;left&#39;, va=&#39;center&#39;, zorder=100 ) label_positions.append((label_x, label_y, label_z)) # 6. 调整Z轴上限以容纳标签 if max_label_z > z_max: print(f"调整Z轴上限以容纳标签: 原上限 {z_max:.4f}, 新上限 {max_label_z + 0.03:.4f}") z_max = max_label_z + 0.03 # 7. 添加全局注释(确保位置合适) print("添加全局注释...") annotation_x = 0.90 # 右侧位置 annotation_y = 0.95 # 顶部位置,避免遮挡 # 添加带颜色的注释文本 for i, col in enumerate(fsim_columns): # 使用预定义的描述信息 description = column_descriptions[col] text = f"{col}: {description}" text_y = annotation_y - i * 0.025 # 从上到下排列 # 使用对应的标签颜色 color = label_colors.get(col, &#39;darkblue&#39;) # 添加注释文本(无背景框) plt.figtext( annotation_x, text_y, text, fontsize=12, color=color, ha=&#39;right&#39;, va=&#39;top&#39;, # 顶部对齐 zorder=100, fontweight=&#39;bold&#39; ) # 8. 坐标轴设置 ax.set_xlabel(&#39;BOW ()&#39;, fontsize=15, labelpad=25) ax.set_ylabel(&#39;WAVE ()&#39;, fontsize=15, labelpad=25) ax.set_zlabel(&#39;Fsim&#39;, fontsize=15, labelpad=25) title = ax.set_title( f&#39;Multi-Fsim Analysis ({len(fsim_columns)} Surfaces)&#39;, fontsize=20, pad=15, y=0.98 ) # 自动计算合适的x轴刻度 num_ticks = min(6, max(3, int((x_max - x_min) / 0.5) + 1)) ax.set_xticks(np.linspace(x_min, x_max, num_ticks)) ax.set_yticks([0.2, 0.4, 0.6, 0.8, 1.0]) z_step = 0.05 z_min_rounded = np.floor(z_min / z_step) * z_step z_max_rounded = np.ceil(z_max / z_step) * z_step z_ticks = np.arange(z_min_rounded, z_max_rounded + z_step/2, z_step) if len(z_ticks) > 10: step_idx = max(1, int(len(z_ticks) / 5)) z_ticks = z_ticks[::step_idx] ax.set_zticks(z_ticks) ax.set_zticklabels([f"{z:.2f}" for z in z_ticks]) ax.tick_params(axis=&#39;both&#39;, which=&#39;major&#39;, labelsize=11, pad=10) # 9. 网格和平面设置 print("添加背景网格线...") grid_params = { &#39;visible&#39;: True, &#39;linestyle&#39;: &#39;--&#39;, &#39;linewidth&#39;: 0.5, &#39;alpha&#39;: 0.2, &#39;color&#39;: &#39;gray&#39; } ax.xaxis._axinfo["grid"].update(grid_params) ax.yaxis._axinfo["grid"].update(grid_params) ax.zaxis.axinfo["grid"].update(grid_params) ax.xaxis.pane.fill = False ax.yaxis.pane.fill = False ax.zaxis.pane.fill = False ax.xaxis.pane.set_edgecolor(&#39;lightgray&#39;) ax.yaxis.pane.set_edgecolor(&#39;lightgray&#39;) ax.zaxis.pane.set_edgecolor(&#39;lightgray&#39;) ax.xaxis.pane.set_alpha(0.05) ax.yaxis.pane.set_alpha(0.05) ax.zaxis.pane.set_alpha(0.05) # 10. 视图设置 print("配置轴测图投影...") ax.set_proj_type(&#39;ortho&#39;) ax.view_init(elev=32, azim=42) ax.set_box_aspect(aspect=(1, 1, 1)) # 关键修改:设置视图范围(移除了额外的固定边距) ax.set_xlim(x_min, x_max) # 直接使用动态计算的范围 ax.set_ylim(0.0, 1.1) ax.set_zlim(z_min, z_max) # 11. 添加全局颜色条 print("添加全局Fsim颜色条...") cax = fig.add_axes([0.85, 0.25, 0.02, 0.5]) cbar = ColorbarBase( cax, cmap=global_cmap, norm=global_norm, orientation=&#39;vertical&#39;, label=&#39;Fsim Value&#39; ) cbar.set_label(&#39;Fsim Value&#39;, fontsize=12, labelpad=15) cbar.ax.tick_params(labelsize=10) # 12. 保存和显示 print("保存多曲面轴测图...") plt.savefig(&#39;multi_fsim_surfaces.png&#39;, dpi=350, bbox_inches=&#39;tight&#39;) print("渲染完成,显示图表...") plt.tight_layout(pad=3.0) plt.show() if __name__ == "__main__": generate_multi_fsim_surfaces() import pandas as pd import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.interpolate import griddata from matplotlib.colors import LinearSegmentedColormap, Normalize import os from matplotlib.colorbar import ColorbarBase def generate_multi_fsim_surfaces(): # 1. 数据加载部分 print("从Excel加载原始数据...") try: script_dir = os.path.dirname(os.path.abspath(__file__)) excel_path = os.path.join(script_dir, &#39;fsim_data.xlsx&#39;) df = pd.read_excel(excel_path) # 验证必要列存在 required_columns = [&#39;BOW&#39;, &#39;WAVE&#39;] if not all(col in df.columns for col in required_columns): missing = [col for col in required_columns if col not in df.columns] raise ValueError(f"Excel文件缺少必要的列: {missing}") # 计算BOW列的最小值和最大值 bow_min = df[&#39;BOW&#39;].min() bow_max = df[&#39;BOW&#39;].max() # 添加10%的边距 bow_range = bow_max - bow_min x_min = max(0.1, bow_min - bow_range * 0.1) # 确保最小值不小于0.1 x_max = bow_max + bow_range * 0.1 print(f"BOW数据范围: {bow_min:.2f} 到 {bow_max:.2f}") print(f"设置X轴范围: {x_min:.2f} 到 {x_max:.2f}") # 定义所有可能的曲面描述信息 column_descriptions = { &#39;Fsim_E1&#39;: &#39;0.5mm Edge deformation&#39;, &#39;Fsim_E2&#39;: &#39;1.0mm Edge deformation&#39;, &#39;Fsim_E3&#39;: &#39;1.5mm Edge deformation&#39;, &#39;Fsim_E4&#39;: &#39;1.0mm Edge deformation&#39;, &#39;Fsim_E5&#39;: &#39;2.5mm Edge deformation&#39;, &#39;Fsim_E6&#39;: &#39;3.0mm Edge deformation&#39;, &#39;Fsim_DP&#39;: &#39;recommended range&#39; } # 找出所有Fsim列并验证描述信息 fsim_columns = [col for col in df.columns if col.startswith(&#39;Fsim&#39;)] # 检查是否有未定义的列并添加默认描述 missing_descriptions = [col for col in fsim_columns if col not in column_descriptions] for col in missing_descriptions: column_descriptions[col] = f&#39;custom Fsim metric ({col})&#39; print(f"警告: 为未定义的列添加默认描述: {col} -> {column_descriptions[col]}") if not fsim_columns: raise ValueError("未找到任何Fsim列") print(f"找到 {len(fsim_columns)} 个Fsim列: {&#39;, &#39;.join(fsim_columns)}") points = df[[&#39;BOW&#39;, &#39;WAVE&#39;]].values fsim_values = {col: df[col].values for col in fsim_columns} # 计算全局Fsim范围 all_fsim_min = min(df[col].min() for col in fsim_columns) all_fsim_max = max(df[col].max() for col in fsim_columns) z_padding = (all_fsim_max - all_fsim_min) * 0.1 z_min = all_fsim_min - z_padding z_max = all_fsim_max + z_padding print(f"全局Fsim范围: {all_fsim_min:.4f} 到 {all_fsim_max:.4f}") print(f"设置Z轴范围: {z_min:.4f} 到 {z_max:.4f}") except FileNotFoundError: print(f"错误: 找不到数据文件 {excel_path}") print("请确保fsim_data.xlsx文件位于脚本同一目录下") return except Exception as e: print(f"加载数据时出错: {str(e)}") return # 2. 创建高分辨率网格 print("创建高分辨率网格...") # 使用动态计算的x轴范围 x_fine = np.linspace(x_min, x_max, 150) # 使用动态范围 y_fine = np.linspace(0.1, 1.0, 100) X_fine, Y_fine = np.meshgrid(x_fine, y_fine) # 3. 创建3D图表 print("创建3D图表...") fig = plt.figure(figsize=(18, 14)) ax = fig.add_axes([0.1, 0.07, 0.7, 0.83], projection=&#39;3d&#39;) # 4. 定义全局颜色映射 print("创建颜色映射...") global_cmap = LinearSegmentedColormap.from_list( "global_cmap", [(0.1, 0.2, 0.8), (0.2, 0.8, 0.4), (1.0, 0.9, 0.1)], N=512 ) global_norm = Normalize(vmin=all_fsim_min, vmax=all_fsim_max) alphas = [0.65, 0.55, 0.45, 0.35, 0.25, 0.15] # 5. 曲面绘制和标签添加 print(f"开始处理 {len(fsim_columns)} 个曲面并添加标签...") # 查找固定(BOW=1, WAVE=1)的网格索引 bow_target = 1.0 wave_target = 1.0 # 如果固定不在范围内,使用平均值 if bow_target < x_min or bow_target > x_max: bow_target = (x_min + x_max) / 2 print(f"警告: BOW固定超出范围,调整为中值: {bow_target:.2f}") bow_idx = np.abs(x_fine - bow_target).argmin() wave_idx = np.abs(y_fine - wave_target).argmin() # 预计算所有曲面在固定的Z值 fixed_point_z = {} for col_name in fsim_columns: Z_fine = griddata(points, fsim_values[col_name], (X_fine, Y_fine), method=&#39;cubic&#39;) fixed_point_z[col_name] = Z_fine[wave_idx, bow_idx] # 按固定Z值降序排列(从高到低)- 确保 Fsim_DP 最后绘制 fsim_columns_without_dp = [col for col in fsim_columns if col != &#39;Fsim_DP&#39;] sorted_columns = sorted(fsim_columns_without_dp, key=lambda col: fixed_point_z[col], reverse=True) # 如果有 Fsim_DP,则添加到列表末尾以确保最后绘制(最上层) if &#39;Fsim_DP&#39; in fsim_columns: sorted_columns.append(&#39;Fsim_DP&#39;) # 记录标签位置和颜色 label_positions = [] label_colors = {} # 计算最小间距防止标签重叠 min_spacing = 0.035 base_offset = 0.015 # 第一遍:确定所有标签位置 last_label_z = float(&#39;-inf&#39;) label_z_values = {} max_label_z = float(&#39;-inf&#39;) for col_name in sorted_columns: base_z = fixed_point_z[col_name] label_z = base_z + base_offset # 防止标签重叠 if label_z <= last_label_z + min_spacing: label_z = last_label_z + min_spacing last_label_z = label_z label_z_values[col_name] = label_z if label_z > max_label_z: max_label_z = label_z # 第二遍:绘制曲面和标签 for i, col_name in enumerate(sorted_columns): values = fsim_values[col_name] print(f"正在处理列: {col_name} ({i+1}/{len(sorted_columns)})") # 执行插值 print(" 执行插值...") Z_fine = griddata(points, values, (X_fine, Y_fine), method=&#39;cubic&#39;) # 曲面着色 - 为 Fsim_DP 使用单一红色 if col_name == &#39;Fsim_DP&#39;: facecolors = np.zeros((Z_fine.shape[0]-1, Z_fine.shape[1]-1, 4)) facecolors[:, :, 0] = 1.0 # R facecolors[:, :, 1] = 0.0 # G facecolors[:, :, 2] = 0.0 # B else: # 常规着色方式 Z_top_left = Z_fine[:-1, :-1] Z_top_right = Z_fine[:-1, 1:] Z_bottom_left = Z_fine[1:, :-1] Z_bottom_right = Z_fine[1:, 1:] Z_midpoints = (Z_top_left + Z_top_right + Z_bottom_left + Z_bottom_right) / 4.0 facecolors = global_cmap(global_norm(Z_midpoints)) # 设置透明度 alpha_idx = min(i, len(alphas)-1) alpha = alphas[alpha_idx] facecolors[:, :, 3] = alpha print(f" 渲染曲面 (透明度: {alpha:.2f})...") # 优化渲染密度 stride_x = 1 stride_y = 1 if len(x_fine) > 100: render_density = max(1, int(len(x_fine)/75)) stride_x = render_density stride_y = max(1, int(render_density/1.5)) # 绘制曲面 surf = ax.plot_surface( X_fine[::stride_y, ::stride_x], Y_fine[::stride_y, ::stride_x], Z_fine[::stride_y, ::stride_x], facecolors=facecolors[::stride_y, ::stride_x], shade=False, rcount=min(300, len(y_fine)), ccount=min(300, len(x_fine)), alpha=alpha, antialiased=True, edgecolor=&#39;black&#39;, linewidth=0.1, zorder=len(fsim_columns)-i ) # 添加曲面标签 print(" 添加曲面标签...") label_x = X_fine[wave_idx, bow_idx] label_y = Y_fine[wave_idx, bow_idx] base_z = fixed_point_z[col_name] # 为 Fsim_DP 使用红色标签,其他使用基于值的颜色 if col_name == &#39;Fsim_DP&#39;: text_color = (1.0, 0.0, 0.0) # 纯红色 else: normalized_value = global_norm(base_z) text_color = global_cmap(normalized_value)[:3] # RGB颜色 # 存储标签颜色 label_colors[col_name] = text_color # 使用计算的标签高度 label_z = label_z_values[col_name] # 添加标签 ax.text( label_x, label_y, label_z, " " + col_name, fontsize=11, color=text_color, fontweight=&#39;bold&#39;, ha=&#39;left&#39;, va=&#39;center&#39;, zorder=100 ) label_positions.append((label_x, label_y, label_z)) # 6. 调整Z轴上限以容纳标签 if max_label_z > z_max: print(f"调整Z轴上限以容纳标签: 原上限 {z_max:.4f}, 新上限 {max_label_z + 0.03:.4f}") z_max = max_label_z + 0.03 # 7. 添加全局注释(确保位置合适) print("添加全局注释...") annotation_x = 0.90 # 右侧位置 annotation_y = 0.95 # 顶部位置,避免遮挡 # 添加带颜色的注释文本 for i, col in enumerate(fsim_columns): # 使用预定义的描述信息 description = column_descriptions[col] text = f"{col}: {description}" text_y = annotation_y - i * 0.025 # 从上到下排列 # 使用对应的标签颜色 color = label_colors.get(col, &#39;darkblue&#39;) # 添加注释文本(无背景框) plt.figtext( annotation_x, text_y, text, fontsize=12, color=color, ha=&#39;right&#39;, va=&#39;top&#39;, # 顶部对齐 zorder=100, fontweight=&#39;bold&#39; ) # 8. 坐标轴设置 ax.set_xlabel(&#39;BOW ()&#39;, fontsize=15, labelpad=25) ax.set_ylabel(&#39;WAVE ()&#39;, fontsize=15, labelpad=25) ax.set_zlabel(&#39;Fsim&#39;, fontsize=15, labelpad=25) title = ax.set_title( f&#39;Multi-Fsim Analysis ({len(fsim_columns)} Surfaces)&#39;, fontsize=20, pad=15, y=0.98 ) # 自动计算合适的x轴刻度 num_ticks = min(6, max(3, int((x_max - x_min) / 0.5) + 1)) ax.set_xticks(np.linspace(x_min, x_max, num_ticks)) ax.set_yticks([0.2, 0.4, 0.6, 0.8, 1.0]) z_step = 0.05 z_min_rounded = np.floor(z_min / z_step) * z_step z_max_rounded = np.ceil(z_max / z_step) * z_step z_ticks = np.arange(z_min_rounded, z_max_rounded + z_step/2, z_step) if len(z_ticks) > 10: step_idx = max(1, int(len(z_ticks) / 5)) z_ticks = z_ticks[::step_idx] ax.set_zticks(z_ticks) ax.set_zticklabels([f"{z:.2f}" for z in z_ticks]) ax.tick_params(axis=&#39;both&#39;, which=&#39;major&#39;, labelsize=11, pad=10) # 9. 网格和平面设置 print("添加背景网格线...") grid_params = { &#39;visible&#39;: True, &#39;linestyle&#39;: &#39;--&#39;, &#39;linewidth&#39;: 0.5, &#39;alpha&#39;: 0.2, &#39;color&#39;: &#39;gray&#39; } ax.xaxis._axinfo["grid"].update(grid_params) ax.yaxis._axinfo["grid"].update(grid_params) ax.zaxis.axinfo["grid"].update(grid_params) ax.xaxis.pane.fill = False ax.yaxis.pane.fill = False ax.zaxis.pane.fill = False ax.xaxis.pane.set_edgecolor(&#39;lightgray&#39;) ax.yaxis.pane.set_edgecolor(&#39;lightgray&#39;) ax.zaxis.pane.set_edgecolor(&#39;lightgray&#39;) ax.xaxis.pane.set_alpha(0.05) ax.yaxis.pane.set_alpha(0.05) ax.zaxis.pane.set_alpha(0.05) # 10. 视图设置 print("配置轴测图投影...") ax.set_proj_type(&#39;ortho&#39;) ax.view_init(elev=32, azim=42) ax.set_box_aspect(aspect=(1, 1, 1)) # 关键修改:设置视图范围(移除了额外的固定边距) ax.set_xlim(x_min, x_max) # 直接使用动态计算的范围 ax.set_ylim(0.0, 1.1) ax.set_zlim(z_min, z_max) # 11. 添加全局颜色条 print("添加全局Fsim颜色条...") cax = fig.add_axes([0.85, 0.25, 0.02, 0.5]) cbar = ColorbarBase( cax, cmap=global_cmap, norm=global_norm, orientation=&#39;vertical&#39;, label=&#39;Fsim Value&#39; ) cbar.set_label(&#39;Fsim Value&#39;, fontsize=12, labelpad=15) cbar.ax.tick_params(labelsize=10) # 12. 保存和显示 print("保存多曲面轴测图...") plt.savefig(&#39;multi_fsim_surfaces.png&#39;, dpi=350, bbox_inches=&#39;tight&#39;) print("渲染完成,显示图表...") plt.tight_layout(pad=3.0) plt.show() if __name__ == "__main__": generate_multi_fsim_surfaces() name__ == "__main__": generate_multi_fsim_surfaces() 将python中的x轴调整为固定的0~2,刻度是0.5设置一个,y轴调整为0~3,刻度是0.5设置一个,z轴调整为0.6~1.0,刻度式0.1设置一个,调整python代码
10-27
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值