产品新增3d模型展示

This commit is contained in:
jinling.yang
2023-03-06 17:02:21 +08:00
parent 84a674e4e2
commit 2df811d4ce
35 changed files with 159 additions and 1291 deletions

View File

@@ -1,2 +0,0 @@
from . import models
from . import controllers

View File

@@ -1,32 +0,0 @@
{
"name": "OWL开发演示",
"summary": "示例OWL开发模块。",
"description": "用于演示OWL模块的开发。",
"author": "Van",
"website": "https://topodoo.com",
"category": "Tutorials",
"version": "15.0.0.1",
"depends": ["sale", "sale_management"],
"demo": [],
"data": [
#'report/test_sale_report.xml',
'views/views.xml',
],
'assets': {
'web.assets_qweb': [
# 'owl_demo/static/src/xml/MyComponent.xml',
#'owl_demo/static/src/js/html_template/template_demo_field.xml',
'owl_demo/static/src/js/3d_viewer/3d_viewer.xml',
],
'web.assets_backend': [
# 'owl_demo/static/src/xml/MyComponent.xml',
# 'owl_demo/static/src/js/components/MyComponent.js',
# 'owl_demo/static/src/js/services/*',
#'owl_demo/static/src/js/html_template/*',
'owl_demo/static/src/js/3d_viewer/*',
'owl_demo/static/src/lib/model-viewer.min.js',
],
}
}

View File

@@ -1 +0,0 @@
from . import main

View File

@@ -1,266 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import io
import json
import os
from odoo import _
from odoo import http
from odoo.http import request
class Viewer3d(http.Controller):
@http.route(['/Viewer3d/<int:attachment_id>'], type='http', auth="None")
def viewer3d(self, attachment_id, **kwargs):
attachment = self._attachment_get(attachment_id)
return attachment
def _attachment_get(self, attachment_id):
attachment = request.env['ir.attachment'].sudo().browse(attachment_id)
if not attachment:
return
return attachment.datas
def stl_to_gltf(binary_stl_path, out_path, is_binary):
import struct
gltf2 = '''
{
"scenes" : [
{
"nodes" : [ 0 ]
}
],
"nodes" : [
{
"mesh" : 0
}
],
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0
} ]
}
],
"buffers" : [
{
%s
"byteLength" : %d
}
],
"bufferViews" : [
{
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : %d,
"target" : 34963
},
{
"buffer" : 0,
"byteOffset" : %d,
"byteLength" : %d,
"target" : 34962
}
],
"accessors" : [
{
"bufferView" : 0,
"byteOffset" : 0,
"componentType" : 5125,
"count" : %d,
"type" : "SCALAR",
"max" : [ %d ],
"min" : [ 0 ]
},
{
"bufferView" : 1,
"byteOffset" : 0,
"componentType" : 5126,
"count" : %d,
"type" : "VEC3",
"min" : [%f, %f, %f],
"max" : [%f, %f, %f]
}
],
"asset" : {
"version" : "2.0"
}
}
'''
header_bytes = 80
unsigned_long_int_bytes = 4
float_bytes = 4
vec3_bytes = 4 * 3
spacer_bytes = 2
num_vertices_in_face = 3
vertices = {}
indices = []
if not is_binary:
out_bin = os.path.join(out_path, "out.bin")
out_gltf = os.path.join(out_path, "out.gltf")
else:
out_bin = out_path
unpack_face = struct.Struct("<12fH").unpack
face_bytes = float_bytes * 12 + 2
with open(path_to_stl, "rb") as f:
f.seek(header_bytes) # skip 80 bytes headers
num_faces_bytes = f.read(unsigned_long_int_bytes)
number_faces = struct.unpack("<I", num_faces_bytes)[0]
# the vec3_bytes is for normal
stl_assume_bytes = header_bytes + unsigned_long_int_bytes + number_faces * (
vec3_bytes * 3 + spacer_bytes + vec3_bytes)
assert stl_assume_bytes == os.path.getsize(path_to_stl), "stl is not binary or ill formatted"
minx, maxx = [9999999, -9999999]
miny, maxy = [9999999, -9999999]
minz, maxz = [9999999, -9999999]
vertices_length_counter = 0
data = struct.unpack("<" + "12fH" * number_faces, f.read())
len_data = len(data)
for i in range(0, len_data, 13):
for j in range(3, 12, 3):
x, y, z = data[i + j:i + j + 3]
x = int(x * 100000) / 100000
y = int(y * 100000) / 100000
z = int(z * 100000) / 100000
tuple_xyz = (x, y, z);
try:
indices.append(vertices[tuple_xyz])
except KeyError:
vertices[tuple_xyz] = vertices_length_counter
vertices_length_counter += 1
indices.append(vertices[tuple_xyz])
if x < minx: minx = x
if x > maxx: maxx = x
if y < miny: miny = y
if y > maxy: maxy = y
if z < minz: minz = z
if z > maxz: maxz = z
# f.seek(spacer_bytes, 1) # skip the spacer
number_vertices = len(vertices)
vertices_bytelength = number_vertices * vec3_bytes # each vec3 has 3 floats, each float is 4 bytes
unpadded_indices_bytelength = number_vertices * unsigned_long_int_bytes
out_number_vertices = len(vertices)
out_number_indices = len(indices)
unpadded_indices_bytelength = out_number_indices * unsigned_long_int_bytes
indices_bytelength = (unpadded_indices_bytelength + 3) & ~3
out_bin_bytelength = vertices_bytelength + indices_bytelength
if is_binary:
out_bin_uir = ""
else:
out_bin_uir = '"uri": "out.bin",'
gltf2 = gltf2 % (out_bin_uir,
# buffer
out_bin_bytelength,
# bufferViews[0]
indices_bytelength,
# bufferViews[1]
indices_bytelength,
vertices_bytelength,
# accessors[0]
out_number_indices,
out_number_vertices - 1,
# accessors[1]
out_number_vertices,
minx, miny, minz,
maxx, maxy, maxz
)
glb_out = bytearray()
if is_binary:
gltf2 = gltf2.replace(" ", "")
gltf2 = gltf2.replace("\n", "")
scene = bytearray(gltf2.encode())
scene_len = len(scene)
padded_scene_len = (scene_len + 3) & ~3
body_offset = padded_scene_len + 12 + 8
file_len = body_offset + out_bin_bytelength + 8
# 12-byte header
glb_out.extend(struct.pack('<I', 0x46546C67)) # magic number for glTF
glb_out.extend(struct.pack('<I', 2))
glb_out.extend(struct.pack('<I', file_len))
# chunk 0
glb_out.extend(struct.pack('<I', padded_scene_len))
glb_out.extend(struct.pack('<I', 0x4E4F534A)) # magic number for JSON
glb_out.extend(scene)
while len(glb_out) < body_offset:
glb_out.extend(b' ')
# chunk 1
glb_out.extend(struct.pack('<I', out_bin_bytelength))
glb_out.extend(struct.pack('<I', 0x004E4942)) # magin number for BIN
# print('<%dI' % len(indices))
# print(struct.pack('<%dI' % len(indices), *indices))
glb_out.extend(struct.pack('<%dI' % len(indices), *indices))
for i in range(indices_bytelength - unpadded_indices_bytelength):
glb_out.extend(b' ')
vertices = dict((v, k) for k, v in vertices.items())
# glb_out.extend(struct.pack('f',
# print([each_v for vertices[v_counter] for v_counter in range(number_vertices)]) # magin number for BIN
vertices = [vertices[i] for i in range(number_vertices)]
flatten = lambda l: [item for sublist in l for item in sublist]
# for v_counter in :
# v_3f = vertices[v_counter]
# all_floats_in_vertices.append(v_3f[0])
# all_floats_in_vertices.append(v_3f[1])
# all_floats_in_vertices.append(v_3f[2])
# for v_counter in range(number_vertices):
glb_out.extend(struct.pack('%df' % number_vertices * 3, *flatten(vertices))) # magin number for BIN
# for v_counter in range(number_vertices):
# glb_out.extend(struct.pack('3f', *vertices[v_counter])) # magin number for BIN
# for (v_x, v_y, v_z), _ in sorted(vertices.items(), key=lambda x: x[1]):
# glb_out.extend(struct.pack('3f', v_x, v_y, v_z)) # magin number for BIN
# # glb_out.extend(struct.pack('f', v_y)) # magin number for BIN
# # glb_out.extend(struct.pack('f', v_z)) # magin number for BIN
with open(out_bin, "wb") as out:
out.write(glb_out)
if not is_binary:
with open(out_gltf, "w") as out:
out.write(gltf2)

