本节主要基于第3章创建的CubeDemo对象,实现在立方体上开洞的效果。CubeDemo 对象创建见3.1.5节示例代码3-5,3-6,3-7,3-8。

对象的创建主要分为三点:

  • 一是创建一个洞口对象;
  • 二是创建CubeDemo与洞口对象的关联关系;
  • 三是在原CubeDemo对象上开洞,通过CubeDemo 对象和洞口对象的布尔实现。

对象的布置中,需要在布置洞口时拾取CubeDemo 对象,并在洞口对象布置后刷新CubeDemo 对象。

9.2.1 创建洞口对象

3.1.5节中已经讲到,对象创建主要分为创建对象数据表、实现对象类、对象注册三个步骤,本节将不再赘述创建对象数据表和对象注册两部分,主要讲解洞口对象类的实现。

本节的洞口对象几何造型类似CubeDemo 的拉伸体造型,为了实现洞口效果,设置洞口几何图素的透明度为1。

#pragma once
namespace DemoObject
{
    //定义智能指针、引用等
    class OpenningDemo;
    typedef OpenningDemo const&          OpenningDemoCR;
    typedef OpenningDemo&                OpenningDemoR;
    typedef OpenningDemo*                OpenningDemoP;
    typedef RefCountedPtr<OpenningDemo>  OpenningDemoPtr;

    class OpenningDemo : public BIMBase::Data::BPGraphicElement
    {
        DefineSuper(BPGraphicElement)

        public:
        OpenningDemo();
        ~OpenningDemo();
        int    getWidth() const;
        void   setWidth(int nWidth);
        int    getLength() const;
        void   setLength(int nLength);
        int    getHeight() const;
        void   setHeight(int nHeight);

        protected:
        virtual Utf8String _getSchemaName() const override { return PBM_SCHEMA_Demo; };
        virtual Utf8String _getClassName() const override { return PBM_CLASS_OPENNING_Demo; };

        virtual ::p3d::P3DStatus _copyToData (BPDataR data, BPProjectR project) const override;
        virtual ::p3d::P3DStatus  _initFromData(BPDataCR data) override;
        virtual BPGraphicsPtr _createPhysicalGraphics(BPProjectR project, PModelIdCR modelId, bool isDynamics) override;
        private:
        int m_nWidth;
        int m_nHeight;
        int m_nLenght;
        Demo_CREATE(OpenningDemo);
    };
    Demo_EXTENSION(OpenningDemo);
}

代码9-1:洞口OpenningDemo对象(头文件)

#include "pch.h"
#define Property_Lenght                          "Length"
#define Property_Width                           "Width"
#define Property_Height                          "Height"
using namespace DemoObject;

OpenningDemo::OpenningDemo()
{
    m_nLenght = 500;
    m_nWidth = 200;
    m_nHeight = 500;
}

OpenningDemo::~OpenningDemo()
{}

int OpenningDemo::getWidth() const
{
    return m_nWidth;
}
void OpenningDemo::setWidth(int nWidth)
{
    m_nWidth = nWidth;
}
int OpenningDemo::getLength() const
{
    return m_nLenght;
}
void OpenningDemo::setLength(int nLength)
{
    m_nLenght = nLength;
}
int OpenningDemo::getHeight() const
{
    return m_nHeight;
}
void OpenningDemo::setHeight(int nHeight)
{
    m_nHeight = nHeight;
}

::p3d::P3DStatus OpenningDemo::_copyToData(::BIMBase::Core::BPDataR data, ::BIMBase::Core::BPProjectR project) const
{
    if (T_Super::_copyToData(data, project) != P3DStatus::SUCCESS)
        return ERROR;
    P3DStatus status;
    status = data.setValue(Property_Lenght, BPValue(this->getLength()));
    if (P3DStatus::SUCCESS != status)
        return ERROR;
    status = data.setValue(Property_Width, BPValue(this->getWidth()));
    if (P3DStatus::SUCCESS != status)
        return ERROR;
    status = data.setValue(Property_Height, BPValue(this->getHeight()));
    if (P3DStatus::SUCCESS != status)
        return ERROR;
    return SUCCESS;
}

