diff --git a/src/Add-on/Entsel.ts b/src/Add-on/Entsel.ts index b998993c0..fd71745b8 100644 --- a/src/Add-on/Entsel.ts +++ b/src/Add-on/Entsel.ts @@ -1,6 +1,7 @@ import { Vector3 } from 'three'; import { app } from '../ApplicationServices/Application'; import { Entity } from '../DatabaseServices/Entity/Entity'; +import { EntityFbx } from '../DatabaseServices/Entity/EntityFbx'; import { EntityRef } from '../DatabaseServices/Entity/EntityRef'; import { Line } from '../DatabaseServices/Entity/Line'; import { TemplateRecord } from '../DatabaseServices/Template/TemplateRecord'; @@ -35,7 +36,7 @@ export class Entsel implements Command if (res.Entity instanceof Entity) if (res.Entity.Template) dyn.UpdatePrompt(`模版:${res.Entity.Template.Index},${((res.Entity.Template.Object)).Objects.length}`); - else if (res.Entity instanceof EntityRef) + else if (res.Entity instanceof EntityRef || res.Entity instanceof EntityFbx) dyn.UpdatePrompt(res.Entity.Url); else dyn.UpdatePrompt(res.Entity.ColorIndex.toString()); diff --git a/src/Add-on/ExportData.tsx b/src/Add-on/ExportData.tsx index 81b6c57b0..5ad0546bf 100644 --- a/src/Add-on/ExportData.tsx +++ b/src/Add-on/ExportData.tsx @@ -11,7 +11,6 @@ import { Log } from "../Common/Log"; import { Vector2ApplyMatrix4 } from "../Common/Matrix4Utils"; import { StoreageKeys } from '../Common/StoreageKeys'; import { copyTextToClipboard } from "../Common/Utils"; -import { IsMirror } from '../csg/core/math/IsMirrot'; import { CylinderHole } from '../DatabaseServices/3DSolid/CylinderHole'; import { ExtrudeHole } from '../DatabaseServices/3DSolid/ExtrudeHole'; import { RevolveSolid } from '../DatabaseServices/3DSolid/RevolveSolid'; @@ -22,6 +21,7 @@ import { Board } from "../DatabaseServices/Entity/Board"; import { Circle } from "../DatabaseServices/Entity/Circle"; import { Curve } from "../DatabaseServices/Entity/Curve"; import { Entity } from "../DatabaseServices/Entity/Entity"; +import { EntityFbx } from '../DatabaseServices/Entity/EntityFbx'; import { EntityRef } from '../DatabaseServices/Entity/EntityRef'; import { ExtrudeSolid } from "../DatabaseServices/Entity/Extrude"; import { Line } from '../DatabaseServices/Entity/Line'; @@ -55,6 +55,7 @@ import { ModalPosition } from '../UI/Components/Modal/ModalInterface'; import { AppToaster } from '../UI/Components/Toaster'; import { DownPanelStore } from '../UI/Store/DownPanelStore'; import { TopPanelStore } from '../UI/Store/TopPanelStore'; +import { IsMirror } from '../csg/core/math/IsMirrot'; import { Hole } from './../DatabaseServices/3DSolid/Hole'; import { CompositeEntity } from './../DatabaseServices/Entity/CompositeEntity'; @@ -133,7 +134,7 @@ export class Command_ExportData implements Command UseSelect: true, Msg: "请选择需要发送到渲染器的实体:", Filter: { - filterTypes: [Board, SweepSolid, CompositeEntity, RevolveSolid, EntityRef, Light, Region, + filterTypes: [Board, SweepSolid, CompositeEntity, RevolveSolid, EntityRef, EntityFbx, Light, Region, RoomWallBase, RoomFlatBase, RoomHolePolyline, BulkheadCeiling ] }, @@ -220,7 +221,7 @@ export function Entitys2Data(ents: Iterable): Data arrayPushArray(d.Entitys, CompositeEntity2Data(e)); else if (e instanceof RevolveSolid) d.Entitys.push(ConvertRevolve2Data(e)); - else if (e instanceof EntityRef) + else if (e instanceof EntityRef || e instanceof EntityFbx) d.Entitys.push(ConveEntityRef2Data(e)); else if (e instanceof DirectionalLight) d.Entitys.push(ConverDirectionalLight2Data(e)); @@ -397,11 +398,11 @@ function ConveRegion2Data(e: Region) return reg; } -function ConveEntityRef2Data(e: EntityRef) +function ConveEntityRef2Data(e: EntityRef | EntityFbx) { let ref: any = {}; ref.Id = e.Id?.Index ?? 0; - ref.Type = "Ref"; + ref.Type = (e instanceof EntityRef) ? "Ref" : "Fbx"; ref.Url = e.Url; ref.OCS = e.OCSNoClone.toArray(); diff --git a/src/Add-on/FBXLoad.ts b/src/Add-on/FBXLoad.ts new file mode 100644 index 000000000..042f89f5b --- /dev/null +++ b/src/Add-on/FBXLoad.ts @@ -0,0 +1,255 @@ +import { Intent } from "@blueprintjs/core"; +import { MathUtils, Matrix4, Vector3 } from "three"; +import { app } from "../ApplicationServices/Application"; +import { FS } from "../Common/FileSystem"; +import { FBXURL } from "../Common/HostUrl"; +import { Log, LogType } from "../Common/Log"; +import { PostJson, RequestStatus } from "../Common/Request"; +import { UpdateDraw } from "../Common/Status"; +import { CADFiler } from "../DatabaseServices/CADFiler"; +import { EntityFbx } from "../DatabaseServices/Entity/EntityFbx"; +import { TemplateEntityRef } from "../DatabaseServices/Template/ProgramTempate/TemplateEntityRef"; +import { CommandWrap } from "../Editor/CommandMachine"; +import { JigUtils } from "../Editor/JigUtils"; +import { PromptStatus } from "../Editor/PromptResult"; +import { TempEditor } from "../Editor/TempEditor"; +import { ParsePlacePos } from "../Editor/TranstrolControl/ParsePlacePos"; +import { AppToaster, ShowLinesToaster } from "../UI/Components/Toaster"; +import { TopPanelStore } from "../UI/Store/TopPanelStore"; + +export class Command_FBXImport +{ + async exec() + { + if (TempEditor.EditorIng) + { + AppToaster.show({ + message: "当前正处于编辑模式下", + timeout: 3000, + intent: Intent.WARNING, + }); + return; + } + FS.ChooseFile({ + filter: ".fbx", + multiple: false, + callback: async files => + { + let f = files.item(0); + await FbxImport(f); + } + }); + } +} + +export async function FbxImport(f: File) +{ + let ext = FS.getFileExtension(f.name); + let fbxArr: ArrayBuffer; + if (ext === "fbx") + fbxArr = await f.arrayBuffer(); + else + { + AppToaster.show({ + message: `文件类型错误,不是FBX文件`, + intent: Intent.WARNING, + timeout: 1000, + }); + return; + } + + let cameraFiler = new CADFiler; + let fbxEnt = new EntityFbx(); + let errorMsg = fbxEnt.LoadFBXModelFromArrayBuffer(fbxArr); + if (errorMsg) + { + AppToaster.show({ + message: errorMsg, + timeout: 2000, + intent: Intent.DANGER, + }); + Log(errorMsg, LogType.Error); + return; + } + TempEditor.Start(); + + CommandWrap(() => + { + app.Database.ModelSpace.Append(fbxEnt); + }, ""); + + app.Database.hm.lockIndex++;//禁止初始化动作被撤销 + app.Viewer.CameraCtrl.WriteFile(cameraFiler); + + let isUpLoad = false; + + ShowLinesToaster( + ["是否上传FBX数据?"], + { + intent: Intent.PRIMARY, + timeout: 0, + action: + { + onClick: () => { isUpLoad = true; }, + text: "上传", + }, + onDismiss: async () => + { + await EndEditor(); + } + }, "upLoad_FBX"); + + async function EndEditor() + { + const hashBuffer = await crypto.subtle.digest("SHA-1", fbxArr); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "")).join(""); + + const shopId = TopPanelStore.GetInstance().shopId; + + let ent = fbxEnt.Clone(); + ent.Url = `/${shopId}/${hashHex}`; + + app.Viewer.CameraCtrl.ReadFile(cameraFiler); + await app.Editor.ModalManage.EndExecingCmd(); + TempEditor.End(); + app.Editor.MaskManage.Clear(); + + if (isUpLoad) + { + const data = await PostJson(FBXURL, { path: `UsrFbxs/${shopId}/${hashHex}` }); + if (data.err_code != RequestStatus.Ok) return; + + const text = new Uint8Array(fbxArr); + const blob = new Blob([text], { type: 'text/plain' }); + + const formData = new FormData(); + formData.append('OSSAccessKeyId', data.sign.accessid); + formData.append('policy', data.sign.policy); + formData.append('success_action_status', '200'); + formData.append('Signature', data.sign.signature); + formData.append('key', `UsrFbxs/${shopId}/${hashHex}`); + formData.append('Expires', data.sign.expire); + formData.append('file', blob, `${f.name}`); + + let res = await fetch(data.sign.host, { + method: 'POST', + body: formData + }); + + if (res.status === 200) + { + AppToaster.show({ + message: "FBX数据上传成功", + timeout: 3000, + intent: Intent.SUCCESS + }); + } + else + { + AppToaster.show({ + message: "FBX数据上传失败", + timeout: 3000, + intent: Intent.DANGER, + }); + return; + } + + CommandWrap(async () => + { + JigUtils.Draw(ent); + app.Editor.MouseCtrl.EnableMouseUpDoit = true; //响应鼠标抬起就绘制 + + let move = new Vector3; + let isCopy = false; + while (true) + { + //关闭捕捉 + let snapBak = app.Editor.GetPointServices.snapServices.SnapModeEnable; + app.Editor.GetPointServices.snapServices.SnapModeEnable = 0; + + let ptRes = await app.Editor.GetPoint({ + Msg: "请点击放置位置(按住Ctrl强制穿透):", + Raycast: true, + RaycastFilter: { selectFreeze: true }, + Callback: (p, i) => + { + if (i) + new ParsePlacePos(ent, i); + else + ent.Position = p; + }, + KeyWordList: [ + { key: "B", msg: "基点" }, + { key: "CO", msg: isCopy ? "不复制" : "复制" }, + { key: "R", msg: "旋转" }, + ] + }); + + app.Editor.GetPointServices.snapServices.SnapModeEnable = snapBak; + app.Editor.MouseCtrl.EnableMouseUpDoit = false; + + if (ptRes.Status === PromptStatus.OK) + { + if (ptRes.intersection) + new ParsePlacePos(ent, ptRes.intersection); + else + ent.Position = ptRes.Point.add(move); + + app.Database.ModelSpace.Append(ent); + + let tr = new TemplateEntityRef; + app.Database.TemplateTable.Append(tr); + tr.Objects.push(ent.Id); + tr.InitBaseParams(); + + if (isCopy) + { + ent = ent.Clone(); + JigUtils.Draw(ent); + } + else return; + } + else if (ptRes.Status === PromptStatus.Keyword) + { + if (ptRes.StringResult === "R") + { + let ocsBack = ent.OCS; + let anRes = await app.Editor.GetAngle({ + Msg: "指定旋转角度", + Callback: (newan: number) => + { + ent.OCS = new Matrix4().makeRotationZ(MathUtils.degToRad(newan)).setPosition(ent.Position); + ent.Update(UpdateDraw.Matrix); + }, + BasePoint: ent.Position, + }); + + if (anRes.Status === PromptStatus.OK) + ent.OCS = new Matrix4().makeRotationZ(MathUtils.degToRad(anRes.Distance)).setPosition(ent.Position); + else + ent.OCS = ocsBack; + + ent.Update(UpdateDraw.Matrix); + } + else if (ptRes.StringResult === "B") + { + let ptRes = await app.Editor.GetPoint({ Msg: "指定基点" }); + if (ptRes.Status === PromptStatus.OK) + move = ent.Position.sub(ptRes.Point); + else if (ptRes.Status === PromptStatus.Cancel) return; + } + else if (ptRes.StringResult === "CO") + { + isCopy = !isCopy; + } + else + return; + } + else + return; + } + }, "fbx插入"); + } + } +} diff --git a/src/Add-on/HideSelect/HideSelectUtils.ts b/src/Add-on/HideSelect/HideSelectUtils.ts index 593a87091..a14a637b6 100644 --- a/src/Add-on/HideSelect/HideSelectUtils.ts +++ b/src/Add-on/HideSelect/HideSelectUtils.ts @@ -1,6 +1,7 @@ import { Board } from "../../DatabaseServices/Entity/Board"; import { BoardOpenDir, BoardType } from "../../DatabaseServices/Entity/BoardInterface"; import { Entity } from "../../DatabaseServices/Entity/Entity"; +import { EntityFbx } from "../../DatabaseServices/Entity/EntityFbx"; import { EntityRef } from "../../DatabaseServices/Entity/EntityRef"; import { HardwareCompositeEntity } from "../../DatabaseServices/Hardware/HardwareCompositeEntity"; import { HardwareTopline } from "../../DatabaseServices/Hardware/HardwareTopline"; @@ -118,6 +119,7 @@ export function IsHouse(en: Entity): boolean { if (en instanceof RoomBase || en instanceof EntityRef || + en instanceof EntityFbx || (en instanceof HardwareTopline && (en.HardwareOption.name === "地脚线" || en.HardwareOption.name === "吊顶")) || en.Template?.Object instanceof TemplateArcWindowRecord || (en.Template?.Object as TemplateRecord)?.Parent?.Object instanceof TemplateWindowRecord || diff --git a/src/Add-on/Mirror.ts b/src/Add-on/Mirror.ts index 914a18a64..466dcc363 100644 --- a/src/Add-on/Mirror.ts +++ b/src/Add-on/Mirror.ts @@ -13,6 +13,7 @@ import { Board } from "../DatabaseServices/Entity/Board"; import { CompositeEntity } from "../DatabaseServices/Entity/CompositeEntity"; import { Curve } from "../DatabaseServices/Entity/Curve"; import { Entity } from "../DatabaseServices/Entity/Entity"; +import { EntityFbx } from "../DatabaseServices/Entity/EntityFbx"; import { EntityRef } from "../DatabaseServices/Entity/EntityRef"; import { HardwareCompositeEntity } from "../DatabaseServices/Hardware/HardwareCompositeEntity"; import { Command } from "../Editor/CommandMachine"; @@ -23,7 +24,7 @@ import { UpdateEntityDrawTask } from "./UpdateEntityDrawTask"; const NEED_ENTITY = [Curve, AlignedDimension, LineAngularDimension, RadiusDimension]; -const CanMirrorEntity = [...NEED_ENTITY, Hole, Board, SweepSolid, CompositeEntity, EntityRef]; +const CanMirrorEntity = [...NEED_ENTITY, Hole, Board, SweepSolid, CompositeEntity, EntityRef, EntityFbx]; export class MirrorCommand implements Command { diff --git a/src/Add-on/Scale.ts b/src/Add-on/Scale.ts index 10c054330..1afe9c343 100644 --- a/src/Add-on/Scale.ts +++ b/src/Add-on/Scale.ts @@ -6,6 +6,7 @@ import { ExtrudeHole } from "../DatabaseServices/3DSolid/ExtrudeHole"; import { Arc } from "../DatabaseServices/Entity/Arc"; import { Circle } from "../DatabaseServices/Entity/Circle"; import { Entity } from "../DatabaseServices/Entity/Entity"; +import { EntityFbx } from "../DatabaseServices/Entity/EntityFbx"; import { EntityRef } from "../DatabaseServices/Entity/EntityRef"; import { ExtrudeSolid } from "../DatabaseServices/Entity/Extrude"; import { Line } from "../DatabaseServices/Entity/Line"; @@ -25,7 +26,7 @@ export class Command_Scale implements Command let ssRes = await app.Editor.GetSelection({ UseSelect: true, Filter: { - filterTypes: [Arc, Circle, Line, Region, Text, Polyline, EntityRef, ExtrudeSolid, ExtrudeHole]//Ellipse详细请看代码备注 + filterTypes: [Arc, Circle, Line, Region, Text, Polyline, EntityRef, EntityFbx, ExtrudeSolid, ExtrudeHole]//Ellipse详细请看代码备注 } }); if (ssRes.Status != PromptStatus.OK) diff --git a/src/Add-on/ShareView/Command_ShareView.ts b/src/Add-on/ShareView/Command_ShareView.ts index ae1a891b6..8cda066e6 100644 --- a/src/Add-on/ShareView/Command_ShareView.ts +++ b/src/Add-on/ShareView/Command_ShareView.ts @@ -7,6 +7,7 @@ import { Intent } from "../../Common/Toaster"; import { CADFiler } from "../../DatabaseServices/CADFiler"; import { Database } from "../../DatabaseServices/Database"; import { Board } from "../../DatabaseServices/Entity/Board"; +import { EntityFbx } from "../../DatabaseServices/Entity/EntityFbx"; import { EntityRef } from "../../DatabaseServices/Entity/EntityRef"; import { FileServer } from "../../DatabaseServices/FileServer"; import { HardwareCompositeEntity } from "../../DatabaseServices/Hardware/HardwareCompositeEntity"; @@ -38,7 +39,7 @@ export class Command_ShareView implements Command UseSelect: true, Filter: { filterFunction: (obj, ent) => - !(ent instanceof DirectionalLight || ent instanceof EntityRef || + !(ent instanceof DirectionalLight || ent instanceof EntityRef || ent instanceof EntityFbx || (!store.m_Option.IsExportBoard && (ent instanceof Board)) || (!store.m_Option.IsExportHardware && (ent instanceof HardwareCompositeEntity || ent instanceof HardwareTopline)))//避免拷贝太阳光 太阳光只能有一个 }, }); diff --git a/src/Add-on/testEntity/ParseMaterialImage.ts b/src/Add-on/testEntity/ParseMaterialImage.ts index a92c78132..3fda86dc3 100644 --- a/src/Add-on/testEntity/ParseMaterialImage.ts +++ b/src/Add-on/testEntity/ParseMaterialImage.ts @@ -35,6 +35,33 @@ export function ParseBoxUrl(url: string, box = false): string return GenerateCdnUrl(`/Paks/paks_cooked2/ue_resource/Content${url}/Mesh_${name}${box ? "_BOX" : ""}.ubox`, true); } +let defultFBXMaterial: MeshPhysicalMaterial; +export function GetDefultFBXMaterial() +{ + if (!defultFBXMaterial) + { + defultFBXMaterial = new MeshPhysicalMaterial({ + name: "默认", + side: DoubleSide, + transparent: false, + metalness: 0.2, + opacity: 1, + roughness: 0.2, + bumpScale: 0.0005 + }); + } + //分开处理,因为有小概率会出现envMap为空的情况 + if (!defultFBXMaterial.envMap) + { + HostApplicationServices.LoadDefaultExr().then(exr => + { + defultFBXMaterial.envMap = exr; + defultFBXMaterial.needsUpdate = true; + }); + } + return defultFBXMaterial; +}; + export async function ConverMaterial2(m: MeshPhongMaterial, url: string) { let name = m.name; @@ -61,6 +88,7 @@ export async function ConverMaterial2(m: MeshPhongMaterial, url: string) try { + if (!url) throw ""; //url为空的时候,走默认的材质 let dataString = await (await fetch(GenerateCdnUrl(`/Paks/paks_cooked2/ue_resource/Content${encodeURI(url)}/${name}.json`))).text(); let data = JSON.parse(dataString); diff --git a/src/ApplicationServices/Application.ts b/src/ApplicationServices/Application.ts index 0c1d97a14..c0340adc8 100644 --- a/src/ApplicationServices/Application.ts +++ b/src/ApplicationServices/Application.ts @@ -6,6 +6,7 @@ import { HardwareCuttingReactor } from '../Add-on/BoardCutting/HardwareCuttingRe import { DwgDxfImport } from '../Add-on/DXFLoad'; import { DrillingReactor } from '../Add-on/DrawDrilling/DrillingReactor'; import { AppendUserInfo } from '../Add-on/ExportData'; +import { FbxImport } from '../Add-on/FBXLoad'; import { ImportJiajuFile } from '../Add-on/JiaJu/Import/JiaJuImport'; import { ImportKJLData } from '../Add-on/KJL/Import/KJLImport'; import { CommandNames } from '../Common/CommandNames'; @@ -372,6 +373,7 @@ export class ApplicationService let cadfiles: File[] = []; let jsonFiles: File[] = []; let xmlFiles: File[] = []; + let fbxFiles: File[] = []; for (let i = 0; i < e.dataTransfer.files.length; i++) { let f = e.dataTransfer.files.item(i); @@ -391,6 +393,11 @@ export class ApplicationService e.preventDefault(); xmlFiles.push(f); } + else if (ext === ".FBX") + { + e.preventDefault(); + fbxFiles.push(f); + } } const exec = async () => @@ -421,6 +428,11 @@ export class ApplicationService ImportJiajuFile(xmlFile); } } + if (fbxFiles.length > 0) + { + for (let f of fbxFiles) + await FbxImport(f); + } }; exec(); diff --git a/src/Common/CommandNames.ts b/src/Common/CommandNames.ts index a00569881..fe8ad5706 100644 --- a/src/Common/CommandNames.ts +++ b/src/Common/CommandNames.ts @@ -10,6 +10,7 @@ export enum CommandNames Group = "GROUP", DXFImport = "DXF", DWGImport = "DWG", + FBXImport = "FBX", Insert = "INSERT", Line = "LINE", //直线 XLine = "XLINE", diff --git a/src/Common/HostUrl.ts b/src/Common/HostUrl.ts index 892163625..5bb6a4145 100644 --- a/src/Common/HostUrl.ts +++ b/src/Common/HostUrl.ts @@ -204,3 +204,5 @@ export const PrivateModule = { GetModuleList: CURRENT_HOST + "/CAD-privateModuleLists",//模型列表 UpdateModule: CURRENT_HOST + "/CAD-privateModuleUpdate"//修改模型信息 }; + +export const FBXURL = CURRENT_HOST + "/CAD-ossUploadSign"; diff --git a/src/Common/IsMeshMaterialEntity.ts b/src/Common/IsMeshMaterialEntity.ts index ff8a48b11..facc2d0d7 100644 --- a/src/Common/IsMeshMaterialEntity.ts +++ b/src/Common/IsMeshMaterialEntity.ts @@ -3,6 +3,7 @@ import { SweepSolid } from "../DatabaseServices/3DSolid/SweepSolid"; import { Board } from "../DatabaseServices/Entity/Board"; import { CompositeEntity } from "../DatabaseServices/Entity/CompositeEntity"; import { Entity } from "../DatabaseServices/Entity/Entity"; +import { EntityFbx } from "../DatabaseServices/Entity/EntityFbx"; import { EntityRef } from "../DatabaseServices/Entity/EntityRef"; import { Region } from "../DatabaseServices/Entity/Region"; import { HardwareTopline } from "../DatabaseServices/Hardware/HardwareTopline"; @@ -16,6 +17,7 @@ export function IsMeshMaterialEntity(en: Entity): boolean return (en instanceof Board || en instanceof Region || en instanceof EntityRef + || en instanceof EntityFbx || en instanceof RevolveSolid || en instanceof CompositeEntity || en instanceof SweepSolid || en instanceof HardwareTopline || en instanceof RoomWallBase || en instanceof RoomFlatBase diff --git a/src/DatabaseServices/Entity/EntityFbx.ts b/src/DatabaseServices/Entity/EntityFbx.ts new file mode 100644 index 000000000..73b61a9b9 --- /dev/null +++ b/src/DatabaseServices/Entity/EntityFbx.ts @@ -0,0 +1,517 @@ +import { Box3, Group, Matrix3, Matrix4, Mesh, MeshPhongMaterial, Object3D, Vector3 } from "three"; +import { BoxLine } from "../../Add-on/testEntity/BoxLine"; +import { GenerateCdnUrl } from "../../Add-on/testEntity/GenerateCdnUrl"; +import { GetDefultFBXMaterial, UE_FBX_LOADER } from "../../Add-on/testEntity/ParseMaterialImage"; +import { HostApplicationServices } from "../../ApplicationServices/HostApplicationServices"; +import { ColorMaterial } from "../../Common/ColorPalette"; +import { DisposeThreeObj, Object3DRemoveAll } from "../../Common/Dispose"; +import { Log, LogType } from "../../Common/Log"; +import { UpdateDraw } from "../../Common/Status"; +import { ObjectSnapMode } from "../../Editor/ObjectSnapMode"; +import { Box3Ext } from "../../Geometry/Box"; +import { equalv3 } from "../../Geometry/GeUtils"; +import { RenderType } from "../../GraphicsSystem/RenderType"; +import { IndexedDbStore, StoreName } from "../../IndexedDb/IndexedDbStore"; +import { Factory } from "../CADFactory"; +import { CADFiler } from "../CADFiler"; +import { ObjectId } from "../ObjectId"; +import { PhysicalMaterialRecord } from "../PhysicalMaterialRecord"; +import { GetBoxGeoBufferGeometry } from "./BoxSolid"; +import { Entity } from "./Entity"; +import { GenUVForWorld } from "./GenUVForWorld"; + +/** + * 外部引用的实体,比如glTF + */ +@Factory +export class EntityFbx extends Entity +{ + OnlyRenderType = true; + + private _Size = new Vector3;//原始尺寸 + private _ScaleSize = new Vector3;//缩放后的尺寸 + private _Center = new Vector3;//盒子中心 + + private _OverWriteMaterial = new Map>();//section index -> materialId + + constructor(private _url?: string) + { + super(); + } + + override get IsVisible() + { + return HostApplicationServices.IsRoomEntityVisible && super.IsVisible; + } + + get Url() { return this._url; } + set Url(url: string) + { + if (this._url !== url) + { + this.WriteAllObjectRecord(); + this._url = url; + } + } + + get CurSize() { return this.ScaleSize.x ? this.ScaleSize.clone() : this._Size.clone(); } + + get ScaleSize() { return this._ScaleSize; } + set ScaleSize(size: Vector3) + { + if (!equalv3(size, this.ScaleSize.x ? this.ScaleSize : this._Size)) + { + this.WriteAllObjectRecord(); + if (this._ScaleSize.x && equalv3(size, this._Size)) + this._ScaleSize.set(0, 0, 0); + else + this._ScaleSize.copy(size); + this.Update(); + } + } + + get Scale() + { + if (this._ScaleSize.x && this._Size.x) + return this._ScaleSize.clone().divide(this._Size); + return new Vector3(1, 1, 1); + } + + get BoundingBox() + { + return this.BoundingBoxInOCS.applyMatrix4(this.OCSNoClone); + } + + get BoundingBoxInOCS() + { + let box = new Box3Ext( + this._Size.clone().multiplyScalar(-0.5).add(this._Center), + this._Size.clone().multiplyScalar(0.5).add(this._Center)); + + if (this._ScaleSize.x) + { + box.applyMatrix4(new Matrix4().makeScale( + this._ScaleSize.x / this._Size.x, + this._ScaleSize.y / this._Size.y, + this._ScaleSize.z / this._Size.z, + )); + } + return box; + } + get OverWriteMaterial() + { + return this._OverWriteMaterial; + } + + SetMaterialAtSlot(mtl: ObjectId, slotIndex: number) + { + if (this._OverWriteMaterial.get(slotIndex) !== mtl) + { + this.WriteAllObjectRecord(); + this._OverWriteMaterial.set(slotIndex, mtl); + this.Update(UpdateDraw.Material); + } + } + newObject: Group; + + //通过二进制数组生成fbx模型 + LoadFBXModelFromArrayBuffer(fbxArray: ArrayBuffer) + { + let obj: Group; + try + { + obj = UE_FBX_LOADER.parse(fbxArray, UE_FBX_LOADER.path); + } + catch (error) + { + return "错误:FBX资源无法解析"; + } + + this.newObject = obj; + this.Update(); + } + + CloneDrawObject(from: this) + { + for (let [type, obj] of from._CacheDrawObject) + { + let oldUserDaata = obj.userData; + obj.traverse(o => o.userData = {}); + let newObj = obj.clone(); + + newObj.traverse(o => + { + if (o instanceof Mesh) + { + if (Array.isArray(o.material)) + o.material = [...o.material]; + } + }); + + obj.userData = oldUserDaata; + + newObj.matrix = this._Matrix; + newObj.userData = { Entity: this }; + this._CacheDrawObject.set(type, newObj); + } + this.NeedUpdateFlag = UpdateDraw.None; + } + ApplyScaleMatrix(m: Matrix4) + { + this.WriteAllObjectRecord(); + let p = this.Position; + p.applyMatrix4(m); + + m.extractBasis(Entity._xa, Entity._ya, Entity._za); + + let scaleX = Entity._xa.length(); + let scaleY = Entity._ya.length(); + let scaleZ = Entity._za.length(); + + if (!this._ScaleSize.x) this._ScaleSize.copy(this._Size); + + this._ScaleSize.x *= scaleX; + this._ScaleSize.y *= scaleY; + this._ScaleSize.z *= scaleZ; + + Entity._xa.normalize(); + Entity._ya.normalize(); + Entity._za.normalize(); + m = new Matrix4().makeBasis(Entity._xa, Entity._ya, Entity._za); + this.ApplyMatrix(m); + + this.Position = p; + this.Update(); + return this; + } + + InitDrawObject(renderType: RenderType = RenderType.Wireframe) + { + if (renderType > 100) return;//避免CTRL+P 无法打印(首次开图,同时选墙和窗) + if (!this._url && !this.newObject) return;//没有url或newObject,无法初始化 + + let dObj = new Object3D(); + + const GetMaterial = (m: MeshPhongMaterial, index?: number) => + { + let overMtl = this._OverWriteMaterial.get(index); + if (overMtl?.Object?.Material) + return overMtl.Object.Material; + else + return GetDefultFBXMaterial(); + }; + + if (this.newObject) + { + let gen = new GenUVForWorld; + this.newObject.traverse(async o => + { + if (o instanceof Mesh) + { + o.geometry["IsMesh"] = true; + + o.castShadow = true; + o.receiveShadow = true; + + if (Array.isArray(o.material)) + o.material = o.material.map(GetMaterial); + else + o.material = GetMaterial(o.material); + + gen.GenUV(o); + + this.AsyncUpdated();//保证更新视图 + } + }); + + //缩放尺寸(如果用户指定了这个缩放 我们就缩放它) + if (this._ScaleSize.x) + this.newObject.scale.copy(this._ScaleSize).divide(this._Size).multiplyScalar(10); + else + this.newObject.scale.set(10, 10, 10);//UE4缩放10 如果按米为单位的缩放1000 + + dObj.add(this.newObject); + + this.newObject.updateMatrix();//保证更新缩放 + this.newObject.updateMatrixWorld(false);//保证更新位置 + + const boundingBox = new Box3().expandByObject(this.newObject); + boundingBox.getSize(this._Size); + boundingBox.getCenter(this._Center); + this.AsyncUpdated();//保证更新视图 + } + else + { + let previewBoxMesh = new Mesh(GetBoxGeoBufferGeometry(), ColorMaterial.GetBasicMaterialTransparent(7, 0.3)); + //显示预览盒子,顺便请求模型的原始尺寸,以便我们开图就能有预览效果 + if (this._Size.x) + { + previewBoxMesh.scale.copy(this._ScaleSize.x ? this._ScaleSize : this._Size); + previewBoxMesh.position.copy(this._Center); + } + else + { + previewBoxMesh.position.set(0, 0, 150); + previewBoxMesh.scale.set(300, 300, 300); + } + + previewBoxMesh.updateMatrix(); + previewBoxMesh.updateMatrixWorld(false);//保证更新位置 + dObj.add(previewBoxMesh); + + IndexedDbStore.CADStore().then(async store => + { + let array = await store.Get(StoreName.FBX, this._url) as ArrayBuffer; + let needSave = false; + if (!array) + { + needSave = true; + + let fbxurl = GenerateCdnUrl(`/UsrFbxs${this._url}`, false); + let res = await fetch(fbxurl); + + if (res.status === 200) + array = await res.arrayBuffer(); + else + { + let errorMsg = "错误:网络异常或者服务端资源不存在!无法请求到服务器FBX资源:" + this._url; + Log(errorMsg, LogType.Error, [this]); + //TODO:模型请求失败 是不是应该做点什么 过会重试? + reportError(errorMsg); + return; + } + } + + try + { + this.newObject = UE_FBX_LOADER.parse(array, UE_FBX_LOADER.path); + } + catch (error) + { + let errorMsg = "错误:FBX资源无法解析:" + this._url; + Log(errorMsg, LogType.Error, [this]); + reportError(new Error(errorMsg)); + return; + } + if (needSave) + store.Put(StoreName.FBX, this.Url, array); + + DisposeThreeObj(dObj); + Object3DRemoveAll(dObj); + + let gen = new GenUVForWorld; + + this.newObject.traverse(async o => + { + if (o instanceof Mesh) + { + o.geometry["IsMesh"] = true; + + o.castShadow = true; + o.receiveShadow = true; + + if (Array.isArray(o.material)) + o.material = o.material.map(GetMaterial); + else + o.material = GetMaterial(o.material); + + gen.GenUV(o); + + this.AsyncUpdated();//保证更新视图 + } + }); + + dObj.add(this.newObject); + + //缩放尺寸(如果用户指定了这个缩放 我们就缩放它) + if (this._ScaleSize.x) + this.newObject.scale.copy(this._ScaleSize).divide(this._Size).multiplyScalar(10); + else + this.newObject.scale.set(10, 10, 10);//UE4缩放10 如果按米为单位的缩放1000 + + this.newObject.updateMatrix();//保证更新缩放 + this.newObject.updateMatrixWorld(false);//保证更新位置 + + const boundingBox = new Box3().expandByObject(this.newObject); + boundingBox.getSize(this._Size); + boundingBox.getCenter(this._Center); + this.AsyncUpdated();//保证更新视图 + }); + } + return dObj; + } + + UpdateDrawObject(type: RenderType, obj: Object3D) + { + let newObject = obj.children[0]; + + //缩放尺寸(如果用户指定了这个缩放 我们就缩放它) + if (this._ScaleSize.x && this._Size.x) + newObject.scale.copy(this._ScaleSize).divide(this._Size).multiplyScalar(10); + else + newObject.scale.set(10, 10, 10);//UE4缩放10 如果按米为单位的缩放1000 + + newObject.updateMatrix();//保证更新缩放 + newObject.updateMatrixWorld(false);//保证更新位置 + } + + UpdateDrawObjectMaterial(renderType: RenderType, obj: Object3D) + { + obj.traverse(o => + { + if (o instanceof Mesh) + { + if (Array.isArray(o.material)) + { + for (let i = 0; i < o.material.length; i++) + { + let mtl = this._OverWriteMaterial.get(i)?.Object?.Material; + if (mtl) + { + o.material[i] = mtl; + if (mtl.transparent) + { + o.castShadow = false; + o.receiveShadow = false; + } + } + else + { + o.material[i] = GetDefultFBXMaterial(); + this.AsyncUpdated(); + } + } + } + else + { + let mtl = this._OverWriteMaterial.get(0)?.Object?.Material; + if (mtl) + o.material = mtl; + else + { + o.material = GetDefultFBXMaterial(); + this.AsyncUpdated(); + } + } + } + }); + } + GetObjectSnapPoints( + snapMode: ObjectSnapMode, + pickPoint: Vector3, + lastPoint: Vector3, + viewXform: Matrix3 + ): Vector3[] + { + let box = this.BoundingBox; + let [x1, y1, z1] = [box.min.x, box.min.y, box.min.z]; + let [x2, y2, z2] = [box.max.x, box.max.y, box.max.z]; + switch (snapMode) + { + case ObjectSnapMode.End: + return this.GetGripPoints(); + case ObjectSnapMode.Mid: + let mid = [ + new Vector3(x1, y1, (z1 + z2) / 2), + new Vector3(x1, y2, (z1 + z2) / 2), + new Vector3(x2, y2, (z1 + z2) / 2), + new Vector3(x2, y1, (z1 + z2) / 2), + ]; + let midline = [ + new Vector3(x1, (y1 + y2) / 2, z1), + new Vector3(x2, (y1 + y2) / 2, z1), + new Vector3((x1 + x2) / 2, y2, z1), + new Vector3((x1 + x2) / 2, y1, z1), + ]; + let v = new Vector3(0, 0, z2); + let mids: Vector3[] = []; + mids.push(...mid, ...midline, ...midline.map(p => p.clone().add(v))); + return mids; + case ObjectSnapMode.Nea: + let lines = BoxLine(this.BoundingBox); + let neas: Vector3[] = []; + for (let l of lines) + neas.push(...l.GetObjectSnapPoints(snapMode, pickPoint, lastPoint, viewXform)); + return neas; + default: + break; + } + return []; + } + + MoveGripPoints(indexList: Array, vec: Vector3) + { + if (indexList.length) + { + this.WriteAllObjectRecord(); + this.Position = this.Position.add(vec); + } + } + + //#region -------------------------File------------------------- + + //对象从文件中读取数据,初始化自身 + protected _ReadFile(file: CADFiler) + { + let ver = file.Read(); + super._ReadFile(file); + this._url = file.Read(); + + if (ver > 1) + { + this._Size.x = file.Read(); + this._Size.y = file.Read(); + this._Size.z = file.Read(); + + this._Center.x = file.Read(); + this._Center.y = file.Read(); + this._Center.z = file.Read(); + + this._ScaleSize.x = file.Read(); + this._ScaleSize.y = file.Read(); + this._ScaleSize.z = file.Read(); + } + + this._OverWriteMaterial.clear(); + if (ver > 2) + { + let size = file.Read(); + for (let i = 0; i < size; i++) + { + let index = file.Read(); + let id = file.ReadHardObjectId() as ObjectId; + if (id) + this._OverWriteMaterial.set(index, id); + } + } + } + //对象将自身数据写入到文件. + WriteFile(file: CADFiler) + { + file.Write(3); + super.WriteFile(file); + file.Write(this._url); + + //2 + file.Write(this._Size.x); + file.Write(this._Size.y); + file.Write(this._Size.z); + + file.Write(this._Center.x); + file.Write(this._Center.y); + file.Write(this._Center.z); + + file.Write(this._ScaleSize.x); + file.Write(this._ScaleSize.y); + file.Write(this._ScaleSize.z); + + //ver3 + file.Write(this._OverWriteMaterial.size); + for (let [index, id] of this._OverWriteMaterial) + { + file.Write(index); + file.WriteHardObjectId(id); + } + } + //#endregion +} diff --git a/src/DatabaseServices/Entity/EntityRef.ts b/src/DatabaseServices/Entity/EntityRef.ts index 34ca7b9d9..7b7cf573a 100644 --- a/src/DatabaseServices/Entity/EntityRef.ts +++ b/src/DatabaseServices/Entity/EntityRef.ts @@ -29,14 +29,14 @@ export class EntityRef extends Entity { OnlyRenderType = true; - private _Size = new Vector3;//原始尺寸 - private _ScaleSize = new Vector3;//缩放后的尺寸 - private _Center = new Vector3;//盒子中心 + protected _Size = new Vector3;//原始尺寸 + protected _ScaleSize = new Vector3;//缩放后的尺寸 + protected _Center = new Vector3;//盒子中心 - private _OverWriteMaterial = new Map>();//section index -> materialId + protected _OverWriteMaterial = new Map>();//section index -> materialId // `/Data/ASSETS/DXAA_0001` - constructor(private _url?: string) + constructor(protected _url?: string) { super(); } @@ -47,6 +47,14 @@ export class EntityRef extends Entity } get Url() { return this._url; } + set Url(url: string) + { + if (this._url !== url) + { + this.WriteAllObjectRecord(); + this._url = url; + } + } get CurSize() { return this.ScaleSize.x ? this.ScaleSize.clone() : this._Size.clone(); } @@ -100,6 +108,31 @@ export class EntityRef extends Entity let oldUserDaata = obj.userData; obj.traverse(o => o.userData = {}); let newObj = obj.clone(); + + let mesh1 = []; + let mesh2 = []; + obj.traverse(o => + { + if (o instanceof Mesh) + mesh1.push(o); + }); + + newObj.traverse(o => + { + if (o instanceof Mesh) + { + if (Array.isArray(o.material)) + o.material = [...o.material]; + + mesh2.push(o); + } + }); + + for (let i = 0; i < mesh1.length; i++) + { + mesh2[i]['__old_material__'] = mesh1[i]['__old_material__']; + } + obj.userData = oldUserDaata; // obj.userData.IsClone = true;//因为这个实体不需要修改内部的geom 所以我们可以复用她 @@ -417,7 +450,7 @@ export class EntityRef extends Entity if (!converMtl) { converMtl = ConverMaterial2(oldMtl, this._url); - this.mtlpromiseCache.set(mtl.name, converMtl); + this.mtlpromiseCache.set(oldMtl.name, converMtl); } converMtl.then(mtl => { diff --git a/src/Editor/CommandRegister.ts b/src/Editor/CommandRegister.ts index d63390bde..d92daefe9 100644 --- a/src/Editor/CommandRegister.ts +++ b/src/Editor/CommandRegister.ts @@ -206,7 +206,6 @@ import { Command_SplitTemplate, Command_SplitTemplateByDir } from "../Add-on/Tem import { Command_TemplateSearch } from "../Add-on/TemplateSearch"; import { Command_ClosePt } from "../Add-on/closetest"; import { Command_INsTest } from "../Add-on/instest"; -import { Fbx } from "../Add-on/loadfbx"; import { Command_PLTest } from "../Add-on/polytest"; import { Command_GroovesModify } from "../Add-on/showModal/GroovesModify"; import { ShowEditorBBS } from "../Add-on/showModal/ShowModal"; @@ -221,6 +220,7 @@ import { ApplyModel2ToBoard } from "../Add-on/DrawBoard/ApplyModel2ToBoard"; import { ParseHandle } from "../Add-on/DrawBoard/ParseHandle"; import { ParseHinge } from "../Add-on/DrawBoard/ParseHinge"; import { Command_BoardInfoDimTool } from "../Add-on/DrawDim/BoardInfoDimTool"; +import { Command_FBXImport } from "../Add-on/FBXLoad"; import { Command_Show2DPathLine } from "../Add-on/Show2DPathLine/Show2DPathLine"; import { Command_Show2DPathObject } from "../Add-on/Show2DPathLine/Show2DPathObject"; import { TestFb } from "../Add-on/TestFb"; @@ -332,6 +332,7 @@ export function registerCommand() //CAD图纸导入 commandMachine.RegisterCommand(CommandNames.DXFImport, new Command_DWGDXFImport()); commandMachine.RegisterCommand(CommandNames.DWGImport, new Command_DWGDXFImport()); + commandMachine.RegisterCommand(CommandNames.FBXImport, new Command_FBXImport()); //帮帮我 出错了 commandMachine.RegisterCommand("999", new Command_999()); @@ -420,7 +421,8 @@ export function registerCommand() commandMachine.RegisterCommand("grip", new DrawGripStretch()); - commandMachine.RegisterCommand("fbx", new Fbx()); + //暂时关闭这个fbx导入 + // commandMachine.RegisterCommand("fbx", new Fbx()); commandMachine.RegisterCommand(CommandNames.HideSelect, new Command_HideSelected()); commandMachine.RegisterCommand(CommandNames.HideUnSelect, new Command_HideUnselected()); diff --git a/src/UI/Components/CommandPanel/CommandList.ts b/src/UI/Components/CommandPanel/CommandList.ts index c9281e059..b18d1fcc8 100644 --- a/src/UI/Components/CommandPanel/CommandList.ts +++ b/src/UI/Components/CommandPanel/CommandList.ts @@ -2668,6 +2668,16 @@ export const CommandList: ICommand[] = [ // enName: "Import DXF", chDes: "导入Dwg或者DXF文件", }, + { + typeId: "file", + link: `#`, + defaultCustom: CommandNames.FBXImport, + command: CommandNames.FBXImport, + type: "文件", + chName: "FBX", + // enName: "Import FBX", + chDes: "导入FBX文件", + }, { icon: IconEnum.SaveToDxf, typeId: "file", diff --git a/src/UI/Components/ToolBar/ModifyModel/ModifyModelStore.ts b/src/UI/Components/ToolBar/ModifyModel/ModifyModelStore.ts index 2a9853e52..f5650d641 100644 --- a/src/UI/Components/ToolBar/ModifyModel/ModifyModelStore.ts +++ b/src/UI/Components/ToolBar/ModifyModel/ModifyModelStore.ts @@ -8,6 +8,7 @@ import { CommandHistoryRecord } from "../../../../DatabaseServices/CommandHistor import { CreateObjectData } from "../../../../DatabaseServices/CreateObjectData"; import { Board } from "../../../../DatabaseServices/Entity/Board"; import { Entity } from "../../../../DatabaseServices/Entity/Entity"; +import { EntityFbx } from "../../../../DatabaseServices/Entity/EntityFbx"; import { EntityRef } from "../../../../DatabaseServices/Entity/EntityRef"; import { RoomBase } from "../../../../DatabaseServices/Room/Entity/RoomBase"; import { commandMachine } from "../../../../Editor/CommandMachine"; @@ -127,7 +128,7 @@ export default class ModifyModelStore this.module_Width = ent.Width; this.module_Height = ent.Thickness; } - else if (ent instanceof EntityRef) + else if (ent instanceof EntityRef || ent instanceof EntityFbx) { this.name = "[模型]" + ent.Url; let size = ent.CurSize; @@ -224,7 +225,7 @@ export default class ModifyModelStore if (ents.length === 1) { let ent = ents[0]; - if (ent instanceof EntityRef) + if (ent instanceof EntityRef || ent instanceof EntityFbx) ent.ScaleSize = new Vector3(length, width, height); else if (ent instanceof Board) { diff --git a/src/UI/Components/ToolBar/ModifyModel/ModuleBaseParams.tsx b/src/UI/Components/ToolBar/ModifyModel/ModuleBaseParams.tsx index 639bb1959..5a3dc4026 100644 --- a/src/UI/Components/ToolBar/ModifyModel/ModuleBaseParams.tsx +++ b/src/UI/Components/ToolBar/ModifyModel/ModuleBaseParams.tsx @@ -3,6 +3,7 @@ import { observer } from 'mobx-react'; import React, { Component } from 'react'; import { app } from '../../../../ApplicationServices/Application'; import { Entity } from '../../../../DatabaseServices/Entity/Entity'; +import { EntityFbx } from '../../../../DatabaseServices/Entity/EntityFbx'; import { EntityRef } from '../../../../DatabaseServices/Entity/EntityRef'; import { HardwareTopline } from '../../../../DatabaseServices/Hardware/HardwareTopline'; import { BulkheadCeiling } from '../../../../DatabaseServices/Room/Entity/Ceiling/BulkheadCeiling'; @@ -53,20 +54,23 @@ export default class ModuleBaseParams extends Component -
+