View File

@@ -1 +0,0 @@
from . import sale_order

View File

@@ -1,15 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import models,fields
from odoo.tools import populate, groupby
_logger = logging.getLogger(__name__)
class SaleOrder(models.Model):
_inherit = "sale.order"
step_file = fields.Binary("Step File")

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="sale.report_saleorder_document" inherit_id="sale.report_saleorder_document">
<t t-call="web.external_layout">
<h1>hello jacker!</h1>
</t>
</template>
</odoo>

View File

@@ -1,30 +0,0 @@
.o_int_colorpicker {
.o_color_pill {
display: inline-block;
height: 25px;
width: 25px;
margin: 4px;
border-radius: 25px;
position: relative;
@for $size from 1 through length($o-colors) {
&.o_color_#{$size - 1} {
background-color: nth($o-colors, $size);
&:not(.readonly):hover {
transform: scale(1.2);
transition: 0.3s;
cursor: pointer;
}
&.active:after{
content: "\f00c";
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
color: #fff;
position: absolute;
padding: 4px;
font-size: 16px;
}
}
}
}
}

View File

@@ -1,127 +0,0 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { _lt } from "@web/core/l10n/translation";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { useInputField } from "@web/views/fields/input_field_hook";
import { FileUploader } from "@web/views/fields/file_handler";
import { session } from "@web/session";
import { useService } from "@web/core/utils/hooks";
import { isBinarySize } from "@web/core/utils/binary";
import { download } from "@web/core/network/download";
import utils from 'web.utils';
import core from 'web.core';
import rpc from 'web.rpc';
var QWeb = core.qweb;
import { Component, onWillUpdateProps, useState, useRef, useEffect } from "@odoo/owl";
export class StepViewer extends Component {
setup() {
this.notification = useService("notification");
this.state = useState({
fileName: this.props.record.data[this.props.fileNameField] || "",
});
onWillUpdateProps((nextProps) => {
if (nextProps.readonly) {
this.state.fileName = nextProps.record.data[nextProps.fileNameField] || "";
}
});
this.show_template = useRef('showStepViewer')
useInputField({
getValue: () => this.props.value || ""
});
useEffect(
(el) => {
if (!el) {
return;
}
return this.formatTemplate($(el));
},
() => [this.show_template.el],
)
}
formatTemplate($el) {
console.log($el)
var self=this;
if (this.props.readonly && this.props.value) {
var url = "/owl_demo/static/src/js/3d_viewer/test.glb";
// var session = this.getSession();
// if (this.props.value) {
// if (utils.is_bin_size(this.props.value)) {
// url = session.url("/web/content", {
// model: this.model,
// id: JSON.stringify(this.props.record.res_id),
// field: this.props.record.name,
// });
// } else {
// url = "data:model/gltf-binary;base64," + this.propsvalue.value;
// }
// }
return $el.html(QWeb.render("owl_demo.StepViewer",{'url':url}))
}
}
get fileName() {
return this.state.fileName || this.props.value || "";
}
update({ data, name }) {
this.state.fileName = name || "";
const { fileNameField, record } = this.props;
const changes = { [this.props.name]: data || false };
if (fileNameField in record.fields && record.data[fileNameField] !== name) {
changes[fileNameField] = name || false;
}
return this.props.record.update(changes);
}
async onFileDownload() {
await download({
data: {
model: this.props.record.resModel,
id: this.props.record.resId,
field: this.props.name,
filename_field: this.fileName,
filename: this.fileName || "",
download: true,
data: isBinarySize(this.props.value) ? null : this.props.value,
},
url: "/web/content",
});
}
}
StepViewer.template = "owl_demo.BinaryField3d";
StepViewer.displayName = _lt("3D File");
StepViewer.supportedTypes = ["binary"];
StepViewer.components = {
FileUploader,
};
StepViewer.props = {
...standardFieldProps,
acceptedFileExtensions: { type: String, optional: true },
fileNameField: { type: String, optional: true },
};
StepViewer.defaultProps = {
acceptedFileExtensions: "*.stp",
};
StepViewer.extractProps = ({ attrs }) => {
return {
acceptedFileExtensions: attrs.options.accepted_file_extensions,
fileNameField: attrs.filename,
};
};
registry.category("fields").add("Viewer3D", StepViewer);