::p3d::P3DStatus  OpenningDemo::_initFromData(::BIMBase::Core::BPDataCR data)
{
    if (T_Super::_initFromData(data) != P3DStatus::SUCCESS)
        return ERROR;
    BPValue value;
    P3DStatus status;
    status = data.getValue(value, Property_Lenght);
    if (P3DStatus::SUCCESS != status)
        return ERROR;
    setLength(value.getInteger());
    status = data.getValue(value, Property_Width);
    if (P3DStatus::SUCCESS != status)
        return ERROR;
    setWidth(value.getInteger());
    status = data.getValue(value, Property_Height);
    if (P3DStatus::SUCCESS != status)
        return ERROR;
    setHeight(value.getInteger());
    return SUCCESS;
}

BIMBase::Core::BPGraphicsPtr    OpenningDemo::_createPhysicalGraphics(::BIMBase::Core::BPProjectR project, ::BIMBase::PModelIdCR modelId, bool isDynamics)
{
    BPModelPtr model = project.getModelById(modelId);
    if (model.isNull())
        return nullptr;
    BPGraphicsPtr ptrGraphic = model->createPhysicalGraphics();
    if (ptrGraphic.isNull())
        return nullptr;

    int nWidth = getWidth();
    int nLength = getLength();
    int nHeight = getHeight();

    //绘制底部外轮廓
    GeCurveArrayPtr outLines = GeCurveArray::create(GeCurveArray::BOUNDARY_TYPE_Outer);
    pvector<GePoint3d> pts;
    pts.push_back(GePoint3d::create(0, -nWidth / 2, 0));
    pts.push_back(GePoint3d::create(nLength, -nWidth / 2, 0));
    pts.push_back(GePoint3d::create(nLength, nWidth / 2, 0));
    pts.push_back(GePoint3d::create(0, nWidth / 2, 0));
    pts.push_back(GePoint3d::create(0, -nWidth / 2, 0));
    IGeCurveBasePtr pLine = IGeCurveBase::createLineString(pts);
    outLines->push_back(pLine);

    //向Z方向拉伸
    GeVec3d veccc = GeVec3d::create(0, 0, nHeight);
    GeExtrusionInfo extrData(outLines, veccc, true);
    IGeSolidBasePtr extrusion = IGeSolidBase::createGeExtrusion(extrData);

    //设置洞口颜色为白色,透明度为1
    BPSymbology symb;
    symb.style = 0;  //线型
    symb.weight = 0;  //线宽
    symb.color = BPColorUtil::getEntityColor(RGB(255, 255, 255), project,
 true);
    ptrGraphic->addGeSolidBase(*extrusion, symb, 1);
    return ptrGraphic;
}

代码9-2:洞口OpenningDemo对象(源文件)

从洞口对象源文件的_createPhysicalGraphics接口实现中我们可以发现,洞口对象几何构造基本和CubeDemo对象相同,只在调用addGeSolidBase接口时将几何图素透明度设置为1。

9.2.2 创建立方体对象与洞口对象的关联关系

在实际应用场景中,存在许多构件联动的需求,比如墙与门窗的联动等,这些在schema中通过关联关系实现。

本章开洞范例中,需要将立方体与洞口对象进行关联,才能实现在洞口编辑时刷新立方体,或者立方体编辑时联动洞口。

首先,在3.1中设置立方体的schema文件的基础上,模仿CubeDemo类的创建方法,增加一个新的classes类并完成属性添加,将其命名为OpenningDemo;对Relationship Classes右键添加引用类,点击关联约束选项,即可查看Source与Target区块,其中source代表引用源类,本例为CubeDemo类,Target代表目标源,本例为OpenningDemo文件,保存schema文件后,即完成了相关对象的关联。

9.2.3 在立方体对象中实现关联洞口开洞

示例代码3-6和示例代码3-7已经详细讲解了CubeDemo对象的创建,本节将在其基础上进行补充,实现CubeDemo和洞口对象的布尔操作。

#pragma once
namespace DemoObject
{
    //定义智能指针、引用等
    class CubeDemo;
    typedef CubeDemo const& CubeDemoCR;
    typedef CubeDemo& CubeDemoR;
    typedef CubeDemo* CubeDemoP;
    typedef RefCountedPtr<CubeDemo>  CubeDemoPtr;

    class CubeDemo : public BIMBase::Data::BPGraphicElement
    {
        DefineSuper(BPGraphicElement)
    public:
        CubeDemo();
        ~CubeDemo();

        int getWidth() const;
        void   setWidth(int nWidth);

