进阶教程 | AnyCAD .NET桥梁建模(3):交互式创建

点击此处查看最新的网赚项目教程

上一章节,我们引入 dxf 格式对桥梁模型数据进行描述、保存和解析。

本章节中,我们将采用交互式创建桥梁。在此章节完成后,我们的桥梁设计工具将更贴近桥梁工程师的使用习惯。

几何数据的导出

在介绍交互式操作之前,先介绍几何数据的持久化,在本章节后续内容中,将会用到保存好的几何文件。

数据格式

常见的几何数据表达格式有:stp/step, igs/iges, stl, stb 等, 在 AnyCAD 中对上述格式都进行了支持。此外,也可以采用兼容性更好的 brep 格式进行持久化。

数据导出

我们将之前章节中提到的桥墩几何进行保存:

pierShapes = createPierTypeB(1.62.610.5)
compound = AnyCAD.ShapeBuilder.MakeCompound(pierShapes)
AnyCAD.ShapeIO.Save(compound, 'k1.brep')

经过上述简单操作,我们就已经将参数为 W=1.6 W1=2.6 HD=10.5 的 B 型桥墩保存在 k1 文件中。

在后文中,我们将读取这一数据并使用。

交互式操作含义

交互式操作,简单来说是输入与输出的交互,输入可以是鼠标、键盘等设备信号。输出则是内容的展示。

交互式操作中,对于使用者更弱化了数据的实际数值。

下文中,我们使用 C# .NET 进行交互式操作的实现。

环境准备

从头搭建一个 AnyCAD 环境,虽说并不复杂,但老助手选择拿已有的工程来用一下。

从官方找到高级示例 RapidCAX:

git clone git@gitee.com:anycad/RapidCAX.git

编译运行,即可看到软件的运行界面。

常见的数据建模工具软件有哪些,0,0,0,0.0,0,0,0,,-_常见的数据建模工具_常用的建模工具软件

软件启动界面AnyCAD 中的 Editor

AnyCAD 中的 Editor 定义为编辑器。可以理解为,当 Editor 激活时,会对键盘、鼠标等设备输入进行响应,而响应的具体内容可以由用户自行实现。

下面将介绍如何通过自定义响应的实现,制作一个桥梁创建工具。

在界面上添加按钮

在 SketchRibbonTab.xaml 中添加行,创建我们需要的按钮:

<Fluent:RibbonGroupBox Header="测试" IsLauncherVisible="False" Margin="7,0,0,0">
    ...
    <Fluent:Button Header="放置桥墩" Icon="/Rapid.Common.Res;component/Image/SectionBar.png" Size="Large" Command="{Binding AddPierCommand}" Margin="0,0,7,0"/>
    <Fluent:Button Header="创建直线梁" Icon="/Rapid.Common.Res;component/Image/SectionBar.png" Size="Large" Command="{Binding AddLineBeamCommand}" Margin="0,0,7,0"/>
</Fluent:RibbonGroupBox>

常见的数据建模工具_常用的建模工具软件_常见的数据建模工具软件有哪些,0,0,0,0.0,0,0,0,,-

添加按钮导入 DXF

注意到,这个示例中已经为我们实现了导入 DXF 的功能,我们可以用该功能直接导入桥梁中轴线的草图。

导入dxf实现“放置桥墩”功能

下文将介绍放置桥墩功能的实现,放置桥墩的关键在于确定桥墩的位置和方向,我们不妨将操作定义为:用户点击的第一个点为桥墩的位置,第一个点到第二个点的向量为桥墩的方向(顺桥向)。

我们先从 Editor 继承一个子类,用于表示放置桥墩功能编辑器:

class AddPierEditorEditor
{
    public AddPierEditor()
    {
        SetName("AddPier");
    }
}

我们重写该类的 Start 函数,用于表示当 Editor 被激活时,需要进行的准备工作。

先展示代码:

public override EnumEditorCode Start(ViewContext ctx)
{
    var tmp = ctx.GetTempContext();
    tmp.Start();
    tmp.SetPickFilter(EnumShapeFilter.Edge);
    var fileName = DialogUtil.OpenFileDialog("Shape"new StringList { "Shape Files(.brep)""*.brep" });
    if (fileName.IsEmpty())
        return EnumEditorCode.Failed;
    var shape = ShapeIO.Open(fileName);
    if (shape == null)
        return EnumEditorCode.Failed;

    mShapeMaterial = BasicMaterial.Create("temp");
    mShapeMaterial.SetColor(ColorTable.Green);
    mNode = BrepSceneNode.Create(shape, null, mShapeMaterial);
    if (mNode == null)
        return EnumEditorCode.Failed;
    mNode.SetVisible(false);
    ctx.GetScene().AddNode(mNode);
    return base.Start(ctx);
}