View File

@@ -1,88 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="owl_demo.BinaryField3d" owl="1">
<t t-if="!props.readonly">
<t t-if="props.value">
<div class="w-100 d-inline-flex">
<FileUploader
acceptedFileExtensions="props.acceptedFileExtensions"
file="{ data: props.value, name: fileName }"
onUploaded.bind="update"
>
<t if="props.record.resId">
<button
class="btn btn-secondary fa fa-download"
data-tooltip="Download"
aria-label="Download"
t-on-click="onFileDownload"
/>
</t>
<t t-set-slot="toggler">
<input type="text" class="o_input" t-att-value="fileName" readonly="readonly" />
<button
class="btn btn-secondary fa fa-pencil o_select_file_button"
data-tooltip="Edit"
aria-label="Edit"
/>
</t>
<button
class="btn btn-secondary fa fa-trash o_clear_file_button"
data-tooltip="Clear"
aria-label="Clear"
t-on-click="() => this.update({})"
/>
</FileUploader>
</div>
</t>
<t t-else="">
<label class="o_select_file_button btn btn-primary">
<FileUploader
acceptedFileExtensions="props.acceptedFileExtensions"
onUploaded.bind="update"
>
<t t-set-slot="toggler">
Upload your file
</t>
</FileUploader>
</label>
</t>
</t>
<t t-elif="props.record.resId and props.value">
<!-- <div t-ref="showStepViewer"/>-->
<model-viewer urc="/owl_demo/static/src/js/3d_viewer/test.glb" ar-modes="scene-viewer webxr quick-look" poster="poster.webp" shadow-intensity="1" camera-orbit="180deg 72.29deg 12640m" field-of-view="30deg">
<div class="progress-bar hide" slot="progress-bar">
<div class="update-bar"></div>
</div>
</model-viewer>
</t>
</t>
<t t-name="owl_demo.StepViewer">
<model-viewer
t-att-src='url'
name="3D model"
alt="3D model"
auto-rotate="1"
camera-controls="1"
>
<div class="text-center mt-4 mb-4 mr-4">
<span
id="model-viewer-fullscreen"
title="View fullscreen"
role="img"
aria-label="Fullscreen"
>
<i class="fa fa-arrows-alt fa-2x" />
</span>
</div>
<!-- <span style="position: absolute;top: 85%;left: 0%;font-size: 9px;" t-if="widget.model == 'jikimo.process.order.line'">-->
<!-- L<t t-esc="widget.recordData.model_length"/>-->
<!-- W:<t t-esc="widget.recordData.model_width"/>-->
<!-- H:<t t-esc="widget.recordData.model_height"/>-->
<!-- V:<t t-esc="widget.recordData.model_volume"/>-->
<!-- </span>-->
</model-viewer>
</t>
</templates>

View File