        int getLength() const;
        void   setLength(int nLength);

        int getHeight() const;
        void   setHeight(int nHeight);

        //获取起点
        GePoint3d    getStartPoint() const;
        //获取中点
        GePoint3d    getMiddlePoint() const;
        //获取终点
        GePoint3d    getEndPoint() const;
    protected:
        virtual Utf8String     _getSchemaName() const override { return PBM_SCHEMA_Demo; };
        virtual Utf8String     _getClassName() const override { return PBM_CLASS_CUBE_Demo; };
        //写数据
        virtual ::p3d::P3DStatus _copyToData(BIMBase::Core::BPDataR instance, BIMBase::Core::BPProject& project) const override;
        //读数据
        virtual ::p3d::P3DStatus _initFromData(BIMBase::Core::BPDataCR    instance) override;
        //创建几何造型
        virtual BIMBase::Core::BPGraphicsPtr _createPhysicalGraphics(BIMBase::Core::BPProjectR project, BIMBase::PModelIdCR modelId, bool bIsDynamics) override;
    private:
        int m_nWidth;
        int m_nHeight;
        int m_nLenght;

        Demo_CREATE(CubeDemo);
    };
    Demo_EXTENSION(CubeDemo);
}

代码9-4:CubeDemo头文件

#include "pch.h"
#include "CubeDemo.h"

#define Property_Lenght                          "Length"
#define Property_Width                           "Width"
#define Property_Height                          "Height"

using namespace DemoObject;

CubeDemo::CubeDemo()
{
    m_nLenght = 1000;
    m_nWidth = 200;
    m_nHeight = 3000;
}

CubeDemo::~CubeDemo()
{

}

int CubeDemo::getWidth() const
{
    return m_nWidth;
}

void CubeDemo::setWidth(int nWidth)
{
    m_nWidth = nWidth;
}

int CubeDemo::getLength() const
{
    return m_nLenght;
}

void CubeDemo::setLength(int nLength)
{
    m_nLenght = nLength;
}

int CubeDemo::getHeight() const
{
    return m_nHeight;
}

void CubeDemo::setHeight(int nHeight)
{
    m_nHeight = nHeight;
}

::p3d::P3DStatus CubeDemo::_copyToData(BIMBase::Core::BPDataR instance, BIMBase::Core::BPProject& project) const
{
    if (T_Super::_copyToData(instance, project) != P3DStatus::SUCCESS)
        return ERROR;

    P3DStatus status;
    status = instance.setValue(Property_Lenght, BPValue(this->getLength()));
    if (P3DStatus::SUCCESS != status)
        return ERROR;

    status = instance.setValue(Property_Width, BPValue(this->getWidth()));
    if (P3DStatus::SUCCESS != status)
        return ERROR;

    status = instance.setValue(Property_Height, BPValue(this->getHeight()));
    if (P3DStatus::SUCCESS != status)
        return ERROR;

    return SUCCESS;
}