可以看到,当 Editor 被激活时,我们做了下面几件事:

为了将节点存储下来,我们将其定义为成员变量:

BrepSceneNode mNode;
BasicMaterial mShapeMaterial;
public override EnumEditorCode Start(ViewContext ctx)
{
    //implement start here
}

接下来,我们定义“鼠标移动”时的行为。这里可以发现,鼠标移动时的行为可以分为两个阶段:

1. 桥墩的位置还没确定;

2. 桥墩的位置已经确定。

我们定义一个成员变量表示桥墩的位置,当它为 null 的时候,表示上述阶段 1;当该变量有数据时,表示上述阶段 2:

Vector3? mLocation = null;

我们定义“鼠标移动”时的行为如下:

if (mNode == null)
    return EnumEditorCode.Ignored;
var pick = ctx.SnapPoint(evt.GetX(), evt.GetY());
var pt = pick.GetPosition();
if (mLocation == null)
{
    mNode.SetTransform(Matrix4.makeTranslate(pt.x, pt.y, pt.z));
    ctx.RequestUpdate(EnumUpdateFlags.Scene);
}
else
{
    var offset = pt - mLocation;
    offset.normalize();
    var rot = Matrix4.makeRotation(Vector3.UNIT_X, offset);
    var translate = Matrix4.makeTranslate(mLocation.x, mLocation.y, mLocation.z);
    mNode.SetTransform(translate * rot);
    ctx.RequestUpdate(EnumUpdateFlags.Scene);
}
mNode.SetVisible(true);
return base.OnMouseMove(ctx, evt);

代码逻辑解释为:当桥墩位置还不确定时,通过鼠标位置更新桥墩位置(桥墩跟着鼠标移动)。当桥墩位置确定时,鼠标与桥墩相对位置的向量将用于调整桥墩的朝向。

注意到,当场景 node 的数据被更新时,我们需要主动提示更新场景。并且当鼠标出现首次移动的动作时,就可以将 node 设置为可见了。

接下来我们定义鼠标点击(以弹起动作为准)时的行为:

public override EnumEditorCode OnMouseUp(ViewContext ctx, InputEvent evt)
{
    if (mLocation == null)
    {
        var pick = ctx.SnapPoint(evt.GetX(), evt.GetY());
        mLocation = pick.GetPosition();
    }
    else
    {
        // 创建好了
        mNode.SetEdgeMaterial(null);
        //重新创建个临时对象
        mNode = BrepSceneNode.Create(mNode.GetTopoShape(), null, mShapeMaterial);
        mNode.SetVisible(false);
        ctx.GetScene().AddNode(mNode);
        ctx.RequestUpdate(EnumUpdateFlags.Scene);
        return EnumEditorCode.Exit;
    }
    return base.OnMouseDown(ctx, evt);
}

这段代码也容易理解:当桥墩位置还没确定时,点击确定桥墩位置;当桥墩位置已经确定时,点击确定桥墩朝向,并退出。

这里:

return EnumEditorCode.Exit;

将提示系统关闭当前 Editor。关闭前,Editor 的 Finish 成员方法会被执行:

public override void Finish(ViewContext ctx)
{
    ctx.GetScene().RemoveNode(mNode.GetUuid());
    ctx.RequestUpdate(EnumUpdateFlags.Scene);
    base.Finish(ctx);
}

我们在 Finish 方法中定义当前 Editor 被关闭时需要执行的动作,往往是一些清理、提交、回滚等收尾工作。

由此,我们就完成了放置桥墩工具的实现。

绑定命令

命令管理器可以将命令和 Editor 关联起来,我们在 SketchRibbonViewModel.cs 中添加如下代码:

[RelayCommand]
void AddPier()
{
    MainViewModel.ActiveViewModel.RenderView.SetEditor(new AddPierEditor());
}

这样就将命令与 Editor 进行了关联。还记得我们添加按钮时在 xaml 中写的 Binding 吗,绑定到的就是这个命令。

效果放置桥墩Editor实现“创建直线梁”功能

我们注意到,该例子中,桥梁的梁体有直线和圆弧两种,本段落介绍直线梁的创建工具实现。

设计思路

直线梁的创建和桥墩的放置比较相似,都是“先点一个点——再点一个点”,我们这回用第一个点表示线段起点,另一个点表示线段终点。

同样也使用一个变量对操作阶段进行标记。

相信读者心中对代码已经有个大致的思路了,附代码:

using AnyCAD.Foundation;
namespace Rapid.Sketch.Plugin
{
    class AddLineBeamEditor : Editor
    {
        public AddLineBeamEditor()
        {
            SetName("AddLineBeam");
        }

