产品新增3d模型展示
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
from . import models
|
||||
from . import controllers
|
||||
@@ -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',
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
from . import main
|
||||
@@ -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)
|
||||
@@ -1 +0,0 @@
|
||||
from . import sale_order
|
||||
@@ -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")
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
Binary file not shown.
@@ -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');
|
||||
});
|
||||
@@ -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);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
//});
|
||||
|
||||
@@ -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">-->
|
||||
<!--<!– <h3><t t-out="record.data.name"/></h3>–>-->
|
||||
<!-- <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>
|
||||
@@ -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);
|
||||
@@ -1,3 +0,0 @@
|
||||
.o_field_widget.o_field_template {
|
||||
display: block;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates>
|
||||
|
||||
</templates>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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="材料"/>
|
||||
|
||||
@@ -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()
|
||||
|
||||
1
web_widget_model_viewer/__init__.py
Normal file
1
web_widget_model_viewer/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
22
web_widget_model_viewer/__manifest__.py
Normal file
22
web_widget_model_viewer/__manifest__.py
Normal 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/*',
|
||||
],
|
||||
}
|
||||
}
|
||||
25
web_widget_model_viewer/readme.md
Normal file
25
web_widget_model_viewer/readme.md
Normal 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>
|
||||
|
||||
## 然后就可以到销售订单页面上查看效果
|
||||

|
||||
BIN
web_widget_model_viewer/static/src/images/img.png
Normal file
BIN
web_widget_model_viewer/static/src/images/img.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
62
web_widget_model_viewer/static/src/js/3d_viewer.js
Normal file
62
web_widget_model_viewer/static/src/js/3d_viewer.js
Normal 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);
|
||||
41
web_widget_model_viewer/static/src/js/3d_viewer.xml
Normal file
41
web_widget_model_viewer/static/src/js/3d_viewer.xml
Normal 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>
|
||||
Reference in New Issue
Block a user