::p3d::P3DStatus CubeDemo::_initFromData(BIMBase::Core::BPDataCR instance)
{
    if (T_Super::_initFromData(instance) != P3DStatus::SUCCESS)
        return ERROR;

    BPValue value;
    P3DStatus status;

    status = instance.getValue(value, Property_Lenght);
    if (P3DStatus::SUCCESS != status)
        return ERROR;
    setLength(value.getInteger());

    status = instance.getValue(value, Property_Width);
    if (P3DStatus::SUCCESS != status)
        return ERROR;
    setWidth(value.getInteger());

    status = instance.getValue(value, Property_Height);
    if (P3DStatus::SUCCESS != status)
        return ERROR;
    setHeight(value.getInteger());

    return SUCCESS;
}
BIMBase::Core::BPGraphicsPtr DemoObject::CubeDemo::_createPhysicalGraphics(BIMBase::Core::BPProjectR project, BIMBase::PModelIdCR modelId, bool isDynamics)
{
    BPModelPtr ptrModel = project.getModelById(modelId);
    if (ptrModel.isNull())
        return nullptr;

    BPGraphicsPtr ptrGraphic = ptrModel->createPhysicalGraphics();
    if (ptrGraphic.isNull())
        return nullptr;

    int nWidth = getWidth();
    int nLength = getLength();
    int nHeight = getHeight();

    //绘制底部外轮廓
    GeCurveArrayPtr outLines = GeCurveArray::create(GeCurveArray::BOUNDARY_TYPE_Outer);
    pvector<GePoint3d> pts;

    pts.push_back(GePoint3d::create(0, -nWidth / 2, 0));
    pts.push_back(GePoint3d::create(nLength, -nWidth / 2, 0));
    pts.push_back(GePoint3d::create(nLength, nWidth / 2, 0));
    pts.push_back(GePoint3d::create(0, nWidth / 2, 0));
    pts.push_back(GePoint3d::create(0, -nWidth / 2, 0));
    IGeCurveBasePtr pLine = IGeCurveBase::createLineString(pts);
    outLines->push_back(pLine);

    //向Z方向拉伸
    GeVec3d veccc = GeVec3d::create(0, 0, nHeight);
    GeExtrusionInfo extrData(outLines, veccc, true);
    IGeSolidBasePtr extrusion = IGeSolidBase::createGeExtrusion(extrData);

    ptrGraphic->addGeSolidBase(*extrusion);

    //---------------以下为创建洞口代码-------------------
    if (getData(project) == nullptr)
        return ptrGraphic;

    //获取cube关联的openning对象,用于布尔计算
    BPDataKeyArray relDataIds;
    BPRelationshipFinder::getRelatedDatasByRelationship(relDataIds, getDataKey(), project, PBM_SCHEMA_Demo, PBM_RELSHIP_CUBEWITHOPENNING);
    if (relDataIds.size() == 0)
        return ptrGraphic;

    BPGraphicsPtr ptrGraphicResult = ptrModel->createPhysicalGraphics();
    if (ptrGraphicResult.isNull())
        return nullptr;

    for each (auto dataKey in relDataIds)
    {
        BPDataPtr ptrData = BPDataUtil::getDataByKey(dataKey, project);
        if (ptrData == nullptr)
            continue;
        OpenningDemoPtr ptrOpenning = OpenningDemo::create(*ptrData);
        if (ptrOpenning == nullptr)
            continue;
        //如果当前宽度不一致,修改洞口宽度
        if (abs(ptrOpenning->getWidth() - getWidth()) > 0.01)
        {
            ptrOpenning->setWidth(getWidth());
            ptrOpenning->replaceInProject(project);
        }
        //获取洞口图素,返回局部坐标系下图素
        BPGraphicsPtr ptrGraphicOpenning = ptrOpenning->createPhysicalGraphics(project, ptrModel->getModelId(), true);
        GeTransform openningTrans = ptrOpenning->getTransform();
        GeTransform cubeTrans = getTransform();
        cubeTrans.setByInverse(cubeTrans);

        BPGraphicsPtr ptrGraphicResultTemp = ptrModel->createPhysicalGraphics();
        if (ptrGraphicResultTemp.isNull())
            continue;

        //当前cube为局部坐标系下图素,需要将openning的图素转到世界坐标系下,再转到cube的局部坐标系下进行
        BPGraphicsUtils::transformPhysicalGraphics(*ptrGraphicOpenning, GeTransform::createByProduct(cubeTrans, openningTrans));
        BPSolidBooleanUtil::doBoolean(ptrGraphicResultTemp, ptrGraphic, ptrGraphicOpenning, BPBooleanOp::Substract);
        ptrGraphic = ptrModel->createPhysicalGraphics();
        if (ptrGraphic.isNull())
            continue;
        BPGraphicsUtils::copyPhysicalGraphics(*ptrGraphic, *ptrGraphicResultTemp);
        ptrGraphicResult = ptrGraphicResultTemp;
    }
    return ptrGraphicResult;
}
GePoint3d DemoObject::CubeDemo::getStartPoint() const
{
    //通过转换矩阵获取原点
    return getPlacement().getOrigin();
}

GePoint3d DemoObject::CubeDemo::getMiddlePoint() const
{
    GePoint3d ptMiddle = GePoint3d::create(getLength() / 2.0, 0, 0);
    //墙局部坐标系转到世界坐标系下
    GeTransform trans = getPlacement().toTransform();
    trans.multiply(ptMiddle);
    return ptMiddle;
}

GePoint3d DemoObject::CubeDemo::getEndPoint() const
{
    GePoint3d ptEnd = GePoint3d::create(getLength(), 0, 0);
    //墙局部坐标系转到世界坐标系下
    GeTransform trans = getPlacement().toTransform();
    trans.multiply(ptEnd);
    return ptEnd;
}