@@ -1,277 +0,0 @@
import os
def stl_to_gltf(binary_stl_path, out_path, is_binary):
import struct
gltf2 = '''
{
"scenes" : [
{
"nodes" : [ 0 ]
}
],
"nodes" : [
{
"mesh" : 0
}
],
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0
} ]
}
],
"buffers" : [
{
%s
"byteLength" : %d
}
],
"bufferViews" : [
{
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : %d,
"target" : 34963
},
{
"buffer" : 0,
"byteOffset" : %d,
"byteLength" : %d,
"target" : 34962
}
],
"accessors" : [
{
"bufferView" : 0,
"byteOffset" : 0,
"componentType" : 5125,
"count" : %d,
"type" : "SCALAR",
"max" : [ %d ],
"min" : [ 0 ]
},
{
"bufferView" : 1,
"byteOffset" : 0,
"componentType" : 5126,
"count" : %d,
"type" : "VEC3",
"min" : [%f, %f, %f],
"max" : [%f, %f, %f]
}
],
"asset" : {
"version" : "2.0"
}
}
'''
header_bytes = 80
unsigned_long_int_bytes = 4
float_bytes = 4
vec3_bytes = 4 * 3
spacer_bytes = 2
num_vertices_in_face = 3
vertices = {}
indices = []
if not is_binary:
out_bin = os.path.join(out_path, "out.bin")
out_gltf = os.path.join(out_path, "out.gltf")
else:
out_bin = out_path
unpack_face = struct.Struct("<12fH").unpack
face_bytes = float_bytes*12 + 2
with open(path_to_stl, "rb") as f:
f.seek(header_bytes) # skip 80 bytes headers
num_faces_bytes = f.read(unsigned_long_int_bytes)
number_faces = struct.unpack("<I", num_faces_bytes)[0]
# the vec3_bytes is for normal
stl_assume_bytes = header_bytes + unsigned_long_int_bytes + number_faces * (vec3_bytes*3 + spacer_bytes + vec3_bytes)
assert stl_assume_bytes == os.path.getsize(path_to_stl), "stl is not binary or ill formatted"
minx, maxx = [9999999, -9999999]
miny, maxy = [9999999, -9999999]
minz, maxz = [9999999, -9999999]
vertices_length_counter = 0
data = struct.unpack("<" + "12fH"*number_faces, f.read())
len_data = len(data)
for i in range(0, len_data, 13):
for j in range(3, 12, 3):
x, y, z = data[i+j:i+j+3]
x = int(x*100000)/100000
y = int(y*100000)/100000
z = int(z*100000)/100000
tuple_xyz = (x, y, z);
try:
indices.append(vertices[tuple_xyz])
except KeyError:
vertices[tuple_xyz] = vertices_length_counter
vertices_length_counter += 1
indices.append(vertices[tuple_xyz])
if x < minx: minx = x
if x > maxx: maxx = x
if y < miny: miny = y
if y > maxy: maxy = y
if z < minz: minz = z
if z > maxz: maxz = z
# f.seek(spacer_bytes, 1) # skip the spacer
number_vertices = len(vertices)
vertices_bytelength = number_vertices * vec3_bytes # each vec3 has 3 floats, each float is 4 bytes
unpadded_indices_bytelength = number_vertices * unsigned_long_int_bytes
out_number_vertices = len(vertices)
out_number_indices = len(indices)
unpadded_indices_bytelength = out_number_indices * unsigned_long_int_bytes
indices_bytelength = (unpadded_indices_bytelength + 3) & ~3
out_bin_bytelength = vertices_bytelength + indices_bytelength
if is_binary:
out_bin_uir = ""
else:
out_bin_uir = '"uri": "out.bin",'
gltf2 = gltf2 % ( out_bin_uir,
#buffer
out_bin_bytelength,
# bufferViews[0]
indices_bytelength,
# bufferViews[1]
indices_bytelength,
vertices_bytelength,
# accessors[0]
out_number_indices,
out_number_vertices - 1,
# accessors[1]
out_number_vertices,
minx, miny, minz,
maxx, maxy, maxz
)
glb_out = bytearray()
if is_binary:
gltf2 = gltf2.replace(" ", "")
gltf2 = gltf2.replace("\n", "")
scene = bytearray(gltf2.encode())
scene_len = len(scene)
padded_scene_len = (scene_len + 3) & ~3
body_offset = padded_scene_len + 12 + 8
file_len = body_offset + out_bin_bytelength + 8
# 12-byte header
glb_out.extend(struct.pack('<I', 0x46546C67)) # magic number for glTF
glb_out.extend(struct.pack('<I', 2))
glb_out.extend(struct.pack('<I', file_len))
# chunk 0
glb_out.extend(struct.pack('<I', padded_scene_len))
glb_out.extend(struct.pack('<I', 0x4E4F534A)) # magic number for JSON
glb_out.extend(scene)
while len(glb_out) < body_offset:
glb_out.extend(b' ')
# chunk 1
glb_out.extend(struct.pack('<I', out_bin_bytelength))
glb_out.extend(struct.pack('<I', 0x004E4942)) # magin number for BIN
# print('<%dI' % len(indices))
# print(struct.pack('<%dI' % len(indices), *indices))
glb_out.extend(struct.pack('<%dI' % len(indices), *indices))
for i in range(indices_bytelength - unpadded_indices_bytelength):
glb_out.extend(b' ')
vertices = dict((v, k) for k,v in vertices.items())
# glb_out.extend(struct.pack('f',
# print([each_v for vertices[v_counter] for v_counter in range(number_vertices)]) # magin number for BIN
vertices = [vertices[i] for i in range(number_vertices)]
flatten = lambda l: [item for sublist in l for item in sublist]
# for v_counter in :
# v_3f = vertices[v_counter]
# all_floats_in_vertices.append(v_3f[0])
# all_floats_in_vertices.append(v_3f[1])
# all_floats_in_vertices.append(v_3f[2])
# for v_counter in range(number_vertices):
glb_out.extend(struct.pack('%df' % number_vertices*3, *flatten(vertices))) # magin number for BIN
# for v_counter in range(number_vertices):
# glb_out.extend(struct.pack('3f', *vertices[v_counter])) # magin number for BIN
# for (v_x, v_y, v_z), _ in sorted(vertices.items(), key=lambda x: x[1]):
# glb_out.extend(struct.pack('3f', v_x, v_y, v_z)) # magin number for BIN
# # glb_out.extend(struct.pack('f', v_y)) # magin number for BIN
# # glb_out.extend(struct.pack('f', v_z)) # magin number for BIN
with open(out_bin, "wb") as out:
out.write(glb_out)
if not is_binary:
with open(out_gltf, "w") as out:
out.write(gltf2)
print("Done! Exported to %s" %out_path)
if __name__ == '__main__':
import sys
if len(sys.argv) < 3:
print("use it like python3 stl_to_gltf.py /path/to/stl /path/to/gltf/folder")
print("or python3 stl_to_gltf.py /path/to/stl /path/to/glb/file -b")
sys.exit(1)
path_to_stl = sys.argv[1]
out_path = sys.argv[2]
if len(sys.argv) > 3:
is_binary = True
else:
is_binary = False
if out_path.lower().endswith(".glb"):
print("Use binary mode since output file has glb extension")
is_binary = True
else:
if is_binary:
print("output file should have glb extension but not %s", out_path)
if not os.path.exists(path_to_stl):
print("stl file does not exists %s" % path_to_stl)
if not is_binary:
if not os.path.isdir(out_path):
os.mkdir(out_path)
stl_to_gltf(path_to_stl, out_path, is_binary)