        TopoShape mContour;
        BrepSceneNode mNode;
        BasicMaterial mShapeMaterial;
        public override EnumEditorCode Start(ViewContext ctx)
        {
            var tmp = ctx.GetTempContext();
            tmp.Start();
            tmp.SetPickFilter(EnumShapeFilter.Edge);
            var fileName = DialogUtil.OpenFileDialog("DXF"new StringList { "DXF Files(.brep)""*.dxf" });
            if (fileName.IsEmpty())
                return EnumEditorCode.Failed;
            var dxfShapes = DxfIO.Load(fileName.GetString());
            var wires = CurveBuilder.ConnectEdgesToWires(dxfShapes, 1.0e-7false);
            var outter = CurveBuilder.MakePlanarFace(wires[1]);
            var inner = CurveBuilder.MakePlanarFace(wires[0]);
            mContour = BooleanTool.Cut(outter, inner);

            mShapeMaterial = BasicMaterial.Create("temp");
            mShapeMaterial.SetColor(ColorTable.Green);
            mNode = BrepSceneNode.Create(mContour, null, mShapeMaterial);
            if (mNode == null)
                return EnumEditorCode.Failed;
            mNode.SetVisible(false);
            ctx.GetScene().AddNode(mNode);
            return base.Start(ctx);
        }

        public override void Finish(ViewContext ctx)
        {
            ctx.GetScene().RemoveNode(mNode.GetUuid());
            ctx.RequestUpdate(EnumUpdateFlags.Scene);
            base.Finish(ctx);
        }

        Vector3? mStartPt = null;
        public override EnumEditorCode OnMouseMove(ViewContext ctx, InputEvent evt)
        {
            if (mNode == null)
                return EnumEditorCode.Ignored;
            var pick = ctx.SnapPoint(evt.GetX(), evt.GetY());
            var pt = pick.GetPosition();
            if (mStartPt == null)
            {
                //do nothing
            }
            else
            {
                mNode.SetVisible(true);
                var offset = pt - mStartPt;
                if (offset.length() < 0.01)
                    return EnumEditorCode.Ignored;
                var dir = offset.ToDir();
                var zdir = new GDir(0.00.01.0);
                var axis = new GAx3(mStartPt.ToPnt(), dir, zdir.Crossed(dir));
                var worldShape = TransformTool.Transform(mContour, axis);
                var path = CurveBuilder.MakeLine(pt.ToPnt(), mStartPt.ToPnt());
                var beam = FeatureTool.SweepByFrenet(worldShape, path, EnumSweepTransitionMode.RightCorner, truefalsetrue);
                mNode.SetTopoShape(beam);
                ctx.RequestUpdate(EnumUpdateFlags.Scene);
            }
            return base.OnMouseMove(ctx, evt);
        }

        public override EnumEditorCode OnMouseUp(ViewContext ctx, InputEvent evt)
        {
            if (mStartPt == null)
            {
                var pick = ctx.SnapPoint(evt.GetX(), evt.GetY());
                mStartPt = pick.GetPosition();
            }
            else
            {
                // 创建好了
                mNode.SetEdgeMaterial(null);
                //重新创建个临时对象
                mNode = BrepSceneNode.Create(mNode.GetTopoShape(), null, mShapeMaterial);
                mNode.SetVisible(false);
                ctx.GetScene().AddNode(mNode);
                ctx.RequestUpdate(EnumUpdateFlags.Scene);

                return EnumEditorCode.Exit;
            }
            return base.OnMouseDown(ctx, evt);
        }
    }
}

这里对代码就不过多赘述。

注意到,在 Start 方法中,老助手直接做了这样的操作:

var outter = CurveBuilder.MakePlanarFace(wires[1]);
var inner = CurveBuilder.MakePlanarFace(wires[0]);
mContour = BooleanTool.Cut(outter, inner);

这个操作并不严谨,首先并不能保证 wires 的 size,并且也不能保证成员出现的顺序。这里只是用于示范 Editor 的实现,读者可以用 WireTreeBuilder 进行更严谨的实现。

绑定命令

同样我们对命令进行绑定

[RelayCommand]
void AddLineBeam()
{
    MainViewModel.ActiveViewModel.RenderView.SetEditor(new AddLineBeamEditor());
}

效果创建直线梁Editor其他创建工具

创建圆弧梁的 Editor 留给读者作为练习。

此外,加上隔板创建工具、桥台创建工具,完整的桥梁交互式创建就基本完成了。

结语

本文介绍了 AnyCAD 中 Editor 的定义与实现方法,以放置桥墩、创建直线梁为例,示范了 Editor 的具体实现方式。桥梁建模教程也从“数值时代”跨越到了“点击时代”。

———END———
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击查看详情
站 长 微 信: qs62318888

主题授权提示:请在后台主题设置-主题授权-激活主题的正版授权,授权购买:RiTheme官网

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注