代码9-5:CubeDemo源文件

示例代码9-4为立方体的造型代码,前半部分为原有造型代码,即通过拉伸体造型创建立方体;增加的代码中,通过关联关系获取到与立方体关联的洞口对象,再获取洞口对象的几何图素,分别与立方体进行布尔操作,最终得到一个带洞的立方体。

9.2.4 布置洞口对象

本节通过2.3.2中所讲的基本工具BPPrimitiveTool实现洞口对象的布置。与普通布置工具不同的是,布置洞口时需要拾取CubeDemo对象,在洞口对象布置后需添加CubeDemo 与洞口对象的关联关系并刷新CubeDemo 对象。

class ToolLayoutOpenningDemo :public BPPrimitiveTool
{
    DefineSuper(BPPrimitiveTool)
public:
    ToolLayoutOpenningTest();
    ~ToolLayoutOpenningTest();

protected:
    virtual ::p3d::Utf8CP _getToolName() const { return "layoutOpenning"; }
    virtual void _onPostInstall() override;
    virtual void _onRestartTool() override;
    virtual bool _onDataButton(BPBaseButtonEventCP) override;
    virtual bool _onResetButton(BPBaseButtonEventCP) override;
    virtual void _onDynamicFrame(BPBaseButtonEventCP) override;
    virtual bool _onModelMotion(BPBaseButtonEventCP ev) override;

private:
    GePoint3d m_ptC;
    TestObject::OpenningTestPtr m_ptrOpenning;
    TestObject::CubeTestPtr m_ptrCube;
};

代码9-5:洞口布置工具(头文件)

#pragma once
class ToolLayoutOpenningDemo :public BPPrimitiveTool
{
    DefineSuper(BPPrimitiveTool)
public:
    ToolLayoutOpenningDemo();
    ~ToolLayoutOpenningDemo();

protected:
    virtual ::p3d::Utf8CP _getToolName() const { return "layoutOpenning"; }
    virtual void _onPostInstall() override;
    virtual void _onRestartTool() override;
    virtual bool _onDataButton(BPBaseButtonEventCP) override;
    virtual bool _onResetButton(BPBaseButtonEventCP) override;
    virtual void _onDynamicFrame(BPBaseButtonEventCP) override;
    virtual bool _onModelMotion(BPBaseButtonEventCP ev) override;

private:
    GePoint3d m_ptC;
    DemoObject::OpenningDemoPtr m_ptrOpenning;
    DemoObject::CubeDemoPtr m_ptrCube;
};

代码9-5:洞口布置工具(头文件)

#include "pch.h"
#include "ToolLayoutOpenningDemo.h"
using namespace DemoObject;

ToolLayoutOpenningDemo::ToolLayoutOpenningDemo()
{
    m_ptrCube = CubeDemo::create();
    m_ptrOpenning = OpenningDemo::create();
}

ToolLayoutOpenningDemo::~ToolLayoutOpenningDemo()
{}

void ToolLayoutOpenningDemo::_onPostInstall()
{
    T_Super::_onPostInstall();
    BPSnap::getInstance().enableLocate(false);
    BPSnap::getInstance().enableSnap(true);
}

void ToolLayoutOpenningDemo::_onRestartTool()
{
    ToolLayoutOpenningDemo* newTool = new ToolLayoutOpenningDemo();
    newTool->installTool();
}

bool ToolLayoutOpenningDemo::_onDataButton(BPBaseButtonEventCP ev)
{
    BPViewportP viewPort = BPViewManager::getInstance().getActivedViewport();
    if (viewPort == nullptr)
        return false;
    int nViewIndex = viewPort->getViewNumber();
    BPViewportP vp = BPViewManager::getInstance().getViewport(nViewIndex);
    if (NULL == vp)
        return false;

    BIMBase::Core::BPProjectP pProject = ev->getViewport()->getTargetModel()->getBPProject();
    if (pProject == nullptr)
        return false;
    BPModelP pModel = ev->getViewport()->getTargetModel();
    if (pModel == nullptr)
        return false;
    if (m_ptrCube.isNull())
        return false;
    if (!m_ptrCube->getDataKey().isValid())
        return false;
    if (m_ptrOpenning.isNull())
        return false;
    //增加构件到工程中
    if (SUCCESS != m_ptrOpenning->addToProject(*pProject, pModel->getModelId()))
    {
        AfxMessageBox(L"Can not add to project!");
        return false;
    }
    //与CubeDemo建立关联关系
    BPDataId relationId;
    BPRelationshipInserter::addRelationship(&relationId, *pProject, *m_ptrCube->getData(*pProject), *m_ptrOpenning->getData(*pProject), PBM_SCHEMA_Demo, PBM_RELSHIP_CUBEWITHOPENNING);
    //刷新CubeDemo对象的图素
    m_ptrCube->replaceGraphics(*pProject);
    return true;
}