View File

@@ -1,25 +0,0 @@
/** @odoo-module **/
const { Component, useState, mount, whenReady, xml } = owl;
export class MyComponent extends Component {
static template = 'owl_demo.my_component'
setup() {
this.state = useState({ is_show:true});
}
onRemove(ev) {
this.state.is_show = false;
}
}
//MyComponent.template = 'owl_demo.MyComponent';
whenReady(() => {
var my_component = new MyComponent();
mount(my_component, document.body);
//$("#myModal").modal('show');
});

View File

@@ -1,84 +0,0 @@
/** @odoo-module **/
const { Component, useState, mount, xml } = owl;
import { registry } from "@web/core/registry";
import { formView } from "@web/views/form/form_view";
import { FormCompiler } from "@web/views/form/form_compiler";
import { FormRenderer } from '@web/views/form/form_renderer';
class PartnerOrderSummary extends Component {
partner = useState({});
constructor(self, partner) {
super();
this.partner = partner;
}
};
PartnerOrderSummary.template = "owl_demo.PartnerOrderSummary";
function compilePartner(el, params) {
// this._rpc({
// model: "res.partner",
// method: "read",
// args: [[this.state.data.partner_id.res_id]]
// }).then(data => {
// (new ComponentWrapper(this,
// PartnerOrderSummary,
// useState(data[0])
// )).mount(element);
// });
return "<h1>hello world</h1>";
}
function compileForm() {
const res = this.compileForm(...arguments);
res.classList.append("test111");
return res;
}
export class SaleOrderFormCompiler extends FormCompiler {
setup() {
super.setup();
this.compilers.unshift(
{ selector: "form", fn: compileForm },
{ selector: "div .o_partner_order_summary", fn: compilePartner }
);
}
}
export class SaleOrderFormRenderer extends FormRenderer { }
SaleOrderFormRenderer.components = {
...FormRenderer.components,
PartnerOrderSummary,
};
registry.category('views').add('sale.view_order_form', {
...formView,
Compiler: SaleOrderFormCompiler,
Renderer: SaleOrderFormRenderer,
});
/*** 重载表单渲染器对任何包含o_partner_order_summary这一class 的div挂载当前组件 */
//FormRenderer.include({
// _render: async function() {
// await this._super(...arguments);
// for (const element of this.el.querySelectorAll(".o_partner_order_summary")) {
// this._rpc({
// model: "res.partner",
// method: "read",
// args: [[this.state.data.partner_id.res_id]]
// }).then(data => {
// (new ComponentWrapper(this,
// PartnerOrderSummary,
// useState(data[0])
// )).mount(element);
// });
// }
// }
//});

View File

