TWaver3D机房不但可以单独作为工具使用,而且还提供了独立的组件,为开发者提供二次开发的能力。
开发环境
使用TWaver机房组件进行二次开发,需要以下开发环境:
- JDK 6+;
- Java IDE(Netbeans或Eclipse);
- TWaver Java 3.5+;
类结构
TWaver机房二次开发涉及到的主要类及其关系:

其中,各个类的主要用途如下:
类 |
用途 |
Network2D |
是TWaver Java中TNetwork的一个扩展。作为3D视图的2D呈现、编辑和数据驱动。 |
Floor2D |
和Network2D配合,用于绘制2D视图中的地板平面。 |
Network3D |
3D视图,以3D的方式呈现2D视图中的数据,并提供缩放、旋转等交互。Network3D必须依赖于Network2D而存在。 |
Floor3D |
和Network3D配合,用于在3D视图中绘制地板平面的3D视图。它依赖于Floor2D存在。 |
SittingPane |
一个封装好的3D机房属性编辑面板。包括3D视图中的各种参数设置,可以用于需要数据编辑的场合中。对于只需要3D数据呈现的情况,可以不使用该类。 |
PropertySheet3D |
一个封装好的3D数据属性表。当一个或多个3D数据被选中后,所有3D相关的属性会自动罗列在这个属性表中,方便查看和修改。该类可以用于需要数据编辑的场合中。对于只需要3D数据呈现的情况,可以不使用该类。 |
简单例子
下面的代码可以创建一个简单的3D机房视图。该程序只显示了一个3D视图,并显示了一个3D立方体物体。3D视图可以通过鼠标的滚轮、拖拽等方式进行缩放、平移等操作。
代码如下:
1
import java.awt.Color;
2
import javax.swing.JFrame;
3
import javax.swing.SwingUtilities;
4
import twaver.TDataBox;
5
import twaver.TWaverConst;
6
import twaver.TWaverUtil;
7
8
public class SimpleTest extends JFrame
{
9
10
private TDataBox box = new TDataBox();
11
private Network2D network2d = new Network2D(box);
12
private Floor2D roomFloor2D = new Floor2D(network2d);
13
private Network3D view3d = new Network3D(network2d, roomFloor2D, 300, 100);
14
15
public SimpleTest()
{
16
this.setTitle("TWaver 3D");
17
this.setSize(900, 600);
18
TWaverUtil.centerWindow(this);
19
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
20
view3d.setScale(1.5);
21
22
this.add(view3d);
23
24
//add a 3d object
25
Element3D element = new Element3D();
26
element.setLocation(100, 100);
27
element.setSize(200, 50);
28
element.setDeep(100);
29
element.setFillColor(Color.orange.darker());
30
network2d.getDataBox().addElement(element);
31
}
32
33
public static void main(String[] args)
{
34
SwingUtilities.invokeLater(new Runnable()
{
35
36
public void run()
{
37
TWaverUtil.setLocale(TWaverConst.EN_US);
38
SimpleTest test = new SimpleTest();
39
test.setVisible(true);
40
}
41
});
42
}
43
运行该代码,效果如下图:

在3D视图中显示Link
和TWaver Java中类似,要在3D视图中显示Link,直接在2D Network的DataBox中添加Link对象即可。以下代码在2D视图中创建了几个节点和连线:
1
private void testLink()
{
2
Element3D node1 = new Element3D();
3
node1.setLocation(50, 50);
4
node1.getAlarmState().addNewAlarm(AlarmSeverity.CRITICAL);
5
node1.setDeep(50);
6
node1.setFillColor(Color.green.darker());
7
network2d.getDataBox().addElement(node1);
8
9
Element3D node2 = new Element3D();
10
node2.setLocation(200, 100);
11
node2.setDeep(50);
12
node2.setFillColor(Color.green.darker());
13
network2d.getDataBox().addElement(node2);
14
15
Element3D node3 = new Element3D();
16
node3.setLocation(300, 200);
17
node3.setDeep(50);
18
node3.setFillColor(Color.green.darker());
19
network2d.getDataBox().addElement(node3);
20
21
Link link = new Link(node1, node2);
22
link.putLinkColor(Color.cyan.darker());
23
link.putLinkWidth(8);
24
link.setLinkType(TWaverConst.LINK_TYPE_ORTHOGONAL);
25
link.putLinkOutlineColor(Color.red);
26
link.putLinkOutlineWidth(1);
27
link.putLinkOrthogonalDirection(OrthogonalLinkDirectionType.Y_TO_X);
28
network2d.getDataBox().addElement(link);
29
30
link = new Link(node2, node3);
31
link.putLinkColor(Color.green.darker());
32
link.putLinkWidth(8);
33
link.setLinkType(TWaverConst.LINK_TYPE_FLEXIONAL);
34
link.putLinkOutlineColor(Color.red);
35
link.putLinkOutlineWidth(2);
36
network2d.getDataBox().addElement(link);
37
}
在2D和3D视图中会同时显示Link,显示效果如下:

3D视图会将Link绘制在地板上,并保持和2D的颜色、边框、线宽、选择状态等属性一致。以下是将3D视图进行各角度旋转后Link的显示效果:

创建3D视图右键菜单
在TWaver 2D的Network组件中已经提供了右键菜单支持。TWaver通过一个菜单生成器来动态生成Network的右键菜单。下面代码展示了这种用法:
1
network2d.setPopupMenuGenerator(new PopupMenuGenerator()
{
2
public JPopupMenu generate(TView tview, MouseEvent me)
{
3
JPopupMenu menu = new JPopupMenu();
4
…
5
return menu;
6
}
7
}
8
});
同样,在3D视图中,TWaver也提供了类似机制,并使用了相同的方法名称和接口。我们可以new一个PopupMenuGenerator实例然后同时应用在2D和3D视图中,如下面代码:
1
PopupMenuGenerator generator= new PopupMenuGenerator()
{
2
public JPopupMenu generate(TView tview, MouseEvent me)
{
3
JPopupMenu menu = new JPopupMenu();
4
JMenuItem item = new JMenuItem("Create Object Icon");
5
menu.add(item);
6
return menu;
7
}
8
}
9
});
10
network2d. setPopupMenuGenerator(generator);
11
network3d. setPopupMenuGenerator(generator);
运行以上代码可以看到,在2D和3D视图上右键点击同一个节点,可以看到同样的右键菜单:

创建简单的编辑器
上述例子显示了一个非常简单的、只读的3D视图。本节将介绍如何创建一个简单的编辑器程序,可以对数据进行动态的添加、编辑等操作。其实在上述例子中已经创建了Network2D视图,只是没有显示出来而已。下面的例子将Network2D视图显示出来,并增加了SettingPane参数编辑视图、PropertySheet3D属性表等组件,共同构成了简单的编辑器程序。
代码如下:
1
import java.awt.Color;
2
import java.awt.Insets;
3
import java.awt.event.ActionEvent;
4
import java.awt.event.ActionListener;
5
import javax.swing.JButton;
6
import javax.swing.JComponent;
7
import javax.swing.JFrame;
8
import javax.swing.JSplitPane;
9
import javax.swing.SwingUtilities;
10
import twaver.TDataBox;
11
import twaver.TWaverConst;
12
import twaver.TWaverUtil;
13
import twaver.table.TPropertySheetPane;
14
15
public class Test extends JFrame
{
16
17
private TDataBox box = new TDataBox();
18
private Network2D network2d = new Network2D(box);
19
private Floor2D roomFloor2D = new Floor2D(network2d);
20
private Network3D view3d = new Network3D(network2d, roomFloor2D, 300, 50);
21
private SettingPane settingPane = new SettingPane(roomFloor2D, view3d, 2);
22
private PropertySheet3D sheet = new PropertySheet3D(box);
23
private TPropertySheetPane sheetPane = new TPropertySheetPane(sheet);
24
25
public Test()
{
26
init();
27
}
28
29
private void init()
{
30
this.setTitle("TWaver 3D Room");
31
this.setSize(1000, 700);
32
TWaverUtil.centerWindow(this);
33
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
34
35
JSplitPane split1 = createSplit(sheetPane, network2d, JSplitPane.HORIZONTAL_SPLIT, 300);
36
JSplitPane split2 = createSplit(settingPane, view3d, JSplitPane.HORIZONTAL_SPLIT, 350);
37
JSplitPane split = createSplit(split1, split2, JSplitPane.VERTICAL_SPLIT, 350);
38
39
this.add(split);
40
41
JButton createNodeButton = new JButton("New");
42
createNodeButton.setMargin(new Insets(0, 0, 0, 0));
43
createNodeButton.addActionListener(new ActionListener()
{
44
45
public void actionPerformed(ActionEvent e)
{
46
Element3D element = new Element3D();
47
element.setLocation(100, 100);
48
element.setDeep(50);
49
element.setFillColor(Color.green.darker());
50
network2d.getDataBox().addElement(element);
51
}
52
});
53
network2d.getToolbar().add(createNodeButton);
54
}
55
56
private JSplitPane createSplit(JComponent one, JComponent two, int direction, int dividerLocation)
{
57
JSplitPane split = new JSplitPane(direction);
58
59
split.setLeftComponent(one);
60
split.setTopComponent(one);
61
62
split.setRightComponent(two);
63
split.setBottomComponent(two);
64
65
split.setContinuousLayout(true);
66
split.setDividerLocation(dividerLocation);
67
68
return split;
69
}
70
71
public static void main(String[] args)
{
72
SwingUtilities.invokeLater(new Runnable()
{
73
74
public void run()
{
75
TWaverUtil.setLocale(TWaverConst.EN_US);
76
Test test = new Test();
77
test.setVisible(true);
78
}
79
});
80
}
81
}
上述代码在Network2D的工具条上添加了一个按钮,点击按钮后,创建一个Element3D对象,并将其设置在面板上。程序运行效果如下图:

点击“new”按钮创建一个3D物体,并在左侧属性表中设置其上、左、右三面的图片贴图。效果如下:

保存至文件
要将编辑器中的数据保存至文件,可以在工具条上添加一个按钮,点击后将DataBox中的数据输出到XML字符串,然后将字符串保存至文件。
1
String xml = network3d.toXML(true);
2
JFileChooser chooser = new JFileChooser();
3
chooser.setFileFilter(filter);
4
int answer = chooser.showSaveDialog(EditorDemo.this);
5
if (answer == JFileChooser.APPROVE_OPTION)
{
6
String fileName = chooser.getSelectedFile().getAbsolutePath();
7
if (fileName != null)
{
8
if (!fileName.toUpperCase().endsWith(".XML"))
{
9
fileName += ".xml";
10
}
11
OutputStream out = new FileOutputStream(fileName);
12
out.write(xml.getBytes());
13
out.close();
14
}
15
}
从文件打开
对于保存的数据,可以写一个按钮,选择对应的文件,通过DataBox的parse方法进行解析,打开数据文件。
1
JFileChooser chooser = new JFileChooser();
2
chooser.setFileFilter(filter);
3
int answer = chooser.showOpenDialog(EditorDemo.this);
4
if (answer == JFileChooser.APPROVE_OPTION)
{
5
File file = chooser.getSelectedFile();
6
if (file.exists())
{
7
network2d.getDataBox().clear();
8
network3d.clearObject3D();
9
InputStream input = new FileInputStream(file);
10
network2d.getDataBox().parse(input);
11
}
12
}
Element3D主要属性
通过以上例子可以发现,创建一个3D视图非常简单。在3D视图中创建一个3D数据的主要代码只有如下几行:
1
Element3D element = new Element3D();
2
element.setLocation(100, 100);
3
element.setDeep(50);
4
element.setFillColor(Color.green.darker());
5
network2d.getDataBox().addElement(element);
Element3D实际上是一个普通的TWaver Element对象,它从TWaver预定义的Element——ResizableNode继承而来,并封装了3D物体的基本属性,例如左侧图片、右侧图片、顶部图片等等。通过设置和修改这些属性,即可实现对3D物体的外观定制。
Element3D类的主要属性罗列如下。注意:所有setLeft***均作用域左侧里面,而右侧立面均有对应的setRight***方法,其功能是对右侧立面进行相应的功能设置。
- setFillColor:设置填充颜色;
- setDeep:设置3D物体的厚度(也就是三维中的高度);
- setPaintLeft:是否左侧立面可见。当设置false,整个左侧立面将消失;
- setLeftImageUrl:设置左侧立面的贴图。和TWaver Java中的惯例一样,给定图片的URL字符串即可。注意:默认的贴图方法,是将图片拉伸并铺满整个左侧立面。如需要边距或自定义边界,可以使用setLeftImageBounds方法进行设置;
- setLeftImageBounds:设置左侧立面的固定边界。该函数可以强制指定左侧立面图片的起始坐标位置和图片的宽高;
- setLeftImageTexture:指定左侧立面贴图是否使用纹理模式。如果设置true,则贴图会被作为纹理进行全立面贴图。注意:一旦启用纹理,ImageBounds参数将不再起作用;
- setLeftImageTextureScale:设置左侧立面纹理贴图的放大比例。默认值为3。当该值越大,纹理的单位贴图尺寸也更大;
- isPaintTop:是否顶面可见。当设置false,物体的顶面将会消失;
- setTopImageUrl:物体的顶面贴图。注意:一旦设置顶面贴图,则Network2D中物体的图片将使用该顶面贴图进行显示。
Network3D主要属性
Network3D类是用来呈现3D场景的组件。注意它并非从TWaver Java的TNetwork或上面提到的TNetwork2D继承而来,而是直接从Swing的JPanel扩展而来。Network3D必须依赖于Network2D而存在,从而从Network2D获得数据。此外,Network3D还必须设置一个Floor2D的类,用来绘制3D的场景地平面。Network3D还提供了构造函数可以直接设置其3D坐标原点的屏幕坐标位置。
Network3D的构造函数:
1
//给定Network2D和Floor2D进行构造
2
public Network3D(Network2D network2d, Floor2D floor2d)
3
//给定Network2D和Floor2D进行构造,同时指定3D场景的坐标原点的位置。
4
public Network3D(Network2D network2d, Floor2D floor2d, int originX, int originY)
Network3D的主要函数和用法如下:
- getNetwork2D:获得与Network3D绑定的Network2D对象。
- setScale:设置3D场景的放大系数。设置1为原始比例。也可以通过调用zoomIn或zoomOut函数进行单次的缩放。
- zoomIn/zoomOut:放大或缩小。每次缩放会用当前缩放因数乘以(放大)或除以(缩小)scale step数值(可以通过getScaleStep函数获得,其默认值是1.2)。
- setScaleStep:设置单次缩放变化因数。
- setOrigin:设置3D坐标原点的x和y数值。
- setHorizontalAngle:设置3D场景水平旋转角度。必须为0-90度。
- setVerticalAngle:设置3D场景垂直旋转角度。必须为0-90度。
- getObjectAt:获得给定坐标的3D物体。如果多个物体存在该点,则返回所有物体。
- toXML:将3D场景所有数据输出到XML中。
告警呈现
在TWaver Java中,Network以及其他各组件都有完整的告警呈现能力,例如告警渲染、告警冒泡等。在TWaver 3D机房中,我们也同样也提供了该功能。
告警冒泡
当一个物体在发生新发告警后,在2D的Network组件一样,3D视图也会绘制完全相同的告警冒泡。告警冒泡的位置在物体的顶面中央位置(如下图)。同时,3D物体的三个面的图片也会被动态的渲染为告警颜色:

3D物体颜色自定义
TWaver Java的Network本身具有自定义数据颜色的接口。通过一个插入的Generator接口,开发者可以拦截所有数据并返回任意颜色用于绘制数据。例如,一个node的fillColor为绿色,则默认会使用绿色来填充该node;但通过Generator拦截后,可以返回红色;此时,TWaver Network会使用红色来填充物体,而不再是绿色。
同样,TWaver 3D视图也提供了同样的能力。而且,3D视图直接使用了2D视图的该Generator,以便3D和2D保持同样的外观特性。也就是说,我们不需要在3D视图上设置Generator,而是只在3D视图对应的2D视图上设置Generator即可让2D、3D视图同时生效。以下例子强行将2D、3D视图中所有物体绘制颜色设置为MAGENTA颜色:
1
network2d.setElementBodyColorGenerator(new Generator()
{
2
public Object generate(Object o)
{
3
return Color.MAGENTA;
4
}
5
});

数据的输入与输出
默认输出方法
在3D视图程序中,3D机房数据使用和TWaver Java提供的同样的对象XML序列化方法进行数据的序列化和反序列化,从而实现数据的输入与输出。通过TDataBox.output()方法和TDataBox.parse()方法完成数据的导出和输入。不过,为了方便开发者使用,Network3D类提供了toXML函数可以直接输出XML字符串:
1
String xml = this.network3d.toXML(true);
其中的boolean值是用来控制是否对各立面贴图进行BASE64编码输出。
自定义格式输出
如果要进行进一步细致的输出参数控制,也可以不适用toXML函数,而直接使用TDataBox的output方法进行。以下代码显示了如何将内存中的3D数据导出到XML格式的字符串中并进行输出,同时对输出的各种参数进行控制,例如是否输出ID、是否输出告警信息等:
1
ByteArrayOutputStream out = new ByteArrayOutputStream();
2
setting.setWithElementId(false);
3
setting.setWithUserProperty(true);
4
setting.setOutputStream(out);
5
box.output(setting);
6
String xml = out.toString("utf8");
以上代码可以对数据进行XML序列化输出。
为何使用BASE64图片编码输出
3D物体中的各个立面贴图将以URL字符串的方式输出到XML中。当XML被传输到其他机器或应用环境中,如果图片的URL资源位置发生变化,则无法读到这些图片。解决这一问题的一个方法是:在数据输出过程中,使用BASE64编码对所有图片资源进行编码并序列化输出。这样,XML中就包含了所有的图片资源,无论XML被传输到哪里,都可以无外部依赖的打开。
使用Network3D的toXML函数可以直接进行图片的BASE64编码输出。实际上,该函数也是使用了TDataBox的输出参数控制机制,对用到的图片进行遍历和编码设置。以下代码展示了其具体实现方法:
1
//use base64 image encoding output setting.
2
DataBoxOutputSetting setting = new DataBoxOutputSetting();
3
Map images = new HashMap();
4
Iterator it = this.view2d.getDataBox().iterator();
5
while (it.hasNext())
{
6
Element element = (Element) it.next();
7
if (element instanceof RoomNode)
{
8
RoomNode roomNode = (RoomNode) element;
9
this.addImage(images, roomNode.getLeftImageUrl());
10
this.addImage(images, roomNode.getRightImageUrl());
11
this.addImage(images, roomNode.getTopImageUrl());
12
}
13
}
14
setting.setImages(images);
经过以上代码的预处理之后,再使用这个setting对象对box进行序列化,即可实现BASE64的图片编码存储。
3D物体图片导出
TWaver 3D视图提供了对单个3D物体进行图片导出的功能。通过下面的方法可以直接将指定的3D物体绘制在一个Image图片上:
1
public Image createObjectImage(Object objectID)
以上代码会将3D视图中指定的3D物体绘制在一个单独的Image对象中返回。该物体在图片上的大小、角度、材质等,均与3D视图保持一致;Image图片的大小也会自动裁剪到物体边缘位置,保持图片尺寸最小。
请注意该方法并不需要提供导出图片的大小尺寸。要使用更大或更小的尺寸,或使用不同的旋转角度,可以先通过API或鼠标调整3D视图的缩放系数、旋转角度,再使用该方法进行图片导出。
通过这一功能,可以将一些3D物体导出为小图片或Icon,为其他软件使用。为了演示这一功能,以下代码通过右键菜单来为某个3D物体创建导出图片,并显示在一个弹出对话框中:
1
PopupMenuGenerator generator=new PopupMenuGenerator()
{
2
3
public JPopupMenu generate(TView tview, MouseEvent me)
{
4
TDataBox box = tview.getDataBox();
5
if (box.getSelectionModel().size() == 1)
{
6
Element element = box.getSelectionModel().lastElement();
7
final Object id = element.getID();
8
9
JPopupMenu menu = new JPopupMenu();
10
JMenuItem item = new JMenuItem("Create Object Icon");
11
item.addActionListener(new ActionListener()
{
12
13
public void actionPerformed(ActionEvent e)
{
14
Image image = network3d.createObjectImage(id);
15
if (image != null)
{
16
JDialog dialog = new JDialog(EditorDemo.this);
17
dialog.setModal(true);
18
dialog.add(new JLabel(new ImageIcon(image)));
19
dialog.pack();
20
dialog.setTitle("Object Icon");
21
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
22
TWaverUtil.centerWindow(dialog);
23
dialog.setVisible(true);
24
}
25
}
26
});
27
menu.add(item);
28
return menu;
29
} else
{
30
return null;
31
}
32
}
33
};
34
network3d.setPopupMenuGenerator(generator);
运行以上代码:

也可以用ImageIO等机制进一步改造以上代码,把图片保存到文件中或数据库中。
I18N国际化
切换语言
TWaver Java本身已经提供了完整的国际化功能。通过Java的I18N机制以及TWaver Java包内置的多语言资源文件,可以动态的支持不同语言。TWaver 3D直接使用并扩展了TWaver Java的国际化机制,对TWaver 3D中的组件、对象属性名称等内容进行了国际化。
要在TWaver 3D机房中切换国际化,在程序的启动初期,直接调用TWaver Java提供的API进行设置即可:
1
//set Chinese language for TWaver 3D
2
TWaverUtil.setLocale(TWaverConst.ZH_CN);
3
//set English language for TWaver 3D
4
TWaverUtil.setLocale(TWaverConst.EN_US);
TWaver Java本身提供了中文、英文、西班牙语、日语等多种语言。而TWaver 3D目前仅支持英文、中文两种语言。
扩展资源字符串
TWaver 3D中所有的字符串都通过I18N机制存放在以下资源文件中:
- \twaver\network\d3\d3_en_US.properties(英文资源文件)
- \twaver\network\d3\d3_zh_CN.properties(中文资源文件)
以上资源文件都被直接打包在twaver3d.jar运行文件中。以下是资源文件中的几个例子:
d3_zh_CN.properties |
d3_en_US.properties |
image_texture=纹理图片pick_color=选择颜色room_name=机房名称:
floor_grid_span_count=地板各单位数量:
network_grid_unit_size=最小单元格大小:
|
image_texture=Image Texturepick_color=Pick Colorroom_name=Room Name:
floor_grid_span_count=Floor Grid Span Count:
network_grid_unit_size=Network Grid Unit Size:
|
其中,等号左侧是资源键值,右侧是对应的语言翻译。我们可以直接对翻译进行修改,并更新到包中,来修改TWaver 3D的默认翻译。另外,还可以在资源文件中增加自定义的资源,并在程序中使用。
例如,增加一个新的国际化字符串:
d3_zh_CN.properties |
d3_en_US.properties |
test_key =测试字符串 |
test_key =Test String |
然后,通过twaver.network.d3.Util.getString(String key)方法来获取字符串资源:
1
TWaverUtil.setLocale(TWaverConst.ZH_CN);
2
String resource = Util.getString(“test_key”);
3
System.out.println(resource);
4
TWaverUtil.setLocale(TWaverConst.EN_US);
5
String resource = Util.getString(“test_key”);
6
System.out.println(resource);
运行结果:
总结
通过这个简单的介绍,你一定对TWaver 3D机房充满了好奇。要想获得TWaver 3D机房产品包,可与我们联系。进一步深入的探索,会给你带来更多的惊喜。