bool ToolLayoutOpenningDemo::_onResetButton(BPBaseButtonEventCP)
{
    _exitTool();
    return true;
}

void ToolLayoutOpenningDemo::_onDynamicFrame(BPBaseButtonEventCP ev)
{
    m_ptrCube == nullptr;
    m_ptrOpenning = OpenningDemo::create();
    if (NULL == ev)
        return;
    //获取点击点所在的工程和模型ID
    BPProjectP pProject = ev->getViewport()->getTargetModel()->getBPProject();
    if (pProject == nullptr)
        return;
    BPModelP pModel = ev->getViewport()->getTargetModel();
    if (pModel == nullptr)
        return;

    //获取鼠标当前拾取到的entity
    int nErrorCode = 0;
    p3d::PString reasonDesc = L"";
    BIMBase::Core::BPPickDataPtr path = BPEntityLocateManager::getInstance().doLocatePickData(nErrorCode, reasonDesc, true, ev->getPoint(), ev->getViewport());
    if (path == nullptr)
        return;
    BPEntityP element = path->getEntity();
    if (element == nullptr)
        return;
    if (element->getClassId() != BIMBase::Core::BPSchemaManager::getClassIdByClassName(*pProject, PBM_SCHEMA_Demo, PBM_CLASS_CUBE_Demo))
        return;

    //将entity转为cube对象
    BPDataPtr ptrCubeData = BPDataUtil::getDataOnEntity(*element);
    if (ptrCubeData == nullptr)
        return;
    m_ptrCube = CubeDemo::create(*ptrCubeData);
    if (m_ptrCube == nullptr)
        return;

    //获取Cube对象端点,将鼠标点投影到cube基线上
    GeSegment3d segment = GeSegment3d::create(m_ptrCube->getStartPoint(), m_ptrCube->getEndPoint());
    GePoint3d projectPoint = GePoint3d::createByZero();
    double dPara = 0;
    segment.projectPointToSegment(projectPoint, dPara, *ev->getPoint());

    //获取cube对象的方向
    BPPlacement placement = m_ptrCube->getPlacement();
    placement.setOrigin(projectPoint);

    //使洞口与cube宽度一致、延申方向一致
    m_ptrOpenning->setWidth(m_ptrCube->getWidth());
    m_ptrOpenning->setPlacement(placement);

    //获取洞口图素用于动态展示,注意createPhysicalGraphics获取的为局部坐标系图素
    BPGraphicsPtr ptrGraphic = pModel->createPhysicalGraphics();
    ptrGraphic = m_ptrOpenning->createPhysicalGraphics(*pProject, pModel->getModelId(), true);
    BPGraphicsUtils::transformPhysicalGraphics(*ptrGraphic, placement.toTransform());

    BPRedrawEntitys redrawElems;
    redrawElems.setDrawMode(BPDrawMode::enTempDraw);
    redrawElems.setDrawPurpose(BPDrawPurpose::enFlash);
    redrawElems.setDynamicsViews(ev->getViewport());
    redrawElems.doRedraw(ptrGraphic->getEntityR());
}

bool ToolLayoutOpenningDemo::_onModelMotion(BPBaseButtonEventCP ev)
{
    if (!getDynamicsStarted())
        _beginDynamics();
    return true;
}
//对工具进行注册
BPTool* CreateOpenningTool()
{
    ToolLayoutOpenningDemo* tool = new ToolLayoutOpenningDemo();
    return tool;
}
AutoDoRegisterFunctionsBegin
BPToolsManager::registerTool("layoutOpenning", &CreateOpenningTool);
AutoDoRegisterFunctionsEnd

代码9-6:洞口布置工具(源文件)

下图为立方体开洞效果。



图9-1 立方体开洞