@@ -1,64 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="owl_demo.field_team_id">
<ul class="list-group">
<li class="list-group-item active">Cras justo odio</li>
<li class="list-group-item">Dapibus ac facilisis in</li>
<li class="list-group-item">Morbi leo risus</li>
<li class="list-group-item">Porta ac consectetur ac</li>
<li class="list-group-item">Vestibulum at eros</li>
</ul>
</t>
<!-- 不要加 owl="1" -->
<t t-name="owl_demo.field_order_line">
<!-- <t t-foreach="current_field" t-as="order_line">-->
<!-- <h6><t t-out="order_line.name"/>-->
<!-- <span class="badge rounded-pill dropdown o_tag o_tag_color_7"><t t-out="order_line.product_uom_qty"/></span></h6>-->
<!-- -->
<!-- </t>-->
<table class="table table-bordered">
<!-- <thead>-->
<!-- <tr>-->
<!-- <th scope="col">#</th>-->
<!-- <th scope="col">product</th>-->
<!-- <th scope="col">qty</th>-->
<!-- <th scope="col">subtotal</th>-->
<!-- </tr>-->
<!-- </thead>-->
<tbody>
<t t-set="index" t-value="1"/>
<t t-foreach="current_field" t-as="order_line">
<tr class="o_tag o_tag_color_{{index}}">
<th scope="row"><t t-out="index"/></th>
<td><t t-out="order_line.name.substr(0,20)"/></td>
<td><t t-out="order_line.product_uom_qty"/></td>
<td><t t-out="order_line.price_subtotal"/></td>
</tr>
<t t-set="index" t-value="index+1"/>
</t>
</tbody>
</table>
</t>
<t t-name="owl_demo.field_partner_id">
<h5><t t-out="current_field.name"/></h5>
<span class="badge rounded-pill dropdown o_tag o_tag_color_3"><t t-out="current_field.phone"/></span>
</t>
<!-- <t t-name="owl_demo.field_order_line">-->
<!--&lt;!&ndash; <h3><t t-out="record.data.name"/></h3>&ndash;&gt;-->
<!-- <ul class="list-group">-->
<!-- <t t-foreach="record.data.order_line.records" t-as="order_line">-->
<!-- <li class="list-group-item d-flex justify-content-between align-items-center">-->
<!-- <t t-out="order_line.id"/>-->
<!-- <span class="badge rounded-pill">14</span>-->
<!-- </li>-->
<!-- </t>-->
<!-- </ul>-->
<!-- </t>-->
</templates>

View File

@@ -1,97 +0,0 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { _lt } from "@web/core/l10n/translation";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { useInputField } from "@web/views/fields/input_field_hook";
import { session } from "@web/session";
import core from 'web.core';
import rpc from 'web.rpc';
var QWeb = core.qweb;
import { Component,useRef,useEffect } from "@odoo/owl";
export class CTemplateField extends Component {
setup() {
this.show_template = useRef('showTemplate')
useInputField({
getValue: () => this.props.value || ""
});
useEffect(
(el) => {
if (!el) {
return;
}
return this.formatTemplate($(el));
},
() => [this.show_template.el],
)
}
formatTemplate($el) {
console.log($el)
var self=this;
if (this.props.readonly && this.props.value) {
//value = this.props.value + ' | ' + this.props.template_xml_id;
if (this.props.type == 'one2many' || this.props.type == 'many2many'){
// 如果是one2many或many2many就重新获取对象列表
rpc.query({
model: this.props.value.resModel,
method: 'read',
args: [this.props.value.currentIds,[]],
}).then((result) => {
console.log(result);
return $el.html(QWeb.render(this.props.template_xml_id,{'record':this.props.record,'current_field':result}))
});
}
else if (this.props.type == 'many2one'){
// 如果是one2many或many2many就重新获取对象列表
rpc.query({
model: this.props.record.fields[this.props.name].relation,
method: 'read',
args: [this.props.value[0],[]],
}).then((result) => {
console.log(result);
return $el.html(QWeb.render(this.props.template_xml_id,{'record':this.props.record,'current_field':result[0]}))
});
}
else{
if (this.props.template_xml_id === undefined)
{
return $el.html(this.props.value);
}else{
return $el.html(QWeb.render(this.props.template_xml_id,{'record':this.props.record,'current_field':this.props.value}));
}
}
}
//
}
}
CTemplateField.template = "owl_demo.CTemplateField";
CTemplateField.props = {
...standardFieldProps,
template_xml_id: { type: String, optional: true },
placeholder: { type: String, optional: true },
};
CTemplateField.defaultProps = {
// hideSymbol: false,
// inputType: "text",
};
CTemplateField.supportedTypes = ["many2one","char"];
CTemplateField.displayName = _lt("CTemplate");
CTemplateField.extractProps = ({ attrs }) => {
return {
template_xml_id: attrs.options.template,
placeholder: attrs.placeholder,
};
};
registry.category("fields").add("CTemplate", CTemplateField);

View File

@@ -1,3 +0,0 @@
.o_field_widget.o_field_template {
display: block;
}

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="owl_demo.CTemplateField" owl="1">
<div t-ref="showTemplate"/>
</t>
</templates>

View File

@@ -1,25 +0,0 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
const serviceRegistry = registry.category("services");
const myService = {
dependencies: ["notification","title","effect"],
start(env, { notification, title,effect }) {
// let counter = 1;
// setInterval(() => {
// var tik_str = `Tick Tock ${counter++}`;
// notification.add(tik_str);
// title.setParts({ odoo: tik_str, fruit: tik_str });
// effect.add({
// type: "rainbow_man", // can be omitted, default type is already "rainbow_man"
// message: "Boom! Team record for the past 30 days." + tik_str,
// });
// }, 5000);
}
};
serviceRegistry.add("myService", myService);

