【CAD开发】3dxml文件格式读取(Python、C++、C#)

【CAD开发】3dxml文件格式读取(Python、C++、C#),第1张

🍺相关文章汇总如下🍺:

  • 🎈【CAD开发】3dxml文件格式开发准备🎈
  • 🎈【CAD开发】3dxml文件格式读取(Python、C++、C#)🎈

文章目录
  • 1、简介
  • 2、文件格式
  • 3、代码实现(Python)
    • 3.1 blender(个人测试代码)
    • 3.2 FreeCAD
    • 3.3 trimesh
  • 4、代码实现(C++)
    • 4.1 CAD Exchanger
    • 4.2 个人测试代码
  • 5、代码实现(C#)
    • 5.1 xsd文件验证
    • 5.2 xsd文件读取
  • 结语

1、简介

3D XML 格式用于所有 Dassault Systèmes 品牌 - CATIA、DELMIA、ENOVIA、
SIMULIA、3DVIA、SolidWorks 和 Virtools。 3D XML 格式规范是“3D For All”的一部分
将 3D 带给大众的倡议。 Dassault Systemes 目前提供免费的 3D XML 播放器
用于 3DXML 格式以及从其他来源 (3D PrintScreen) 捕获 3D 的工具。
该产品将继续增长。

3DXML格式是CATIA中基于可扩展标记语言XML的一种文件格式,是一种通用的、轻量的三维文件格式。 该格式可使用户轻松快捷地捕获并共享实时的、精确的 3D 数据。 3D XML 高度压缩复杂数据,提供快速的文件传输和缩短加载时间,同时保持交换文件的精确几何图形。与现有格式不同,3D XML 完全基于标准 XML。 因此,任何软件程序都将能够使用标准工具读取、写入和丰富 3D XML 内容。

2、文件格式

https://www.3ds.com/3ds-passport/downloads/






  • Sample.3dxml:

<Model_3dxml xmlns="http://www.3ds.com/xsd/3DXML" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink">
	<Header>
		<SchemaVersion>4.0SchemaVersion>
		<Title>Quad 3DXml FileTitle>
		<Author>PVOAuthor>
		<Generator>CATIA V5Generator>
		<Created>2007-04-19Created>
	Header>
	<ProductStructure root="1">
		<Reference3D xsi:type="Reference3DType" id="1" name="Quad"/>
		<Instance3D xsi:type="Instance3DType" id="2" name="Chassis.1">
			<IsAggregatedBy>1IsAggregatedBy>
			<IsInstanceOf>3IsInstanceOf>
			<RelativeMatrix>1 -1.43656787854329e-016 0 1.43656787854329e-016 1 0 0 0 1 -35.3964099358518 202.211474837735 0RelativeMatrix>
		Instance3D>
		<Reference3D xsi:type="Reference3DType" id="3" name="Chassis"/>
		<ReferenceRep xsi:type="ReferenceRepType" id="4" name="Chassis_ReferenceRep" format="UVR" version="1.0" associatedFile="urn:3DXML:Chassis.3DRep">
			<PLM_ExternalID>Chassis_ReferenceRep_ReferenceRepPLM_ExternalID>
			<V_discipline>DesignV_discipline>
			<V_usage>3DShapeV_usage>
			<V_nature>1V_nature>
		ReferenceRep>
		<InstanceRep xsi:type="InstanceRepType" id="5" name="Chassis_InstanceRep">
			<IsAggregatedBy>3IsAggregatedBy>
			<IsInstanceOf>4IsInstanceOf>
		InstanceRep>
		<Instance3D xsi:type="Instance3DType" id="6" name="System Direction.1">
			<IsAggregatedBy>1IsAggregatedBy>
			<IsInstanceOf>7IsInstanceOf>
			<RelativeMatrix>0.999976282702582 -0.0068872356451857 4.18212442991818e-006 0.00688723564518346 0.999975545259153 -0.00121444177743552 4.18212454184191e-006 0.00121444177743528 0.999999262556567 -33.892135493604 202.211474837734 8.53090922113019RelativeMatrix>
		Instance3D>
		<Reference3D xsi:type="Reference3DType" id="7" name="System Direction"/>
		<ReferenceRep xsi:type="ReferenceRepType" id="8" name="System Direction_ReferenceRep" format="UVR" version="1.0" associatedFile="urn:3DXML:System_Direction.3DRep">
			<PLM_ExternalID>System Direction_ReferenceRep_ReferenceRepPLM_ExternalID>
			<V_discipline>DesignV_discipline>
			<V_usage>3DShapeV_usage>
			<V_nature>1V_nature>
		ReferenceRep>
		<InstanceRep xsi:type="InstanceRepType" id="9" name="System Direction_InstanceRep">
			<IsAggregatedBy>7IsAggregatedBy>
			<IsInstanceOf>8IsInstanceOf>
		InstanceRep>
		<Instance3D xsi:type="Instance3DType" id="10" name="Axle Assembly.1">
			<IsAggregatedBy>1IsAggregatedBy>
			<IsInstanceOf>11IsInstanceOf>
			<RelativeMatrix>0.595836252691761 -0.00312842317081944 0.803099852413572 0.00688723564512438 0.99997554525915 -0.00121444177735138 -0.803076413527011 0.00625474636786796 0.595843227858188 -41.6573762582876 10.0046989184395 -27.7670496220596RelativeMatrix>
		Instance3D>
		<Reference3D xsi:type="Reference3DType" id="11" name="Axle Assembly"/>
		<Instance3D xsi:type="Instance3DType" id="12" name="Wheel.1">
			<IsAggregatedBy>11IsAggregatedBy>
			<IsInstanceOf>13IsInstanceOf>
			<RelativeMatrix>1 0 0 0 1 0 0 0 1 -0.000299999999999301 -137.788525156015 0.00119999999999365RelativeMatrix>
		Instance3D>
		<Reference3D xsi:type="Reference3DType" id="13" name="Wheel"/>
		<ReferenceRep xsi:type="ReferenceRepType" id="14" name="Wheel_ReferenceRep" format="UVR" version="1.0" associatedFile="urn:3DXML:Wheel.3DRep">
			<PLM_ExternalID>Wheel_ReferenceRep_ReferenceRepPLM_ExternalID>
			<V_discipline>DesignV_discipline>
			<V_usage>3DShapeV_usage>
			<V_nature>1V_nature>
		ReferenceRep>
		<InstanceRep xsi:type="InstanceRepType" id="15" name="Wheel_InstanceRep">
			<IsAggregatedBy>13IsAggregatedBy>
			<IsInstanceOf>14IsInstanceOf>
		InstanceRep>
		<Instance3D xsi:type="Instance3DType" id="16" name="Wheel.2">
			<IsAggregatedBy>11IsAggregatedBy>
			<IsInstanceOf>13IsInstanceOf>
			<RelativeMatrix>1 0 0 0 1 0 0 0 1 -0.000299999999999301 182.211474831485 -0.00040000000000262RelativeMatrix>
		Instance3D>
		<Instance3D xsi:type="Instance3DType" id="17" name="Axle.1">
			<IsAggregatedBy>11IsAggregatedBy>
			<IsInstanceOf>18IsInstanceOf>
			<RelativeMatrix>1 0 0 0 1 0 0 0 1 0.000299999999999301 192.211474837735 0.000400000000002621RelativeMatrix>
		Instance3D>
		<Reference3D xsi:type="Reference3DType" id="18" name="Axle"/>
		<ReferenceRep xsi:type="ReferenceRepType" id="19" name="Axle_ReferenceRep" format="UVR" version="1.0" associatedFile="urn:3DXML:Axle.3DRep">
			<PLM_ExternalID>Axle_ReferenceRep_ReferenceRepPLM_ExternalID>
			<V_discipline>DesignV_discipline>
			<V_usage>3DShapeV_usage>
			<V_nature>1V_nature>
		ReferenceRep>
		<InstanceRep xsi:type="InstanceRepType" id="20" name="Axle_InstanceRep">
			<IsAggregatedBy>18IsAggregatedBy>
			<IsInstanceOf>19IsInstanceOf>
		InstanceRep>
		<Instance3D xsi:type="Instance3DType" id="21" name="Axle Assembly.2">
			<IsAggregatedBy>1IsAggregatedBy>
			<IsInstanceOf>11IsInstanceOf>
			<RelativeMatrix>0.999691727774109 -0.00691442879300021 -0.0238461756783509 0.00688742231737249 0.99997554393347 -0.00121447469382495 0.0238539898934834 0.00104986162245696 0.999714901837887 428.098150416237 10.8332393530064 -27.7669834952255RelativeMatrix>
		Instance3D>
	ProductStructure>
	<DefaultView>
		<Viewpoint xsi:type="ParallelViewpointType" visualizedHeight="336.4707284" targetDistance="1401.501343" nearPlaneDistance="717.5784302" farPlaneDistance="2069.004395">
			<Position>-654.7354126 -704.961853 678.0846558Position>
			<Sight>0.5839541554 0.6828992367 -0.4389148355Sight>
			<Right>0.6628675461 -0.7132402062 -0.2278049141Right>
			<Up>0.4686194956 0.1579147726 0.8691712618Up>
		Viewpoint>
		<DefaultViewProperty>
			<OccurenceId>
				<id>urn:3DXML:Sample.3dxml#6id>
			OccurenceId>
			<GraphicProperties xsi:type="GraphicPropertiesType">
				<SurfaceAttributes xsi:type="SurfaceAttributesType">
					<Color xsi:type="RGBAColorType" red="1" green="1" blue="1"/>
				SurfaceAttributes>
			GraphicProperties>
		DefaultViewProperty>
	DefaultView>
Model_3dxml>

3、代码实现(Python) 3.1 blender(个人测试代码)

https://www.blender.org/download/

  • import_3dxml.py(版本4.3,个人实践的)
    这个脚本作者经过编写和测试,运行成功。
#***************************************************************
#   Purpose:   blender通过python实现3dxml((版本4.3))文件格式的导入
#   Author:    爱看书的小沐
#   Date:      2022-5-1
#   Languages: python
#   Platform:  blender-3.1.2, python3.10
#   OS:        Win10 win64
# **************************************************************
################################################################
# This addon enables blender to import 3D XML 4.3 documents.
# It partially implemented the 3D XML specification 4.3 from
# Dassault Syst¨¨mes. Testing was done with files exported
################################################################

import xml.etree.ElementTree as etree
import pathlib,zipfile,time,os,tempfile,math
import bpy,bmesh,bpy_extras,mathutils

bl_info={
	"name":"3D XML (4.3) import",
	"description":"Import 3D XML 4.3",
	"author":"Chris Xiong",
	"version":(0,1),
	"blender":(3,1,2),
	"category":"Import-Export",
	"support":"TESTING"
}

unitfactor=1
meshes = dict()
meshmat = dict()
NS="{http://www.3ds.com/xsd/3DXML}"
XSI="{http://www.w3.org/2001/XMLSchema-instance}"
texdir="/3dxml/Textures/"

def unflatten2Strips(seq):
    seq_j = []
    seq=seq.strip()
    seqArr=seq.split(',')
    for i in range(len(seqArr)):
        seq=seqArr[i].replace(' ',',')
        seq=seq.split(',')
        seq=list(map(int,seq))
        
        for j in range(0, len(seq)-2):
            if j % 2 == 0 :
                seq_j.append(seq[j])
                seq_j.append(seq[j+1])
                seq_j.append(seq[j+2])
            else :
                seq_j.append(seq[j+1])
                seq_j.append(seq[j])
                seq_j.append(seq[j+2])
        # print(seq_j)

    nseq=len(seq_j)
    step = 3
    fac = 1
    return [tuple(seq_j[i+j]*fac for j in range(0,step))for i in range(0,nseq,step)]

def unflatten2Fans(seq):
    seq_j = []
    seq=seq.strip()
    seqArr=seq.split(',')
    for i in range(len(seqArr)):
        seq=seqArr[i].replace(' ',',')
        seq=seq.split(',')
        seq=list(map(int,seq))
        
        for j in range(1, len(seq)-1):
            seq_j.append(seq[0])
            seq_j.append(seq[j])
            seq_j.append(seq[j+1])
        # print(seq_j)

    nseq=len(seq_j)
    step = 3
    fac = 1
    return [tuple(seq_j[i+j]*fac for j in range(0,step))for i in range(0,nseq,step)]

def unflattenXmlNode(seqNode,func,step,fac=1):
    if seqNode == None:
        return []

    seq = seqNode.text
    seq=seq.strip()
    seq=seq.replace(' ',',')
    seq=seq.split(',')
    seq=list(map(func,seq))
    nseq=len(seq)
    return [tuple(seq[i+j]*fac for j in range(0,step))for i in range(0,nseq,step)]

def unflatten(seq,func,step,fac=1):
    seq=seq.strip()
    seq=seq.replace(' ',',')
    seq=seq.split(',')
    seq=list(map(func,seq))
    nseq=len(seq)
    return [tuple(seq[i+j]*fac for j in range(0,step))for i in range(0,nseq,step)]

def create_mesh(verts,faces,facemat,norms,uvs,meshidx):

    meshname=f"Mesh_{meshidx}"
    mesh=bmesh.new()
    for vert,norm in zip(verts,norms):
        v=mesh.verts.new(vert)
        v.normal=norm
    mesh.verts.ensure_lookup_table()
    mesh.verts.index_update()
    for i,m in zip(faces,facemat):
        f=[mesh.verts[j]for j in i]
        try:
            nf=mesh.faces.new(f)
            if m!=-1:nf.material_index=m
        except ValueError:
            pass
    uv=mesh.loops.layers.uv.new()
    msh=bpy.data.meshes.new(meshname)

    mesh.to_mesh(msh)
    meshes[meshidx]=msh
    mesh.free()
	
def load_meshes(tree, objname, bpy_collection, bpy_node):
    for rep in tree.findall(f".//{NS}Rep[@{XSI}type='PolygonalRepType']"):
        print("PolygonalRepType:", rep.attrib["id"])
        rid=rep.attrib["id"]
        verts=unflattenXmlNode(rep.find(f".//{NS}Positions"),float,3,unitfactor)
        normals=unflattenXmlNode(rep.find(f".//{NS}Normals"),float,3)
        uvs=unflattenXmlNode(rep.find(f".//{NS}TextureCoordinates"),float,2)
        faces=[]
        facemat=[]
        matslots=[]
        defmat=-1
        facesel=rep.find(f".//{NS}Faces")
        if facesel.find(f"{NS}SurfaceAttributes/{NS}MaterialApplication/{NS}MaterialId") is not None:
            defmat=0
            matslots.append(facesel.find(f"./{NS}SurfaceAttributes/{NS}MaterialApplication/{NS}MaterialId").text)
        for face in facesel.findall(f"{NS}Face"):
            fmat=defmat
            if face.find(f".//{NS}MaterialId") is not None:
                matp=-1
                try:
                    matp=matslots.index(face.find(f".//{NS}MaterialId").text)
                except ValueError:
                    matp=len(matslots)
                    matslots.append(face.find(f".//{NS}MaterialId").text)
                fmat=matp
            if "triangles" in face.attrib:
                arr = unflatten(face.attrib["triangles"],int,3)
                faces.extend(arr)
                facemat.extend([fmat]*len(arr))
            if "fans" in face.attrib:
                arr = unflatten2Fans(face.attrib["fans"])
                faces.extend(arr)
                facemat.extend([fmat]*len(arr))
            if "strips" in face.attrib:
                arr = unflatten2Strips(face.attrib["strips"])
                faces.extend(arr)
                facemat.extend([fmat]*len(arr))       
        meshmat[rid]=matslots
        create_mesh(verts,faces,facemat,normals,uvs,rid)
        
        obj=bpy.data.objects.new(str(rid),meshes[rid])
        obj.parent = bpy_node
        bpy_collection.objects.link(obj)
        
def create_objects(tree, zf, xml_nodes, id, bpy_collection, bpy_node_parent):
    xml_node = xml_nodes[id]
    objname = xml_node['name']
    ref_id = xml_node['ref_id']
    mat = xml_node['matrix']
    print("======: ", id, ref_id, mat)
    
#    bpy_node = bpy.data.collections.new(objname)
    bpy_node = bpy.data.objects.new(objname, object_data=None)
    if bpy_node_parent != None:
        bpy_node.parent = bpy_node_parent
    bpy_collection.objects.link(bpy_node)
        
    if len(mat) >0:
        _wmat=mathutils.Matrix()
        for r in range(0,3):
            _wmat[r]=[mat[i] for i in range(r,12,3)]
        bpy_node.matrix_world=_wmat
    
    if ref_id != -1:
        xml_node_ref = xml_nodes[ref_id]
        associatedFile = xml_node_ref['associatedFile']
        print(associatedFile)
        if len(associatedFile) >0 :
            meshid=associatedFile.split(":")[-1]
            xml_text = zf.open(meshid)
            tree_child = etree.parse(xml_text)
            load_meshes(tree_child, objname, bpy_collection, bpy_node)

        for child_id in xml_node_ref['children']:
            create_objects(tree, zf, xml_nodes, child_id, bpy_collection, bpy_node)

    for child_id in xml_node['children']:
        create_objects(tree, zf, xml_nodes, child_id, bpy_collection, bpy_node)

def create_bpynode():
    for collection in bpy.data.collections:
        print(collection.name)
        for obj in collection.all_objects:
            print("obj: ", obj.name)
        bpy.data.collections.remove(collection)
            
    product = bpy.data.collections.get("Product")
    if product != None:
        print(dir(product.children))
        for c in product.children:
            product.children.unlink(c)
        bpy.data.collections.remove(product)
    
    product = bpy.data.collections.new("Product")
    bpy.context.scene.collection.children.link(product)
    return product

def load_objects(tree, zf):
    product = create_bpynode()
    
    # refRepArr=tree.findall(f".//{NS}ReferenceRep[@format='TESSELLATED']")
    # print(refRepArr)

    # inst3dArr=tree.findall(f".//{NS}Instance3D")
    # print(inst3dArr)

    xml_nodes = {}
    structure = tree.getroot().find(f".//{NS}ProductStructure")
    root_id = structure.attrib["root"]
    
    for child in structure:
        # print(child.attrib["id"], child.attrib["name"], child.tag)
        id = int(child.attrib["id"])
        dict = {}
        dict['id'] = id
        dict['name'] = child.attrib["name"]
        dict['children'] = []

        try:
            if child.attrib["format"] == "TESSELLATED":
                dict['associatedFile'] = child.attrib["associatedFile"]
        except KeyError:
            dict['associatedFile'] = ''

        child_attrib = child.find(f".//{NS}IsAggregatedBy")
        if child_attrib != None:
            parent_id = int(child_attrib.text)
            dict['parent_id'] = parent_id
            xml_nodes[parent_id]['children'].append(id)
        else:
            dict['parent_id'] = -1
            
        child_attrib = child.find(f".//{NS}IsInstanceOf")
        if child_attrib != None:
            dict['ref_id'] = int(child_attrib.text)
        else:
            dict['ref_id'] = -1

        child_attrib = child.find(f".//{NS}RelativeMatrix")
        if child_attrib != None:
            dict['matrix'] = mat=list(map(float,child_attrib.text.split(' ')))
        else:
            dict['matrix'] = []

        xml_nodes[id] = dict
    
    create_objects(tree, zf, xml_nodes, int(root_id), product, None)
    
def readStructXml(xml_text, zf):
    tree = etree.parse(xml_text)
    # for child in tree.getroot():
        # print(child.tag, child.attrib)
    header = tree.getroot().find(f".//{NS}Header")
    version = header.find(f".//{NS}SchemaVersion")
    print("SchemaVersion:", version.text)

    # load_materials(tree)
    # load_meshes(tree)
    load_objects(tree, zf)

def readManifestXml(xml_text):
    tree = etree.parse(xml_text)
    root = tree.getroot().find("./Root")
    return root.text

def read(filename):
    os.system("cls") if "nt" in os.name else os.system("clear")
    time1 = time.time()
    xml_text = ""
    if zipfile.is_zipfile(filename):
        zf = zipfile.ZipFile(filename, "r")
        try:
            member = zf.namelist().index("Manifest.xml")
        except ValueError:
            raise RuntimeError("not a 3D XML 4.3 file!")
        xml_text = zf.open("Manifest.xml")
    else:
        print("Warning: the file will be treated as a bare XML document.")

    filename = readManifestXml(xml_text)
    xml_text = zf.open(filename)
    readStructXml(xml_text, zf)

    bpy.ops.file.pack_all()
    time2=time.time()
    print("Total import time is: %.2f seconds."%(time2-time1))

class ImportDialog(bpy.types.Operator,bpy_extras.io_utils.ImportHelper):
	bl_idname="object.tdxml_import"
	bl_label="3DXML Import"
	filter_glob=bpy.props.StringProperty(default='*.3dxml',options={'HIDDEN'})

#	pt=bpy.props.StringProperty(name="Texture path",default=texdir)
#	uf=bpy.props.FloatProperty(name="Unit factor",default=unitfactor,min=0)
#	us=bpy.props.BoolProperty(name="Use auto smooth instead of the normal data in the 3DXML file",default=False)
#	aa=bpy.props.FloatProperty(name="Auto smooth angle",min=0,max=180,default=90)

	def execute(self,context):
		global texdir,unitfactor,usesmooth,smoothangle
		read(self.filepath)
		return {'FINISHED'}

class _3DXMLImport(bpy.types.Operator):
	"""Import a 3DXML 4.3 file"""
	bl_idname="import_mesh.tdxml"
	bl_label="Import 3DXML 4.3..."
	bl_options={'UNDO'}
	filename_ext=".3dxml"

	def execute(self,context):
		bpy.ops.object.tdxml_import('INVOKE_DEFAULT')
		return {'FINISHED'}

def menu_func_import_tdxml(self,context):
	self.layout.operator(_3DXMLImport.bl_idname,text="3DXML 4.3 (.3dxml)")

def register():
	bpy.utils.register_class(ImportDialog)
	bpy.utils.register_class(_3DXMLImport)
	bpy.types.TOPBAR_MT_file_import.append(menu_func_import_tdxml)

def unregister():
	bpy.utils.unregister_class(_3DXMLImport)
	bpy.utils.unrigister_class(ImportDialog)

if __name__=="__main__":
	register()

执行了脚本之后,在blender的菜单“导入”中增加一个“3dxml”子菜单。

导入一个测试的3dxml文件之后,场景树截图如下:

最后blender场景显示如下:

3.2 FreeCAD
  • FreeCAD 是一种通用的基于特征的参数化 3D 建模器,适用于 CAD、MCAD、CAx、CAE 和 PLM,直接针对机械工程和产品设计,但也适用于更广泛的工程用途,例如建筑或其他工程专业。它是 100% 开源且极其模块化,允许进行非常高级的扩展和定制。

  • FreeCAD 基于 OpenCasCade,这是一个强大的几何内核,具有由 Coin 3D 库提供的符合 Open Inventor 的 3D 场景表示模型和广泛的 Python API。该界面是用 Qt 构建的。FreeCAD 在 Windows、Mac OSX 和 Linux 平台上的运行方式完全相同。

  • FreeCAD is a multiplatfom (Windows, Mac and Linux), highly customizable and extensible software. It reads and writes to many open file formats such as STEP, IGES, STL, SVG, DXF, OBJ, IFC, DAE and many others, making it possible to seamlessly integrate it into your workflow.

  • FreeCAD allows you to import and export models and many other kinds of data from your models such as analyses results or quantities data to dozens of different file formats such as STEP, IGES, OBJ, STL, DWG, DXF, SVG, SHP, STL, DAE, IFC or OFF, NASTRAN, VRML, OpenSCAD CSG and many more, in addition to FreeCAD’s native FCStd file format. Add-on workbenches can also add more file formats.

  • Macro_3DXML_import.FCMacro
    https://wiki.freecad.org/Macro_3DXML_import

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# ***************************************************************************
# *   Copyright (c) 2021 heda                         *
# *                                                                         *
# *   This file is part of the FreeCAD CAx development system.              *
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of the GNU Lesser General Public License (LGPL)    *
# *   as published by the Free Software Foundation; either version 2 of     *
# *   the License, or (at your option) any later version.                   *
# *   for detail see the LICENCE text file.                                 *
# *                                                                         *
# *   This program is distributed in the hope that it will be useful,       *
# *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
# *   GNU Library General Public License for more details.                  *
# *                                                                         *
# *   You should have received a copy of the GNU Library General Public     *
# *   License along with this program; if not, write to the Free Software   *
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
# *   USA                                                                   *
# *                                                                         *
# ***************************************************************************


__Name__ = '3DXML_import'
__Comment__ = 'Imports a 3DXML-ascii file to FreeCAD'
__Author__ = 'heda @ fc-forum'
__Version__ = '0.1'
__Date__ = '2021-10-03'
__License__ = 'LGPL-2.0-or-later'
__Web__ = ''
__Wiki__ = 'https://wiki.freecadweb.org/Macro_3DXML_import'
__Icon__ = ''
__Help__ = 'Launch macro and select file.'
__Status__ = 'Stable'
__Requires__ = 'tested on FreeCAD v0.19'
__Communication__ = 'forum'
__Files__ = ''


__doc__ = """
The 3DXML file format allows for multiple types of representations.
This macro is limited to human readable content in the files,
i.e. the ascii tesselated type of geometry definition.
This type of geometry definition includes polylines as edges of geometry,
and meshes (like stl files).

The macro in current state requires a readable xml-file of type ".3DRep".

Import of edges is by default off, since that is the operation taking the
longest time.

It is recommended to run a mesh analysis after import to for example
remove duplicate points or correct flipped normals.

This macro is created using only one reference file (with only "strips" as
facets), thus it's applicability on other .3DRep files is currently unknown.
If other test files are made available, the macro can be updated.
"""

import FreeCAD as App
import Part, Mesh
from PySide import QtGui
import xml.etree.ElementTree as ET

IMPORT_EDGES, MERGE_EDGES = False, False
IMPORT_MESHES, MERGE_MESHES = True, True

filename, filt = QtGui.QFileDialog.getOpenFileName(filter='*.3DRep')

if not filename:
    raise UserWarning('Need to select a 3DRep file.')

print('Importing: {}'.format(filename))
doc = App.ActiveDocument

tree = ET.parse(filename)
root = tree.getroot()

# get namespaces from xml
ns = dict([node for _, node in
           ET.iterparse(filename, events=['start-ns'])])
if ns.get('') != 'http://www.3ds.com/xsd/3DXML':
    raise UserWarning('did not find expected namespace, file malformed?')
ns.update({'3dx':ns.get('')})

def parse_verts(vertbuffer):
    def floatify(xyz):
        return tuple((float(i) for i in xyz.split()))
    return [floatify(v) for v in vertbuffer.split(',')]

def parse_strips(strips):
    return (tuple(int(i) for i in strip.split())
            for strip in strips.split(','))

def make_mesh(element):
    Faces, VertexBuffer, SurfaceAttributes = list(element)
    Color = Faces.find('.//3dx:Color', ns)
    RGBA = tuple((float(i) for i in tuple(Color.attrib.values())[1:]))
    Face = Faces.find('.//3dx:Face', ns)
    strips = Face.get('strips')
    facets = list()
    if strips:
        Positions, Normals = list(VertexBuffer)
        facetverts = fv = parse_verts(Positions.text)
        for strip in parse_strips(strips):
            facetidx = zip(strip, strip[1:], strip[2:])
            facets += [tuple(fv[i] for i in pidx) for pidx in facetidx]
    else:
        print('face type undefined')
    return Mesh.Mesh(facets), RGBA


for BagRepType in root.findall('3dx:Root/3dx:Rep', ns):
    edges, meshes = list(), list()
    for PolygonalRepType in BagRepType:
        if PolygonalRepType.find('./3dx:Edges', ns):
            if not IMPORT_EDGES:
                continue
            Edges = PolygonalRepType.find('./3dx:Edges', ns)
            for polyline in Edges.findall('./3dx:Polyline', ns):
                verts = parse_verts(polyline.get('vertices'))
                edges.append(Part.makePolygon(verts))
                
        elif PolygonalRepType.find('./3dx:Faces', ns):
            print('  found face element')
            if not IMPORT_MESHES:
                continue
            mesh = make_mesh(PolygonalRepType)
            meshes.append(mesh)

    if edges:
        print('  creating edges')
        edgegroup = doc.addObject('App::DocumentObjectGroup','Edges')
        wires = list()
        if MERGE_EDGES:
            _edges = list()
            for _edge in edges:
                _edges += _edge.Edges
            for swire in Part.sortEdges(_edges):
                wire = Part.Wire(swire)
                obj = doc.addObject('Part::Feature', 'Edge')
                obj.Shape = wire
                wires.append(obj)
        else:
            for edge in edges:
                obj = doc.addObject('Part::Feature', 'Edge')
                obj.Shape = edge
                wires.append(obj)
        edgegroup.addObjects(wires)

    if meshes:
        print('  creating meshes')
        meshgroup = doc.addObject('App::DocumentObjectGroup','Meshes')
        meshobjs = list()
        if MERGE_MESHES:
            mesh_assembled = Mesh.Mesh()
            for mesh, RGBA in meshes:
                mesh_assembled.addMesh(mesh)
            obj = doc.addObject('Mesh::Feature', 'Mesh')
            obj.Mesh = mesh_assembled
            obj.ViewObject.ShapeColor = RGBA
            meshobjs.append(obj)
        else:
            for mesh, RGBA in meshes:
                obj = doc.addObject('Mesh::Feature', 'Mesh')
                obj.Mesh = mesh
                obj.ViewObject.ShapeColor = RGBA
                meshobjs.append(obj)
        meshgroup.addObjects(meshobjs)

doc.recompute()
print('... completed import')

# end
3.3 trimesh

https://trimsh.org/trimesh.exchange.threedxml.html

  • Import meshes from binary/ASCII STL, Wavefront OBJ, ASCII OFF, binary/ASCII PLY, GLTF/GLB 2.0, 3MF, XAML, 3DXML, etc.
  • Import and export 2D or 3D vector paths from/to DXF or SVG files
  • Import geometry files using the GMSH SDK if installed (BREP, STEP, IGES, INP, BDF, etc)
  • Export meshes as binary STL, binary PLY, ASCII OFF, OBJ, GLTF/GLB 2.0, COLLADA, etc.
  • Export meshes using the GMSH SDK if installed (Abaqus INP, Nastran BDF, etc)

安装python的第三方库trimesh,命令如下:

pip install trimesh

trimesh的内置3dxml读取文件threedxml.py的代码如下:

"""
threedxml.py
-------------

Load 3DXML files, a scene format from Solidworks.
"""
import numpy as np
import json
import collections
from .. import util

try:
    import networkx as nx
except BaseException as E:
    # create a dummy module which will raise the ImportError
    # or other exception only when someone tries to use networkx
    from ..exceptions import ExceptionModule
    nx = ExceptionModule(E)

def load_3DXML(file_obj, *args, **kwargs):
    """
    Load a 3DXML scene into kwargs. 3DXML is a CAD format
    that can be exported from Solidworks

    Parameters
    ------------
    file_obj : file object
      Open and containing 3DXML data

    Returns
    -----------
    kwargs : dict
      Can be passed to trimesh.exchange.load.load_kwargs
    """
    archive = util.decompress(file_obj, file_type='zip')

    # a dictionary of file name : lxml etree
    as_etree = {}
    for k, v in archive.items():
        # wrap in try statement, as sometimes 3DXML
        # contains non- xml files, like JPG previews
        try:
            as_etree[k] = etree.XML(v.read())
        except etree.XMLSyntaxError:
            # move the file object back to the file start
            v.seek(0)

    # the file name of the root scene
    root_file = as_etree['Manifest.xml'].find('{*}Root').text
    # the etree of the scene layout
    tree = as_etree[root_file]
    # index of root element of directed acyclic graph
    root_id = tree.find('{*}ProductStructure').attrib['root']

    # load the materials library from the materials elements
    colors = {}
    # but only if it exists
    material_key = 'CATMaterialRef.3dxml'
    if material_key in as_etree:
        material_tree = as_etree[material_key]
        for MaterialDomain in material_tree.iter('{*}MaterialDomain'):
            material_id = MaterialDomain.attrib['id']
            material_file = MaterialDomain.attrib['associatedFile'].split(
                'urn:3DXML:')[-1]
            rend = as_etree[material_file].find(
                "{*}Feature[@Alias='RenderingFeature']")
            diffuse = rend.find("{*}Attr[@Name='DiffuseColor']")
            # specular = rend.find("{*}Attr[@Name='SpecularColor']")
            # emissive = rend.find("{*}Attr[@Name='EmissiveColor']")
            rgb = (np.array(json.loads(
                diffuse.attrib['Value'])) * 255).astype(np.uint8)
            colors[material_id] = rgb

        # copy indexes for instances of colors
        for MaterialDomainInstance in material_tree.iter(
                '{*}MaterialDomainInstance'):
            instance = MaterialDomainInstance.find('{*}IsInstanceOf')
            # colors[b.attrib['id']] = colors[instance.text]
            for aggregate in MaterialDomainInstance.findall('{*}IsAggregatedBy'):
                colors[aggregate.text] = colors[instance.text]

    # references which hold the 3DXML scene structure as a dict
    # element id : {key : value}
    references = collections.defaultdict(dict)

    # the 3DXML can specify different visual properties for occurrences
    view = tree.find('{*}DefaultView')
    if view is not None:
        for ViewProp in view.iter('{*}DefaultViewProperty'):
            color = ViewProp.find('{*}GraphicProperties/' +
                                  '{*}SurfaceAttributes/{*}Color')
            if (color is None or
                    'RGBAColorType' not in color.attrib.values()):
                continue
            rgba = np.array([color.attrib[i]
                             for i in ['red',
                                       'green',
                                       'blue',
                                       'alpha']],
                            dtype=np.float64)
            rgba = (rgba * 255).astype(np.uint8)
            for occurrence in ViewProp.findall('{*}OccurenceId/{*}id'):
                reference_id = occurrence.text.split('#')[-1]
                references[reference_id]['color'] = rgba

    # geometries will hold meshes
    geometries = dict()

    # get geometry
    for ReferenceRep in tree.iter(tag='{*}ReferenceRep'):
        # the str of an int that represents this meshes unique ID
        part_id = ReferenceRep.attrib['id']
        # which part file in the archive contains the geometry we care about
        part_file = ReferenceRep.attrib['associatedFile'].split(':')[-1]

        # load actual geometry
        mesh_faces = []
        mesh_colors = []
        mesh_normals = []
        mesh_vertices = []

        if part_file not in as_etree and part_file in archive:
            # the data is stored in some binary format
            util.log.warning('unable to load binary Rep')
            # data = archive[part_file]
            continue

        # the geometry is stored in a Rep
        for Rep in as_etree[part_file].iter('{*}Rep'):
            faces = Rep.find('{*}Faces/{*}Face')
            vertices = Rep.find('{*}VertexBuffer/{*}Positions')

            if faces is None or vertices is None:
                continue

            # these are vertex normals
            normals = Rep.find('{*}VertexBuffer/{*}Normals')
            material = Rep.find('{*}SurfaceAttributes/' +
                                '{*}MaterialApplication/' +
                                '{*}MaterialId')

            (material_file, material_id) = material.attrib['id'].split(
                'urn:3DXML:')[-1].split('#')

            if 'strips' in faces.attrib:
                # triangle strips, sequence of arbitrary length lists
                # np.fromstring is substantially faster than np.array(i.split())
                # inside the list comprehension
                strips = [np.fromstring(i, sep=' ', dtype=np.int64)
                          for i in faces.attrib['strips'].split(',')]

                # convert strips to (m,3) int
                mesh_faces.append(util.triangle_strips_to_faces(strips))
            if 'triangles' in faces.attrib:
                # both triangles and strips are allowed to be defined so
                # make this an if-if instaid of an if-elif
                mesh_faces.append(
                    np.fromstring(faces.attrib['triangles'],
                                  sep=' ', dtype=np.int64).reshape((-1, 3)))
            # they mix delimiters like we couldn't figure it out from the
            # shape :(
            # load vertices into (n, 3) float64
            mesh_vertices.append(np.fromstring(
                vertices.text.replace(',', ' '),
                sep=' ',
                dtype=np.float64).reshape((-1, 3)))

            # load vertex normals into (n, 3) float64
            mesh_normals.append(np.fromstring(
                normals.text.replace(',', ' '),
                sep=' ',
                dtype=np.float64).reshape((-1, 3)))

            # store the material information as (m,3) uint8 FACE COLORS
            mesh_colors.append(np.tile(colors[material_id],
                                       (len(mesh_faces[-1]), 1)))

        # save each mesh as the kwargs for a trimesh.Trimesh constructor
        # aka, a Trimesh object can be created with trimesh.Trimesh(**mesh)
        # this avoids needing trimesh- specific imports in this IO function
        mesh = dict()
        (mesh['vertices'],
         mesh['faces']) = util.append_faces(mesh_vertices,
                                            mesh_faces)
        mesh['vertex_normals'] = np.vstack(mesh_normals)
        mesh['face_colors'] = np.vstack(mesh_colors)

        # as far as I can tell, all 3DXML files are exported as
        # implicit millimeters (it isn't specified in the file)
        mesh['metadata'] = {'units': 'mm'}
        mesh['class'] = 'Trimesh'

        geometries[part_id] = mesh
        references[part_id]['geometry'] = part_id

    # a Reference3D maps to a subassembly or assembly
    for Reference3D in tree.iter('{*}Reference3D'):
        references[Reference3D.attrib['id']] = {
            'name': Reference3D.attrib['name'],
            'type': 'Reference3D'}

    # a node that is the connectivity between a geometry and the Reference3D
    for InstanceRep in tree.iter('{*}InstanceRep'):
        current = InstanceRep.attrib['id']
        instance = InstanceRep.find('{*}IsInstanceOf').text
        aggregate = InstanceRep.find('{*}IsAggregatedBy').text

        references[current].update({'aggregate': aggregate,
                                    'instance': instance,
                                    'type': 'InstanceRep'})

    # an Instance3D maps basically to a part
    for Instance3D in tree.iter('{*}Instance3D'):
        matrix = np.eye(4)
        relative = Instance3D.find('{*}RelativeMatrix')
        if relative is not None:
            relative = np.array(relative.text.split(),
                                dtype=np.float64)

            # rotation component
            matrix[:3, :3] = relative[:9].reshape((3, 3)).T
            # translation component
            matrix[:3, 3] = relative[9:]

        current = Instance3D.attrib['id']
        name = Instance3D.attrib['name']
        instance = Instance3D.find('{*}IsInstanceOf').text
        aggregate = Instance3D.find('{*}IsAggregatedBy').text

        references[current].update({'aggregate': aggregate,
                                    'instance': instance,
                                    'matrix': matrix,
                                    'name': name,
                                    'type': 'Instance3D'})

    # turn references into directed graph for path finding
    graph = nx.DiGraph()
    for k, v in references.items():
        # IsAggregatedBy points up to a parent
        if 'aggregate' in v:
            graph.add_edge(v['aggregate'], k)
        # IsInstanceOf indicates a child
        if 'instance' in v:
            graph.add_edge(k, v['instance'])

    # the 3DXML format is stored as a directed acyclic graph that needs all
    # paths from the root to a geometry to generate the tree of the scene
    paths = []
    for geometry_id in geometries.keys():
        paths.extend(nx.all_simple_paths(
            graph, source=root_id, target=geometry_id))

    # the name of the root frame
    root_name = references[root_id]['name']
    # create a list of kwargs to send to the scene.graph.update function
    # start with a transform from the graphs base frame to our root name

    graph_kwargs = [{'frame_to': root_name,
                     'matrix': np.eye(4)}]

    # we are going to collect prettier geometry names as we traverse paths
    geom_names = {}
    # loop through every simple path and generate transforms tree
    # note that we are flattening the transform tree here
    for path_index, path in enumerate(paths):
        name = ''
        if 'name' in references[path[-3]]:
            name = references[path[-3]]['name']
            geom_names[path[-1]] = name
        # we need a unique node name for our geometry instance frame
        # due to the nature of the DAG names specified by the file may not
        # be unique, so we add an Instance3D name then append the path ids
        node_name = name + '#' + ':'.join(path)

        # pull all transformations in the path
        matrices = [references[i]['matrix']
                    for i in path if 'matrix' in references[i]]
        if len(matrices) == 0:
            matrix = np.eye(4)
        elif len(matrices) == 1:
            matrix = matrices[0]
        else:
            matrix = util.multi_dot(matrices)

        graph_kwargs.append({'matrix': matrix,
                             'frame_from': root_name,
                             'frame_to': node_name,
                             'geometry': path[-1]})

    # remap geometry names from id numbers to the name string
    # we extracted from the 3DXML tree
    geom_final = {}
    for key, value in geometries.items():
        if key in geom_names:
            geom_final[geom_names[key]] = value
    # change geometry names in graph kwargs in place
    for kwarg in graph_kwargs:
        if 'geometry' not in kwarg:
            continue
        kwarg['geometry'] = geom_names[kwarg['geometry']]

    # create the kwargs for load_kwargs
    result = {'class': 'Scene',
              'geometry': geom_final,
              'graph': graph_kwargs}

    return result


def print_element(element):
    """
    Pretty-print an lxml.etree element.

    Parameters
    ------------
    element : etree element
    """
    pretty = etree.tostring(
        element, pretty_print=True).decode('utf-8')
    print(pretty)
    return pretty


try:
    from lxml import etree
    _threedxml_loaders = {'3dxml': load_3DXML}
except ImportError:
    _threedxml_loaders = {}
4、代码实现(C++) 4.1 CAD Exchanger

https://cadexchanger.com/
https://cadexchanger.com/formats/
CAD Exchanger Web Toolkit

CAD Exchanger SDK is a set of C++ / C# / Java libraries to develop fast and robust 3D applications. Read and write STEP, JT, Solidworks, IFC, FBX, and other CAD formats.

The PMI data could be extracted from the following formats:

  • STEP (AP214e3, AP203e2, AP242)
  • JT (version 8.1 and higher)

    CAD Exchanger SDK is written in C++ and has a few wrappers to provide bindings with other programming languages, namely C#, Java and Python. This documentation is generated from the original C++ code, and that is why uses C++ API conventions. Nonetheless, understanding SDK API for other languages should be very straightforward, especially if consulting numerous examples.

    Most often CAD Exchanger SDK is used to read (import) external files in various 3D formats. This is performed by format-specific subclasses of the base class Base_Reader (e.g. JT_Reader, STEP_Reader and alike). All readers follow the same pattern by providing two methods: ReadFile() to parse the file and Transfer() to convert the contents into format-neutral ModelData_Model object.
// ****************************************************************************
// $Id$
//
// Copyright (C) 2008-2014, Roman Lygin. All rights reserved.
// Copyright (C) 2014-2021, CADEX. All rights reserved.
//
// This file is part of the CAD Exchanger software.
//
// You may use this file under the terms of the BSD license as follows:
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// ****************************************************************************

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include "../../cadex_license.cxx"

using namespace std;
using namespace cadex;

int main (int argc, char *argv[])
{
    auto aKey = LicenseKey::Value();

    // Activate the license (aKey must be defined in cadex_license.cxx)
    if (!CADExLicense_Activate (aKey)) {
        cerr << "Failed to activate CAD Exchanger license." << endl;
        return 1;
    }

    if (argc != 3) {
        cerr << "Usage: " << argv[0] << "  , where:"     << endl;
        cerr << "      is a name of the STEP file to be read"         << endl;
        cerr << "     is a name of the XML file to save the model" << endl;
        return 1;
    }

    const char* aSource = argv[1];
    const char* aDest   = argv[2];

	DS3DXML_Reader aReader;
	DS3DXML_ReaderParameters& aReaderParams = aReader.Parameters();

    // Let's set new parameters
    //aReaderParams.PreferredBRepRepresentationType() = STEP_ReaderParameters::AdvancedBRep;
	aReaderParams.ReadPMI();

    ModelData_Model aModel;

    // Reading the file
    if (!aReader.ReadFile (aSource)) {
        cerr << "Failed to read the file " << aSource << endl;
        return 1;
    }

    // Making a model data
    if (!aReader.Transfer (aModel)) {
        cerr << "Failed to transfer model data to specified format" << endl;
        return 1;
    }

    // Now we can get some model data
    cout << "Model name: "      << aModel.Name()          << endl;
    cout << "Number of roots: " << aModel.NumberOfRoots() << endl;
    
    // Saving the model
    if (!ModelData_ModelWriter().Write (aModel, aDest)) {
        cerr << "Failed to write the file " << aDest << endl;
        return 1;
    };

    return 0;
}
4.2 个人测试代码

在网上找了好几天也没找到一个合适的C++解析库,于是小沐同学写了一个,代码如下:

代码实现中使用到了第三方库libzippp,为了编译自动生成x64位和静态链接的库文件,将构建脚本compile.bat修改如下:


@echo off

SET root=%cd%
SET zlib=lib\zlib-1.2.11
SET libzip=lib\libzip-1.8.0

if not exist "%zlib%" goto error_zlib_not_found
if not exist "%libzip%" goto error_libzip_not_found

:compile_zlib
if exist "%zlib%\build\.compiled" goto compile_libzip
echo =============================
echo Compiling zlib...
echo =============================
cd "%zlib%"
mkdir build
cd "build"
cmake -A x64 ".." -DCMAKE_INSTALL_PREFIX="%root%/lib/install" -DBUILD_SHARED_LIBS=OFF
if %ERRORLEVEL% GEQ 1 goto error_zlib
cmake --build "." --config Debug --target install
if %ERRORLEVEL% GEQ 1 goto error_zlib
cmake --build "." --config Release --target install
if %ERRORLEVEL% GEQ 1 goto error_zlib
echo "OK" > ".compiled"
cd "..\..\.."

:compile_libzip
if exist "%libzip%\build\.compiled" goto compile_libzippp
echo =============================
echo Compiling libzip...
echo =============================
cd "%libzip%"
mkdir "build"
cd "build"
cmake -A x64  ".." -DCMAKE_INSTALL_PREFIX="%root%/lib/install" -DCMAKE_PREFIX_PATH="%root%/lib/install" -DENABLE_COMMONCRYPTO=OFF -DENABLE_GNUTLS=OFF -DENABLE_MBEDTLS=OFF -DENABLE_OPENSSL=OFF -DENABLE_WINDOWS_CRYPTO=ON -DBUILD_TOOLS=OFF -DBUILD_REGRESS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_DOC=OFF -DBUILD_SHARED_LIBS=OFF
if %ERRORLEVEL% GEQ 1 goto error_libzip
cmake --build . --config Debug --target install
if %ERRORLEVEL% GEQ 1 goto error_libzip
cmake --build . --config Release --target install
if %ERRORLEVEL% GEQ 1 goto error_libzip
echo "OK" > ".compiled"
cd "..\..\.."

:compile_libzippp
rmdir /q /s "dist"
mkdir "dist"
cd "dist"
mkdir "release"
copy "..\%zlib%\build\Release\zlib.dll" release
copy "..\%libzip%\build\lib\Release\zip.dll" release
copy "..\src\libzippp.h" release

mkdir "debug"
copy "..\%zlib%\build\Debug\zlibd.dll" debug
copy "..\%libzip%\build\lib\Debug\zip.dll" debug
copy "..\src\libzippp.h" debug
cd ".."

echo =============================
echo Compiling (shared) lizippp...
echo =============================
rmdir /q /s "build"
mkdir "build"
cd "build"
cmake -A x64 .. -DCMAKE_PREFIX_PATH="%root%/lib/install" -DBUILD_SHARED_LIBS=ON
if %ERRORLEVEL% GEQ 1 goto error_libzippp
cd ".."
cmake --build build --config Debug
if %ERRORLEVEL% GEQ 1 goto error_libzippp
cmake --build build --config Release
if %ERRORLEVEL% GEQ 1 goto error_libzippp

copy "build\Release\libzippp_shared_test.exe" "dist/release"
copy "build\Release\libzippp.dll" "dist/release"
copy "build\Release\libzippp.lib" "dist/release"
copy "build\Debug\libzippp_shared_test.exe" "dist/debug"
copy "build\Debug\libzippp.dll" "dist/debug"
copy "build\Debug\libzippp.lib" "dist/debug"

echo =============================
echo Compiling (static) lizippp...
echo =============================
rmdir /q /s "build"
mkdir "build"
cd "build"
cmake -A x64 .. -DCMAKE_PREFIX_PATH="%root%/lib/install" -DBUILD_SHARED_LIBS=OFF
if %ERRORLEVEL% GEQ 1 goto error_libzippp
cd ".."
cmake --build build --config Debug
if %ERRORLEVEL% GEQ 1 goto error_libzippp
cmake --build build --config Release
if %ERRORLEVEL% GEQ 1 goto error_libzippp

copy "build\Release\libzippp_static_test.exe" "dist/release"
copy "build\Release\libzippp_static.lib" "dist/release"
copy "build\Debug\libzippp_static_test.exe" "dist/debug"
copy "build\Debug\libzippp_static.lib" "dist/debug"

goto end

:error_zlib_not_found
echo [ERROR] The path was not found: %zlib%.
echo         You have to download zlib 1.2.11 and put in the folder %zlib%.
goto end

:error_zlib
echo [ERROR] Unable to compile zlib
goto end

:error_libzip_not_found
echo [ERROR] The path was not found: %libzip%.
echo         You have to download libzip 1.8.0 and put it in the folder %libzip%.
goto end

:error_libzip
echo [ERROR] Unable to compile libzip
goto end

:error_vs2012_not_found
echo [ERROR] VS2012 was not found (path not found: %vs2012devprompt%).
goto end

:error_libzippp
echo [ERROR] Unable to compile libzippp
goto end

:end
cd "%root%"
cmd
  • 3dxmlLoader.h
//***********************************************************************
//   Purpose:   3dxml文件格式(版本4.3)解析中关于整体结构部分的解析类
//   Author:    爱看书的小沐
//   Date:      2022-4-24
//   Languages: C++
//   Platform:  Visual Studio 2017
//   OS:        Win10 win64
// ***********************************************************************
#pragma once

#include "libzippp.h"
#include "tinyxml2.h"
#include "3dxmlType.h"

using namespace libzippp;
using namespace tinyxml2;

class C3dXmlLoader
{
public:
	bool load(const char* filename);
	void destroy();

private:
	bool loadManifestXml(ZipArchive& zf);
	bool loadRootXml(ZipArchive& zf);

	bool parseHeader(tinyxml2::XMLDocument& doc);
	bool parseProductStructure(tinyxml2::XMLDocument& doc);
	bool linkProductStructure();
	bool createProductStructureTree(ZipArchive& zf);
	bool createProductStructureTreeItem(_3dxml_base* node, VS_KEY keyParent, ZipArchive& zf);

	_3dxml_base* findNode(int id);
	_3dxml_base* parseReference3D(XMLElement* elem);
	_3dxml_base* parseInstance3D(XMLElement* elem);
	_3dxml_base* parseReferenceRep(XMLElement* elem);
	_3dxml_base* parseInstanceRep(XMLElement* elem);

private:
	std::string m_rootFileName;
	std::string m_version;
	std::vector<_3dxml_base*> m_vecNodes;
	std::map<int, _3dxml_base*> m_mapNodes;
	int m_rootId;
};
  • 3dxmlMesh.h
//***********************************************************************
//   Purpose:   3dxml文件格式(版本4.3)解析中关于三维模型部分的解析类
//   Author:    爱看书的小沐
//   Date:      2022-4-24
//   Languages: C++
//   Platform:  Visual Studio 2017
//   OS:        Win10 win64
// ***********************************************************************
#pragma once
#include "tinyxml2.h"
#include 
using namespace tinyxml2;

class C3dxmlMesh
{
public:
	bool parseMesh(const char* xml_text);
	
private:
	bool parsePolygonalRepType(XMLElement* elem);
	bool parsePolygonalLOD(XMLElement* elem);

	bool parseFaces(XMLElement* elem);
	bool parseFace(XMLElement* elem);
	bool parseSurfaceAttributes(XMLElement * elem);
	bool parseTriangles(const char* text);
	bool parseStrips(const char* text);
	bool parseFans(const char* text);

	bool parseVertexBuffer(XMLElement* elem);
	bool parsePositions(XMLElement* elem);
	bool parseNormals(XMLElement* elem);
	bool parseTextureCoordinates(XMLElement* elem);

	bool parseEdges(XMLElement * elem);
	bool parseLineAttributes(XMLElement * elem);
	bool parsePolyline(XMLElement * elem);

	bool parseColor(XMLElement * elem, float & r, float & g, float & b, float & a);

	std::vector<unsigned short> m_vecFaceIndexs;
	std::vector<float> m_vecPosition;
	std::vector<float> m_vecNormal;
	std::vector<float> m_vecTexture;
	std::vector<float> m_vecLines;

	float m_colorFace[4];
	float m_colorLine[4];
};
5、代码实现(C#) 5.1 xsd文件验证
  • 什么是 XML 架构 (XSD)?
    XML Schema 语言也称为 XML Schema Definition (XSD)。XML Schema 描述了 XML 文档的结构。XSD 是一种模式语言;您可以使用它来定义 XML 格式的可能结构和内容。然后,验证解析器可以检查 XML 实例文档是否符合 XSD 模式或一组模式。

  • 为什么需要验证 XML?
    当从发送者向接收者发送数据时,两部分对内容有相同的“期望”是很重要的。
    使用 XML 模式,发送者可以以接收者能够理解的方式描述数据。

XML 文档可以根据XmlSchemaSet中的 XML 模式定义语言 (XSD) 模式进行验证。 XML文档由XmlReader类的Create方法进行验证。要验证 XML文档,请构造一个XmlReaderSettings对象,该对象包含用于验证 XML 文档的 XML 架构定义语言 (XSD) 架构。

  • 测试代码1:
using System;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace ConsoleApp1
{
    class Program
    {
        static void ValidationEventHandler(object sender, ValidationEventArgs e)
        {
            if (e.Severity == XmlSeverityType.Warning)
            {
                Console.Write("WARNING: ");
                Console.WriteLine(e.Message);
            }
            else if (e.Severity == XmlSeverityType.Error)
            {
                Console.Write("ERROR: ");
                Console.WriteLine(e.Message);
            }
        }

        static void Main(string[] args)
        {
            var path = @"C:\Users\tomcat\Documents\test_3dxml";
            XmlSchemaSet schema = new XmlSchemaSet();

            schema.Add("http://www.3ds.com/xsd/3DXML", path + "\3DXMLSimpleTypes.xsd");
            schema.Add("http://www.3ds.com/xsd/3DXML", path + "\3DXMLMesh.xsd");
            schema.Add("http://www.3ds.com/xsd/3DXML", path + "\3DXML.xsd");
            XmlReader reader = XmlReader.Create(path + "\quad.3dxml");
            XDocument doc = XDocument.Load(reader);
            doc.Validate(schema, ValidationEventHandler);
            Console.WriteLine("Validate OK!");
        }
    }
}
  • 测试代码2:
using System;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace ConsoleApp1
{
    class Program
    {
        static void ValidationEventHandler(object sender, ValidationEventArgs e)
        {
            XmlSeverityType type = XmlSeverityType.Warning;
            if (Enum.TryParse<XmlSeverityType>("Error", out type))
            {
                if (type == XmlSeverityType.Error) throw new Exception(e.Message);
            }
        }
        
        static void Main(string[] args)
        {
            var path = @"C:\Users\tomcat\Documents\test4_3";
            XmlReaderSettings schemaSettings = new XmlReaderSettings();
            schemaSettings.Schemas.Add("http://www.3ds.com/xsd/3DXML", path + "\3DXMLSimpleTypes.xsd");
            schemaSettings.Schemas.Add("http://www.3ds.com/xsd/3DXML", path + "\3DXMLMesh.xsd");
            schemaSettings.Schemas.Add("http://www.3ds.com/xsd/3DXML", path + "\3DXML.xsd");
            schemaSettings.ValidationType = ValidationType.Schema;
            schemaSettings.ValidationEventHandler += new ValidationEventHandler(ValidationEventHandler);

            XmlReader reader = XmlReader.Create(path + "\quad.3dxml", schemaSettings);

            while (reader.Read()) { }
            Console.WriteLine("Validate OK!");
        }
    }
}

运行结果如下:

5.2 xsd文件读取

如何使用 XmlSerializer 反序列化对象?
当你反序列化一个对象时,传输格式决定了你是创建一个流还是文件对象。确定传输格式后,您可以根据需要调用Serialize或Deserialize方法。

  1. 使用要反序列化的对象的类型构造一个XmlSerializer 。
  2. 调用Deserialize方法以生成对象的副本。反序列化时,您必须将返回的对象强制转换为原始类型,如以下示例所示,该示例从文件中反序列化对象(尽管也可以从流中反序列化)。
xsd file.xdr [-outputdir:directory][/parameters:file.xml]
xsd file.xml [-outputdir:directory] [/parameters:file.xml]
xsd file.xsd {/classes | /dataset} [/element:element]
             [/enableLinqDataSet] [/language:language]
             [/namespace:namespace] [-outputdir:directory] [URI:uri]
             [/parameters:file.xml]
xsd {file.dll | file.exe} [-outputdir:directory] [/type:typename [...]][/parameters:file.xml]

这里使用的命令如下:

xsd 3DXML.xsd /classes 

官网提供的3dxml.xsd的部分内容如下:

生成的3dxml.cs的部分内容如下:

  • 测试代码如下:
using System;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\tomcat\Documents\test4_3";
            
            var mySerializer = new XmlSerializer(typeof(Model_3dxml));
            // To read the file, create a FileStream.
            var myFileStream = new FileStream(path + "\quad.3dxml", FileMode.Open);
            // Call the Deserialize method and cast to the object type.
            var myObject = (Model_3dxml)mySerializer.Deserialize(myFileStream);
        }
}
  • 运行结果如下:

结语

如果您觉得该方法或代码有一点点用处,可以给作者点个赞,或打赏杯咖啡;╮( ̄▽ ̄)╭
如果您感觉方法或代码不咋地//(ㄒoㄒ)//,就在评论处留言,作者继续改进。o_O???
如果您需要相关功能的代码定制化开发,可以私聊留言作者。(✿◡‿◡)
感谢各位童鞋们的支持!( ´ ▽´ )ノ ( ´ ▽´)っ!!!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/867785.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-12
下一篇 2022-05-12

发表评论

登录后才能评论

评论列表(0条)

保存