更新打包配置,完善组件入参模式,新增事件总线
This commit is contained in:
@@ -5,6 +5,7 @@ import { Viewer } from './Viewer';
|
||||
import { PMREMGenerator3 } from './PMREMGenerator2';
|
||||
import type { PhysicalMaterialRecord, TextureTableRecord } from 'webcad_ue4_api';
|
||||
import { MaterialEditorCameraControl } from './MaterialMouseControl';
|
||||
import { GetConfig } from '../lib/libOutputConfig';
|
||||
|
||||
async function textureRenderUpdate(textureRecord:TextureTableRecord){
|
||||
const texture = textureRecord['texture'] as Texture;
|
||||
@@ -81,11 +82,6 @@ export class MaterialEditor extends Singleton
|
||||
this.Viewer.UpdateRender();
|
||||
this.Viewer.Fov = 90;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Canvas.appendChild(document);
|
||||
}
|
||||
|
||||
}
|
||||
SetViewer(canvas: HTMLElement)
|
||||
{
|
||||
@@ -119,8 +115,8 @@ export class MaterialEditor extends Singleton
|
||||
|
||||
return new Promise(async (res, rej) =>
|
||||
{
|
||||
let urls = ['right.webp', 'left.webp', 'top.webp', 'bottom.webp', 'front.webp', 'back.webp'];
|
||||
new CubeTextureLoader().setPath('./')
|
||||
let urls = [...GetConfig().envTextureSrc];
|
||||
new CubeTextureLoader()
|
||||
.load(urls, (t) =>
|
||||
{
|
||||
t.encoding = sRGBEncoding;
|
||||
@@ -148,8 +144,8 @@ export class MaterialEditor extends Singleton
|
||||
|
||||
this.exrPromise = new Promise<Texture>((res, rej) =>
|
||||
{
|
||||
let urls = ['right-gray.webp', 'left-gray.webp', 'top-gray.webp', 'bottom-gray.webp', 'front-gray.webp', 'back-gray.webp'];
|
||||
new CubeTextureLoader().setPath('./')
|
||||
let urls = [...GetConfig().grayEnvTextureSrc];
|
||||
new CubeTextureLoader()
|
||||
.load(urls, (t) =>
|
||||
{
|
||||
t.encoding = sRGBEncoding;
|
||||
|
@@ -1,5 +1,21 @@
|
||||
<template>
|
||||
<div vertical class="material-adjuster">
|
||||
<div class="adjust-section">
|
||||
<h3>操作</h3>
|
||||
<fieldset v-if="debugMode" style="margin: 1em 0;">
|
||||
<legend>DEBUG</legend>
|
||||
<label>上传路径ID</label>
|
||||
<input v-model="materialInfo.dirId" type="text" placeholder="材质路径ID" />
|
||||
</fieldset>
|
||||
<label>材质名</label>
|
||||
<input v-model.trim="materialInfo.materialName" type="text" placeholder="材质名" />
|
||||
|
||||
<!-- <CfFlex gap="1em">
|
||||
<button class="btn-success" style="min-width: 110px;" @click="HandleUpload">上传</button>
|
||||
<button class="btn-danger" style="min-width: 110px;" @click="HandleCancel">取消</button>
|
||||
</CfFlex> -->
|
||||
</div>
|
||||
|
||||
<div class="adjust-section">
|
||||
<h3>模型预览</h3>
|
||||
<label>选择模型样式</label>
|
||||
@@ -10,8 +26,8 @@
|
||||
<div class="adjust-section" v-if="debugMode">
|
||||
<h3>纹理选择(DEBUG)</h3>
|
||||
<label>纹理链接</label>
|
||||
<input v-model.trim="textureLink" type="text" placeholder="在此键入纹理图像的URL..." />
|
||||
<button class="btn-primary" @click="scene.ChangeTextureAsync(textureLink)"
|
||||
<input v-model.trim="_textureSrc" type="text" placeholder="在此键入纹理图像的URL..." />
|
||||
<button class="btn-primary" @click="scene.ChangeTextureAsync(_textureSrc)"
|
||||
style="margin-left: 1em;">应用</button>
|
||||
</div>
|
||||
<div class="adjust-section">
|
||||
@@ -85,39 +101,41 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="adjust-section">
|
||||
<h3>操作</h3>
|
||||
<fieldset v-if="debugMode" style="margin: 1em 0;">
|
||||
<legend>DEBUG</legend>
|
||||
<label>上传路径ID</label>
|
||||
<input v-model="materialRequest.dirId" type="text" placeholder="材质路径ID" />
|
||||
</fieldset>
|
||||
<label>材质名</label>
|
||||
<input v-model="materialRequest.materialName" type="text" placeholder="材质名" />
|
||||
|
||||
<CfFlex gap="1em">
|
||||
<button class="btn-success" style="min-width: 110px;" @click="HandleUpload">上传</button>
|
||||
<button class="btn-danger" style="min-width: 110px;" @click="HandleCancel">取消</button>
|
||||
</CfFlex>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts'>
|
||||
import { ref, reactive, watch } from "vue"
|
||||
import { ref, reactive, watch, onMounted } from "vue"
|
||||
import { useScene, type TextureAdjustment } from "../stores/sceneStore";
|
||||
import { storeToRefs } from "pinia";
|
||||
import CfFlex from "./CfFlex.vue";
|
||||
import { DirectoryId } from "../api/Request";
|
||||
import { IsNullOrWhitespace } from "../helpers/helper.string";
|
||||
import { DeflateAsync } from "../helpers/helper.compression";
|
||||
|
||||
export interface MaterialRequest {
|
||||
/** 材质名 */
|
||||
name: string;
|
||||
/** 材质logo文件 */
|
||||
logo: Blob;
|
||||
/** 序列化并Deflate压缩后的材质文件的Base64编码 */
|
||||
file: string;
|
||||
}
|
||||
|
||||
const scene = useScene();
|
||||
|
||||
const debugMode = ref(true);
|
||||
const props = defineProps<{
|
||||
textureSrc?: string;
|
||||
}>();
|
||||
const emits = defineEmits<{
|
||||
(e: 'cancel'): void;
|
||||
(e: 'submit', data: MaterialRequest): void;
|
||||
}>();
|
||||
|
||||
const debugMode = ref(false);
|
||||
const { CurrGeometry, Geometries, Material } = storeToRefs(scene);
|
||||
const enableTexture = ref(Material.value.useMap);
|
||||
const textureLink = ref('');
|
||||
const _textureSrc = ref(props.textureSrc);
|
||||
const textureAdjustment = reactive<TextureAdjustment>({
|
||||
wrapS: 0,
|
||||
wrapT: 0,
|
||||
@@ -134,9 +152,18 @@ const model = reactive({
|
||||
normalScale: Material.value.bumpScale,
|
||||
emissiveIntensity: Material.value.specular
|
||||
});
|
||||
const materialRequest = reactive({
|
||||
const materialInfo = reactive({
|
||||
dirId: DirectoryId.MaterialDir, // 正常来说是2
|
||||
materialName: ''
|
||||
materialName: '材质1'
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
scene.ChangeTextureAsync(_textureSrc.value);
|
||||
})
|
||||
|
||||
watch(() => props.textureSrc, async (val) => {
|
||||
_textureSrc.value = val;
|
||||
await scene.ChangeTextureAsync(_textureSrc.value);
|
||||
});
|
||||
|
||||
watch(model, async (val) => {
|
||||
@@ -171,25 +198,34 @@ async function EnableTexture(enable: boolean) {
|
||||
|
||||
async function HandleUpload() {
|
||||
try {
|
||||
if (IsNullOrWhitespace(materialRequest.materialName)) {
|
||||
if (IsNullOrWhitespace(materialInfo.materialName)) {
|
||||
alert('材质名称不可为空');
|
||||
return;
|
||||
}
|
||||
|
||||
uploading.value = true;
|
||||
await scene.UploadMaterialAsync(materialRequest);
|
||||
const data = {
|
||||
name: materialInfo.materialName,
|
||||
logo: await scene.GenerateMaterialLogoAsync(),
|
||||
// jsonString -> Deflate -> BinaryString -> Base64
|
||||
file: btoa(String.fromCharCode(...await DeflateAsync(await scene.SerializeMaterialAsync())))
|
||||
};
|
||||
emits('submit', data);
|
||||
return data;
|
||||
} finally {
|
||||
uploading.value = false;
|
||||
alert("上传成功");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert("上传材质出错,请检查网络连接或联系管理员");
|
||||
}
|
||||
}
|
||||
|
||||
async function HandleCancel() {
|
||||
// TODO: 触发取消事件
|
||||
function HandleCancel() {
|
||||
emits('cancel');
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
Upload: HandleUpload,
|
||||
Cancel: HandleCancel
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@@ -1,35 +1,59 @@
|
||||
<template>
|
||||
<CfFlex class="material-view">
|
||||
<div ref="container" style="width: 100%; height: 100%; flex: 3; box-sizing: border-box;" />
|
||||
<MaterialAdjuster style="flex: 1;overflow-y: auto; width: 100%; height: 100%; box-sizing: border-box;" />
|
||||
<div ref="container" class="material-view-container" />
|
||||
<MaterialAdjuster ref="adjuster" class="material-view-sider" :texture-src="textureSrc" @cancel="config.cancelCallback" @submit="config.submitCallback" />
|
||||
</CfFlex>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, useTemplateRef } from 'vue';
|
||||
import { onMounted, ref, useTemplateRef } from 'vue';
|
||||
import MaterialAdjuster from './MaterialAdjuster.vue';
|
||||
import { useScene } from '../stores/sceneStore';
|
||||
import CfFlex from './CfFlex.vue';
|
||||
import { GetConfig } from '../lib/libOutputConfig';
|
||||
import { useEvent } from '../stores/eventStore';
|
||||
|
||||
const scene = useScene();
|
||||
const container = useTemplateRef<HTMLElement>('container');
|
||||
const eventbus = useEvent();
|
||||
const container = useTemplateRef('container');
|
||||
const adjusterRef = useTemplateRef('adjuster');
|
||||
const config = GetConfig();
|
||||
const textureSrc = ref(config.textureSrc);
|
||||
|
||||
// 禁用右键菜单
|
||||
document.addEventListener('contextmenu', (e) => e.preventDefault());
|
||||
|
||||
onMounted(() => {
|
||||
scene.Initial(container.value);
|
||||
eventbus.Subscribe('submit', () => adjusterRef.value.Upload());
|
||||
eventbus.Subscribe('update-texture', () => {
|
||||
textureSrc.value = config.textureSrc
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.material-view
|
||||
{
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&-container {
|
||||
flex: 3 1;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-sider {
|
||||
flex: 1 1;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
</style>
|
43
src/lib/entry.ts
Normal file
43
src/lib/entry.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { createApp, type App as VueApp } from 'vue'
|
||||
|
||||
import App from '../App.vue'
|
||||
import '../assets/main.css'
|
||||
import { createPinia } from 'pinia';
|
||||
import { ConfigureLibOutput, type LibOutputConfig } from './libOutputConfig';
|
||||
import { useEvent } from '../stores/eventStore';
|
||||
|
||||
let app: VueApp<Element> = undefined;
|
||||
|
||||
/**
|
||||
* 挂载材质编辑器到指定元素
|
||||
* @param element 要挂载的HTML元素
|
||||
* @param options 程序配置
|
||||
*/
|
||||
export function Mount(element: Element, options: Partial<LibOutputConfig>) {
|
||||
ConfigureLibOutput(options);
|
||||
|
||||
const pinia = createPinia();
|
||||
app = createApp(App);
|
||||
app.use(pinia)
|
||||
.mount(element);
|
||||
}
|
||||
|
||||
/** 对程序配置进行更改 */
|
||||
export function Configure(options: Partial<LibOutputConfig>) {
|
||||
ConfigureLibOutput(options);
|
||||
}
|
||||
|
||||
/** 卸载已经挂载的DOM */
|
||||
export function Unmount() {
|
||||
app.unmount();
|
||||
}
|
||||
|
||||
/** 手动进行材质提交 */
|
||||
export function Submit() {
|
||||
useEvent().Publish('submit');
|
||||
}
|
||||
|
||||
/** 更新材质贴图 */
|
||||
export function UpdateTexture() {
|
||||
useEvent().Publish('update-texture');
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
import MaterialView from "../components/MaterialView.vue";
|
||||
import { Mount, Unmount, Submit, UpdateTexture, Configure } from "./entry";
|
||||
|
||||
export {
|
||||
MaterialView
|
||||
Mount,
|
||||
Unmount,
|
||||
Submit,
|
||||
UpdateTexture,
|
||||
Configure
|
||||
}
|
38
src/lib/libOutputConfig.ts
Normal file
38
src/lib/libOutputConfig.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { DeepReadonly } from "vue"
|
||||
import type { MaterialRequest } from "../components/MaterialAdjuster.vue"
|
||||
|
||||
let _libOutputConfig = {
|
||||
textureSrc: "",
|
||||
submitCallback: undefined,
|
||||
cancelCallback: undefined,
|
||||
envTextureSrc: ['./right.webp', './left.webp', './top.webp', './bottom.webp', './front.webp', './back.webp'],
|
||||
grayEnvTextureSrc: ['./right-gray.webp', './left-gray.webp', './top-gray.webp', './bottom-gray.webp', './front-gray.webp', './back-gray.webp'],
|
||||
} as LibOutputConfig
|
||||
|
||||
export function ConfigureLibOutput(config: Partial<LibOutputConfig>) {
|
||||
Object.assign(_libOutputConfig, {
|
||||
...config
|
||||
});
|
||||
// _libOutputConfig = config;
|
||||
}
|
||||
|
||||
export type LibOutputConfig = {
|
||||
/** 材质贴图链接 */
|
||||
textureSrc: string,
|
||||
/** 环境贴图链接(立方体贴图,按照顺序输入[右左上下前后]) */
|
||||
envTextureSrc: string[],
|
||||
/** 灰度环境贴图链接,输入格式与环境贴图一致 */
|
||||
grayEnvTextureSrc: string[],
|
||||
/** 提交材质时的回调 */
|
||||
submitCallback?: (data: MaterialRequest) => void,
|
||||
/**
|
||||
* 取消材质编辑时的回调
|
||||
* @deprecated 不需要使用
|
||||
*/
|
||||
cancelCallback?: () => void
|
||||
}
|
||||
|
||||
export function GetConfig(): DeepReadonly<typeof _libOutputConfig>
|
||||
{
|
||||
return _libOutputConfig;
|
||||
}
|
18
src/stores/eventStore.ts
Normal file
18
src/stores/eventStore.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useEvent = defineStore('event', () => {
|
||||
const events: Partial<Record<EventNames, Function[]>> = {};
|
||||
|
||||
function Subscribe(name: EventNames, callback: Function) {
|
||||
events[name] = events[name] || [];
|
||||
events[name].push(callback);
|
||||
}
|
||||
|
||||
function Publish(name: EventNames, ...args: any[]) {
|
||||
events[name]?.forEach(e => e(...args));
|
||||
}
|
||||
|
||||
return { Subscribe, Publish };
|
||||
});
|
||||
|
||||
export type EventNames = 'submit' | 'update-texture'
|
@@ -1,14 +1,11 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { MaterialEditor } from "../common/MaterialEditor";
|
||||
import { Database, ObjectId, PhysicalMaterialRecord, TextureTableRecord } from "webcad_ue4_api";
|
||||
import { ObjectId, PhysicalMaterialRecord, TextureTableRecord } from "webcad_ue4_api";
|
||||
import { LoadImageFromUrl } from "../helpers/helper.imageLoader";
|
||||
import { Texture } from "three";
|
||||
import { materialRenderer } from "../common/MaterialRenderer";
|
||||
import { ImgsUrl, MaterialUrls } from "../api/Api";
|
||||
import { Post, PostJson, RequestStatus } from "../api/Request";
|
||||
import { MaterialOut } from "../common/MaterialSerializer";
|
||||
import { DeflateAsync } from "../helpers/helper.compression";
|
||||
|
||||
export const useScene = defineStore('scene', () => {
|
||||
let _editor: MaterialEditor;
|
||||
@@ -72,7 +69,7 @@ export const useScene = defineStore('scene', () => {
|
||||
|
||||
_currTexture.value = record['texture'] as Texture;
|
||||
_currTexture.value.image = img;
|
||||
Material.value.map = record.Id;
|
||||
Material.value.map = img ? record.Id : undefined;
|
||||
_currTexture.value.needsUpdate = true;
|
||||
await UpdateMaterialAsync();
|
||||
}
|
||||
@@ -90,31 +87,28 @@ export const useScene = defineStore('scene', () => {
|
||||
Update();
|
||||
}
|
||||
|
||||
interface UploadMaterialRequest {
|
||||
dirId: string;
|
||||
materialName: string;
|
||||
}
|
||||
async function UploadMaterialAsync(request: UploadMaterialRequest) {
|
||||
async function SerializeMaterialAsync() {
|
||||
// TODO: Warn: 是否要生成logo路径?
|
||||
// const logoPath = await HandleUpdateLogo();
|
||||
const matJson = MaterialOut(Material.value as PhysicalMaterialRecord);
|
||||
console.log(matJson);
|
||||
return matJson;
|
||||
}
|
||||
|
||||
async function HandleUpdateLogo() {
|
||||
async function GenerateMaterialLogoAsync() {
|
||||
const blob = await materialRenderer.getBlob(Material.value.Material);
|
||||
const file = new File([blob], "blob.png", { type: blob.type });
|
||||
return blob;
|
||||
// const file = new File([blob], "blob.png", { type: blob.type });
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
// const formData = new FormData();
|
||||
// formData.append("file", file);
|
||||
|
||||
let data = await Post(ImgsUrl.logo, formData);
|
||||
// let data = await Post(ImgsUrl.logo, formData);
|
||||
|
||||
let logoPath = "";
|
||||
if (data.err_code === RequestStatus.Ok) {
|
||||
logoPath = data.images.path;
|
||||
}
|
||||
return logoPath;
|
||||
// let logoPath = "";
|
||||
// if (data.err_code === RequestStatus.Ok) {
|
||||
// logoPath = data.images.path;
|
||||
// }
|
||||
// return logoPath;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -126,7 +120,8 @@ export const useScene = defineStore('scene', () => {
|
||||
UpdateMaterialAsync,
|
||||
ChangeTextureAsync,
|
||||
UpdateTexture,
|
||||
UploadMaterialAsync,
|
||||
SerializeMaterialAsync,
|
||||
GenerateMaterialLogoAsync
|
||||
};
|
||||
});
|
||||
|
||||
@@ -138,4 +133,8 @@ export type TextureAdjustment = {
|
||||
repeatY: number,
|
||||
moveX: number,
|
||||
moveY: number
|
||||
}
|
||||
export interface UploadMaterialRequest {
|
||||
dirId: string;
|
||||
materialName: string;
|
||||
}
|
Reference in New Issue
Block a user