View File

@@ -1,50 +0,0 @@
/** @odoo-module **/
import AbstractField from 'web.AbstractField';
import fieldRegistry from 'web.field_registry';
export const ShowUnitsWidgetField = AbstractField.extend({
supportedFieldTypes: ['float','char','datetime'],
/**
* @override
*/
// init: function () {
// this._super.apply(this, arguments);
// this.units = this.nodeOptions && this.nodeOptions.units || '';
// },
/**
* @override
*/
_renderReadonly() {
this.units = this.nodeOptions && this.nodeOptions.units || '';
this.$el.empty().html(this._formatValue(this.value) + " <b>"+this.units+"</b>");
},
});
fieldRegistry.add('show_units', ShowUnitsWidgetField);
export const ShowUnitsWidgetField = AbstractField.extend({
supportedFieldTypes: ['float','char','datetime'],
/**
* @override
*/
// init: function () {
// this._super.apply(this, arguments);
// this.units = this.nodeOptions && this.nodeOptions.units || '';
// },
/**
* @override
*/
_renderReadonly() {
this.units = this.nodeOptions && this.nodeOptions.units || '';
this.$el.empty().html(this._formatValue(this.value) + " <b>"+this.units+"</b>");
},
});
fieldRegistry.add('show_units', ShowUnitsWidgetField);

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="owl_demo.my_component">
<div t-on-click="increment">
<t t-esc="state.value"/>
</div>
</t>
</templates>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>
</templates>

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="owl_demo.PartnerOrderSummary" owl="1">
<div class="center"
style="width: 100%; text-align: center; border: 1px solid #cecece; padding: 2rem 20%; margin: 12px 0;">
<img
t-attf-src="data:image/jpg;base64,{{partner.image_256}}"
width="75px"
height="75px"
style="background-color: #ccc; border-radius: 50%; margin-bottom: 10px;"/>
<!-- Customer name -->
<p style="font-size: 16px; color: #4d4b4b;"><strong t-esc="partner.name"/></p>
<!-- Address -->
<p style="font-size: 12px; color: #8c8787;">
<i class="fa fa-map-marker" style="padding-right: 4px;"/>
<span t-esc="partner.city"/>
<span t-esc="partner.zip" style="margin-left: 5px;"/>
</p>
<!-- Grid of previous order stats -->
<div class="row" style="padding-top: 20px;">
<div class="col-6" style="border-right: 1px solid #ccc;">
<p style="font-size: 20px;">
<strong t-esc="partner.sale_order_count"/>
</p>
<p style="font-size: 12px; color: #8c8787;">Orders</p>
</div>
<div class="col-6">
<p style="font-size: 20px;">
<strong t-esc="partner.total_invoiced" t-options='{"widget": "monetary"}'/>
</p>
<p style="font-size: 12px; color: #8c8787;">Total Sales</p>
</div>
</div>
</div>
</t>
</templates>

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="sale_order_form_inherit" model="ir.ui.view">
<field name="name">sale.order.form.inherit</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<field name="payment_term_id" position="after">
<!-- <field name="create_date" widget="show_units" options="{'units':'UTC'}" string="Create Date"/>-->
<!-- <group class="o_partner_order_summary" col="2"/>-->
<field name="step_file" widget="Viewer3D" readonly="True"/>
</field>
</field>
</record>
<record id="sale_order_tree_inherit" model="ir.ui.view">
<field name="name">sale.order.tree.inherit</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_tree"/>
<field name="arch" type="xml">
<field name="partner_id" position="attributes">
<attribute name="widget">CTemplate</attribute>
<attribute name="options">{'template':'owl_demo.field_partner_id'}</attribute>
</field>
<field name="team_id" position="before">
<field name="order_line" widget="CTemplate" options="{'template':'owl_demo.field_order_line'}"/>
</field>
</field>
</record>
</odoo>

View File

@@ -7,7 +7,7 @@ from odoo.http import request
class Sf_Bf_Connect(http.Controller):
@http.route('/api/bfm_process_order/list', type='http', auth='none', methods=['GET', 'POST'], csrf=False,
@http.route('/api/bfm_process_order/list', type='http', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
def get_bfm_process_order_list(self, **kw):
"""
@@ -119,6 +119,7 @@ class Sf_Bf_Connect(http.Controller):
logging.info('get_bfm_process_order_list error:%s' % e)
res['status'] = -1
res['message'] = '工厂创建销售订单和产品失败,请联系管理员'
request.cr.rollback()
return json.JSONEncoder().encode(res)

View File

@@ -10,7 +10,7 @@
""",
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['mrp', 'base', 'sf_manufacturing'],
'depends': ['mrp', 'base', 'sf_manufacturing','web_widget_model_viewer'],
'data': [
'data/product_data.xml',
'views/product_template_view.xml',

View File

@@ -7,13 +7,16 @@
<field name="inherit_id" ref="product.product_template_only_form_view"/>
<field name="arch" type="xml">
<!-- <field name="image_1920" position="replace">-->
<!-- <field name='categ_type' invisible="1"/>-->
<!-- <field name="upload_model_file" required="True"-->
<!-- widget='many2many_binary'/>-->
<!-- </field>-->
<field name="invoice_policy" position="after">
<field name="detailed_type" position="before">
<field name='categ_type' invisible="1"/>
<field name="model_file" widget="model_viewer"
<field name="model_file" widget="Viewer3D" string="模型" readonly="1"
attrs="{'invisible': ['|', ('categ_type', '!=', '成品'),('categ_type', '=', False)]}"/>
</field>
<field name="invoice_policy" position="after">
<field name="embryo_model_type_id" string="模型类型"
attrs="{'invisible': ['|',('categ_type', '!=', '胚料'),('categ_type', '=', False)]}"/>
<field name="materials_id" string="材料"/>

View File

@@ -11,7 +11,6 @@ class ReSaleOrder(models.Model):
address_of_delivery = fields.Char('交货人地址')
# 业务平台分配工厂后在智能工厂先创建销售订单
def sale_order_create(self, company_id, delivery_name, delivery_telephone, delivery_address,
deadline_of_delivery):
now_time = datetime.datetime.now()

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,22 @@
{
"name": "3D模型展示",
"summary": "3D模型展示模块",
"description": "3D模型展示模块限odoo16)",
"author": "Van",
"website": "https://jikimo.com",
"category": "Tutorials",
"version": "16.0.0.1",
"depends": ['web'],
"demo": [],
"data": [
#'views/views.xml', #这是为了测试的效果,可以删除
],
'assets': {
'web.assets_qweb': [
'web_widget_model_viewer/static/src/js/3d_viewer.xml',
],
'web.assets_backend': [
'web_widget_model_viewer/static/src/js/*',
],
}
}

View File

@@ -0,0 +1,25 @@
# 演示DEMO
## 先给销售订单添加一个Binary字段
class SaleOrder(models.Model):
_inherit = "sale.order"
step_file = fields.Binary("Step File")
## 然后在销售订单详情的表单视中增加一个带有widget的标签
<record id="sale_order_form_inherit" model="ir.ui.view">
<field name="name">sale.order.form.inherit</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<!-- 以下仅用于演示效果 widget必需放在保存GLB文件内容的字段上 -->
<field name="payment_term_id" position="after">
<field name="step_file" widget="Viewer3D"/>
</field>
</field>
</record>
## 然后就可以到销售订单页面上查看效果
![img.png](static/src/images/img.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

View File

@@ -0,0 +1,62 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { _lt } from "@web/core/l10n/translation";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { useInputField } from "@web/views/fields/input_field_hook";
import { FileUploader } from "@web/views/fields/file_handler";
import { session } from "@web/session";
import { useService } from "@web/core/utils/hooks";
import { isBinarySize } from "@web/core/utils/binary";
import { download } from "@web/core/network/download";
import utils from 'web.utils';
import core from 'web.core';
import rpc from 'web.rpc';
var QWeb = core.qweb;
import { Component, onWillUpdateProps, useState, useRef, useEffect } from "@odoo/owl";
export class StepViewer extends Component {
setup() {
this.props.url = this.formatUrl();
}
formatUrl(){
var url = '';
if (this.props.value) {
if (utils.is_bin_size(this.props.value)) {
var url_props = {
base_url: session['web.base.url'],
model: this.props.record.resModel,
id: JSON.stringify(this.props.record.data['id']),
field: this.props.name}
url = url_props['base_url']+'/web/content/'+url_props['model']+'/'+url_props['id']+'/'+url_props['field']+'?download=true'
} else {
url = "data:model/gltf-binary;base64," + this.props.value;
}
}
return url
}
}
StepViewer.template = "web_widget_model_viewer.BinaryField3d";
StepViewer.displayName = _lt("3D File");
StepViewer.supportedTypes = ["binary"];
StepViewer.props = {
...standardFieldProps,
url: { type: String, optional: true },
};
StepViewer.extractProps = ({ attrs }) => {
return {
url: attrs.options.url,
};
};
registry.category("fields").add("Viewer3D", StepViewer);

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web_widget_model_viewer.BinaryField3d" owl="1">
<model-viewer
t-att-src='props.url'
name="3D model"
alt="3D model"
auto-rotate="1"
camera-controls="1"
style ="background-color: #0D1D54;"
>
<!-- <div class="text-center mt-4 mb-4 mr-4">-->
<!-- <span-->
<!-- id="model-viewer-fullscreen"-->
<!-- title="View fullscreen"-->
<!-- role="img"-->
<!-- aria-label="Fullscreen"-->
<!-- >-->
<!-- <i class="fa fa-arrows-alt fa-2x"/>-->
<!-- </span>-->
<!-- </div>-->
</model-viewer>
<!-- <model-viewer-->
<!-- src='/jikimo_model_viewer/static/src/js/3d_viewer/test.glb'-->
<!-- name="Test 3D model"-->
<!-- alt="Test 3D model"-->
<!-- auto-rotate="1"-->
<!-- camera-controls="1"-->
<!-- />-->
<script type="module"
src="/web_widget_model_viewer/static/src/lib/model-viewer.min.js">
</script>
</t>
</templates>