# Conflicts:
#	sf_manufacturing/models/mrp_workcenter.py
#	sf_manufacturing/models/mrp_workorder.py
#	sf_manufacturing/views/mrp_workorder_view.xml
This commit is contained in:
gqh
2023-01-04 10:35:38 +08:00
42 changed files with 1746 additions and 237 deletions

View File

@@ -77,6 +77,7 @@ class MachineTool(models.Model):
b_axis = fields.Integer('B轴')
c_axis = fields.Integer('C轴')
remark = fields.Text('备注')
is_binding = fields.Boolean('是否绑定机床', default=False)
precision = fields.Float('加工精度')
control_system_id = fields.Many2one('sf.machine.control_system',
string="控制系统")

View File

@@ -8,7 +8,7 @@ from odoo.http import request
class Sf_Bf_Connect(http.Controller):
@http.route('/api/bfm_process_order/list', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
@http.route('/api/bfm_process_order/list', type='http', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def get_bfm_process_order_list(self, **kw):
"""
@@ -19,48 +19,91 @@ class Sf_Bf_Connect(http.Controller):
res = {'status': 1, 'factory_order_no': ''}
logging.info('get_bfm_process_order_list:%s' % kw)
try:
datas = request.httprequest.data
ret = json.loads(datas)
ret = json.loads(ret['result'])
# datas = request.httprequest.data
# ret = json.loads(datas)
# ret = json.loads(ret['result'])
product_id = request.env.ref('sf_dlm.product_template_sf').sudo()
logging.info('product_id:%s' % product_id)
self_machining_id = request.env.ref('sf_dlm.product_embryo_sf_self_machining').sudo()
outsource_id = request.env.ref('sf_dlm.product_embryo_sf_outsource').sudo()
purchase_id = request.env.ref('sf_dlm.product_embryo_sf_purchase').sudo()
company_id = request.env.ref('base.main_company').sudo()
user_id = request.env.ref('base.user_admin').sudo()
logging.info('user_id:%s' % user_id)
bfm_process_order_list = json.loads(kw['bfm_process_order_list'])
order_id = request.env['sale.order'].with_user(request.env.ref("base.user_admin")).sale_order_create(
company_id, ret['delivery_name'], ret['delivery_telephone'], ret['delivery_address'],
ret['delivery_end_date'])
company_id, kw['delivery_name'], kw['delivery_telephone'], kw['delivery_address'],
kw['delivery_end_date'], user_id)
i = 1
for item in ret['bfm_process_order_list']:
for item in bfm_process_order_list:
# product_has = request.env['product.template'].with_user(request.env.ref("base.user_admin")).search([('barcode','=', item['barcode'])])
# if product_has:
# logging.info('product_has:%s' % product_has)
# logging.info('barcode:%s' % item['barcode'])
# order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product_has, item)
# else:
product = request.env['product.template'].sudo().product_create(product_id, item, order_id,
ret['order_number'], i)
order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item)
kw['order_number'], i)
# order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item)
logging.info('order_id:%s' % order_id)
logging.info('product:%s' % product)
bom_data = request.env['mrp.bom'].with_user(request.env.ref("base.user_admin")).get_bom(product)
logging.info('bom_data:%s' % bom_data)
if bom_data:
bom = request.env['mrp.bom'].with_user(request.env.ref("base.user_admin")).bom_create(product,
'normal')
bom.with_user(request.env.ref("base.user_admin")).bom_create_Line(product)
'normal',
False)
bom.with_user(request.env.ref("base.user_admin")).bom_create_line_has(bom_data)
else:
if product.materials_id.gain_way == '自加工':
self_machining = request.env['product.template'].sudo().no_bom_product_create(self_machining_id,
if product.materials_type_id.gain_way == '自加工':
# 创建胚料
self_machining_embryo = request.env['product.template'].sudo().no_bom_product_create(
self_machining_id,
item,
order_i)
bom = request.env['mrp.bom'].with_user(request.env.ref("base.user_admin")).bom_create(
self_machining, 'normal')
bom.with_user(request.env.ref("base.user_admin")).bom_create_Line(self_machining)
elif product.materials_id.gain_way == '外协':
outsource = request.env['product.template'].sudo().no_bom_product_create(outsource_id, item,
order_id)
bom = request.env['mrp.bom'].with_user(request.env.ref("base.user_admin")).bom_create(outsource)
bom.with_user(request.env.ref("base.user_admin")).bom_create_Line(outsource, 'subcontract')
elif product.materials_id.gain_way == '采购':
purchase = request.env['product.template'].sudo().no_bom_product_create(purchase_id, item,
order_id)
order_id, 'self_machining')
# 创建胚料的bom
self_machining_bom = request.env['mrp.bom'].with_user(
request.env.ref("base.user_admin")).bom_create(
self_machining_embryo, 'normal', False)
# 创建胚料里bom的组件
self_machining_bom.with_user(request.env.ref("base.user_admin")).bom_create_line(
self_machining_embryo)
# 产品配置bom
product_bom_self_machining = request.env['mrp.bom'].with_user(
request.env.ref("base.user_admin")).bom_create(
product, 'normal', False)
product_bom_self_machining.with_user(request.env.ref("base.user_admin")).bom_create_line_has(
self_machining_embryo)
elif product.materials_type_id.gain_way == '外协':
# 创建胚料
outsource_embryo = request.env['product.template'].sudo().no_bom_product_create(outsource_id,
item,
order_id,
'subcontract')
# 创建胚料的bom
outsource_bom = request.env['mrp.bom'].with_user(request.env.ref("base.user_admin")).bom_create(
outsource_embryo,
'subcontract', True)
# 创建胚料的bom的组件
outsource_bom.with_user(request.env.ref("base.user_admin")).bom_create_line(outsource_embryo)
# 产品配置bom
product_bom_outsource = request.env['mrp.bom'].with_user(
request.env.ref("base.user_admin")).bom_create(product, 'normal', False)
product_bom_outsource.with_user(request.env.ref("base.user_admin")).bom_create_line_has(
outsource_embryo)
elif product.materials_type_id.gain_way == '采购':
purchase_embryo = request.env['product.template'].sudo().no_bom_product_create(purchase_id,
item,
order_id,
'purchase')
# 产品配置bom
product_bom_purchase = request.env['mrp.bom'].with_user(
request.env.ref("base.user_admin")).bom_create(product, 'normal', False)
product_bom_purchase.with_user(request.env.ref("base.user_admin")).bom_create_line_has(
purchase_embryo)
order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item)
i += 1
res['factory_order_no'] = order_id.name
return json.JSONEncoder().encode(res)
except Exception as e:
logging.info('get_bfm_process_order_list error:%s' % e)
res['status'] = -1

View File

@@ -13,6 +13,10 @@ _logger = logging.getLogger(__name__)
class AuthenticationError(Exception):
pass
class AuthenticationError(Exception):
pass
class Http(models.AbstractModel):
_inherit = 'ir.http'

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', 'purchase', 'mrp_subcontracting'],
'data': [
'data/product_data.xml',
'views/product_template_view.xml'

View File

@@ -1,57 +1,82 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data noupdate="1">
<record id="product_template_sf" model="product.product">
<field name="name">CNC加工产品模板</field>
<!-- <field name="categ_id" ref="product.product_category_5"/>-->
<field name="invoice_policy">delivery</field>
<field name="detailed_type">product</field>
<field name="purchase_ok">false</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="company_id" ref="base.main_company"/>
<field name="active">False</field>
</record>
<record id="product_category_embryo_sf" model="product.category">
<field name="name">胚料</field>
<field name="type">胚料</field>
</record>
<record id="product_category_finished_sf" model="product.category">
<field name="name">成品</field>
<field name="type">成品</field>
</record>
<record id="product_embryo_sf_self_machining" model="product.product">
<field name="name">自加工</field>
<!-- <field name="categ_id" ref="product_category_embryo_sf"/>-->
<record id="product_category_raw_sf" model="product.category">
<field name="name">原材料</field>
<field name="type">原材料</field>
</record>
<record id="product_template_sf" model="product.product">
<field name="name">CNC加工产品模板</field>
<field name="active" eval="False"/>
<field name="categ_id" ref="product_category_finished_sf"/>
<field name="route_ids"
eval="[ref('stock.route_warehouse0_mto'), ref('mrp.route_warehouse0_manufacture')]"/>
<field name="invoice_policy">delivery</field>
<field name="detailed_type">product</field>
<field name="purchase_ok">false</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="company_id" ref="base.main_company"/>
<field name="active">False</field>
<field name="single_manufacturing">true</field>
<field name="tracking">serial</field>
</record>
<record id="product_embryo_sf_self_machining" model="product.product">
<field name="name">胚料自加工模板</field>
<field name="active" eval="False"/>
<field name="categ_id" ref="product_category_embryo_sf"/>
<field name="route_ids"
eval="[ref('stock.route_warehouse0_mto'), ref('mrp.route_warehouse0_manufacture')]"/>
<field name="invoice_policy">delivery</field>
<field name="detailed_type">product</field>
<field name="purchase_ok">false</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="company_id" ref="base.main_company"/>
<field name="single_manufacturing">true</field>
<field name="tracking">serial</field>
<!-- <field name="active" eval="False"/>-->
</record>
<record id="product_embryo_sf_outsource" model="product.product">
<field name="name">外协</field>
<!-- <field name="categ_id" ref="product_category_embryo_sf"/>-->
<field name="name">胚料外协加工模板</field>
<field name="active" eval="False"/>
<field name="categ_id" ref="product_category_embryo_sf"/>
<field name="route_ids"
eval="[ref('stock.route_warehouse0_mto'), ref('purchase_stock.route_warehouse0_buy'),ref('mrp_subcontracting.route_resupply_subcontractor_mto')]"/>
<field name="invoice_policy">delivery</field>
<field name="detailed_type">product</field>
<field name="purchase_ok">false</field>
<field name="purchase_ok">true</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="company_id" ref="base.main_company"/>
<field name="active">False</field>
<field name="tracking">serial</field>
<!-- <field name="active" eval="False"/>-->
</record>
<record id="product_embryo_sf_purchase" model="product.product">
<field name="name">采购</field>
<!-- <field name="categ_id" ref="product_category_embryo_sf"/>-->
<field name="name">胚料采购模板</field>
<field name="active" eval="False"/>
<field name="categ_id" ref="product_category_embryo_sf"/>
<field name="route_ids"
eval="[ref('stock.route_warehouse0_mto'), ref('purchase_stock.route_warehouse0_buy')]"/>
<field name="invoice_policy">delivery</field>
<field name="detailed_type">product</field>
<field name="purchase_ok">false</field>
<field name="purchase_ok">true</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="company_id" ref="base.main_company"/>
<field name="active">False</field>
<field name="tracking">serial</field>
<!-- <field name="active" eval="False"/>-->
</record>
</data>
</odoo>

View File

@@ -1,12 +1,16 @@
from odoo import models, fields, api
from odoo.exceptions import ValidationError
import logging
import base64
import os
from OCC.Extend.DataExchange import read_step_file, write_stl_file
from odoo.modules import get_resource_path
class ResProductTemplate(models.Model):
_inherit = 'product.template'
# 模型的长,宽,高,体积,精度,材料
model_name = fields.Char('模型名称')
model_long = fields.Float('模型长[mm]', digits=(16, 3))
model_width = fields.Float('模型宽[mm]', digits=(16, 3))
model_height = fields.Float('模型高[mm]', digits=(16, 3))
@@ -21,163 +25,204 @@ class ResProductTemplate(models.Model):
model_processing_panel = fields.Char('模型加工面板')
model_surface_process_id = fields.Many2one('sf.production.process', string='表面工艺')
model_process_parameters_id = fields.Many2one('sf.processing.technology', string='工艺参数')
model_price = fields.Float('模型单价', digits=(16, 3))
# model_price = fields.Float('模型单价', digits=(16, 3))
model_remark = fields.Char('模型备注说明')
length = fields.Float('长[mm]', digits=(16, 3))
width = fields.Float('宽[mm]', digits=(16, 3))
height = fields.Float('高[mm]', digits=(16, 3))
materials_id = fields.Many2one('sf.production.materials', string='材料')
materials_type_id = fields.Many2one('sf.materials.model', string='材料型号')
# volume = fields.Float(compute='_compute_volume', store=True)
single_manufacturing = fields.Boolean(string="单个制造")
upload_model_file = fields.Many2many('ir.attachment', 'upload_model_file_attachment_ref', string='上传模型文件')
model_file = fields.Binary('模型文件')
# @api.depends('long', 'width', 'height')
# def _compute_volume(self):
# self.volume = self.long * self.width * self.height
# 胚料的库存路线设置
# def _get_routes(self, route_type):
# route_manufacture = self.env.ref('mrp.route_warehouse0_manufacture', raise_if_not_found=False).sudo()
# route_mto = self.env.ref('stock.route_warehouse0_mto', raise_if_not_found=False).sudo()
# route_purchase = self.env.ref('purchase_stock.route_warehouse0_buy', raise_if_not_found=False).sudo()
# if route_manufacture and route_mto:
# # 外协
# if route_type == 'subcontract':
# route_subcontract = self.env.ref('mrp_subcontracting.route_resupply_subcontractor_mto',
# raise_if_not_found=False).sudo()
# return [route_mto.id, route_purchase.id, route_subcontract.id]
# elif route_type == 'purchase':
# # 采购
# return [route_mto.id, route_purchase.id]
# else:
# return [route_mto.id, route_manufacture.id]
# return []
single_manufacturing = fields.Boolean(string="单个制造")
@api.model
def _get_route(self):
route_manufacture = self.env.ref('stock.warehouse0', raise_if_not_found=False).manufacture_pull_id.route_id.id
route_mto = self.env.ref('stock.warehouse0', raise_if_not_found=False).mto_pull_id.route_id.id
if route_manufacture and route_mto:
return [route_manufacture, route_mto]
return []
route_ids = fields.Many2many(default=lambda self: self._get_route())
# @api.depends('long', 'width', 'height')
# def _compute_volume(self):
# self.volume = self.long * self.width * self.height
# @api.depends('model_long', 'model_width', 'model_height')
# def _compute_model_volume(self):
# self.model_volume = self.model_long * self.model_width * self.model_height
# route_ids = fields.Many2many(default=lambda self: self._get_route())
# 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品
def product_create(self, product_id, item, order_id, order_number, i):
copy_product_id = product_id.with_user(self.env.ref("base.user_admin")).copy()
copy_product_id.product_tmpl_id.active = True
logging.info('product_create:%s' % item)
model_type = self.env['sf.model.type'].search([], limit=1)
attachment = self.attachment_create(item['model_name'], item['model_data'])
vals = {
'name': '%s-%s' % (order_id.name, i),
'model_long': item['model_long'],
'model_width': item['model_width'],
'model_height': item['model_height'],
'length': item['long'],
'width': item['width'],
'height': item['height'],
'volume': item['long'] * item['width'] * item['height'],
'model_price': item['price'],
'model_long': item['model_long'] + model_type.embryo_tolerance,
'model_width': item['model_width'] + model_type.embryo_tolerance,
'model_height': item['model_height'] + model_type.embryo_tolerance,
'model_volume': (item['model_long'] + model_type.embryo_tolerance) * (
item['model_width'] + model_type.embryo_tolerance) * (
item['model_height'] + model_type.embryo_tolerance),
'model_type_id': 1,
# 'model_machining_precision': item['model_machining_precision'],
'model_processing_panel': 'A',
'model_machining_precision': '±0.10mm',
'length': item['model_long'],
'width': item['model_width'],
'height': item['model_height'],
'volume': item['model_long'] * item['model_width'] * item['model_height'],
'model_price': item['price'],
'tracking': 'serial',
'single_manufacturing': True,
'model_file': base64.b64decode(item['model_file']),
'model_name': attachment.name,
'upload_model_file': [(6, 0, [attachment.id])],
# 'single_manufacturing': True,
'list_price': item['price'],
# 'categ_id': self.env.ref('sf_dlm.product_category_finished_sf').id,
'materials_id': self.env['sf.production.materials'].search(
[('materials_no', '=', item['texture_code'])]).id,
'materials_type_id': self.env['sf.materials.model'].search(
[('materials_no', '=', item['texture_type_code'])]).id,
# 'model_surface_process_id': self.env['sf.production.process'].search(
# [('process_encode', '=', item['surface_process_code'])]).id,
'model_surface_process_id': self.env['sf.production.process'].search(
[('process_encode', '=', item['surface_process_code'])]).id,
# 'model_process_parameters_id': self.env['sf.processing.technology'].search(
# [('process_encode', '=', item['process_parameters_code'])]).id,
'model_remark': item['remark'],
# 'default_code': '%s-%s' % (order_number, i),
'default_code': '%s-%s' % (order_number, i),
#'barcode': item['barcode'],
'active': True
'active': True,
# 'route_ids': self._get_routes('')
}
logging.info('product_create1:%s' % item)
copy_product_id.sudo().write(vals)
print(len(copy_product_id.model_file))
product_id.product_tmpl_id.active = False
return copy_product_id
def no_bom_product_create(self, product_id, item, order_id):
copy_product_id = product_id.with_user(self.env.ref("base.user_admin")).copy()
copy_product_id.product_tmpl_id.active = True
logging.info('no_bom_product_create:%s' % item)
def attachment_create(self, name, data):
attachment = self.env['ir.attachment'].create({
'datas': base64.b64decode(data),
'type': 'binary',
'description': '模型文件',
'name': name
})
return attachment
# 创建胚料
def no_bom_product_create(self, product_id, item, order_id, route_type):
no_bom_copy_product_id = product_id.with_user(self.env.ref("base.user_admin")).copy()
no_bom_copy_product_id.product_tmpl_id.active = True
materials_id = self.env['sf.production.materials'].search(
[('materials_no', '=', item['texture_code'])]).id
[('materials_no', '=', item['texture_code'])])
materials_type_id = self.env['sf.materials.model'].search(
[('materials_no', '=', item['texture_type_code'])]).id
[('materials_no', '=', item['texture_type_code'])])
model_type = self.env['sf.model.type'].search([], limit=1)
supplier = self.env['mrp.bom'].get_supplier(materials_type_id)
logging.info('no_bom_copy_product_supplier-vals:%s' % supplier)
vals = {
'name': '%s %s %s %s * %s * %s' % (
order_id.name, materials_id.name, materials_type_id.name, item['model_long'], item['model_width'],
item['model_height']),
'model_long': item['model_long'],
'model_width': item['model_width'],
'model_height': item['model_height'],
'model_volume': item['model_long'] * item['model_width'] * item['model_height'],
'length': item['model_long'],
'width': item['model_width'],
'height': item['model_height'],
'volume': item['model_long'] * item['model_width'] * item['model_height'],
'model_price': item['price'],
'tracking': 'serial',
'single_manufacturing': True,
'length': item['model_long'] + model_type.embryo_tolerance,
'width': item['model_width'] + model_type.embryo_tolerance,
'height': item['model_height'] + model_type.embryo_tolerance,
'volume': (item['model_long'] + model_type.embryo_tolerance) * (
item['model_width'] + model_type.embryo_tolerance) * (
item['model_height'] + model_type.embryo_tolerance),
# 'model_price': item['price'],
'list_price': item['price'],
'materials_id': materials_id.id,
'materials_type_id': materials_type_id.id,
# 'route_ids': self._get_routes(route_type),
# 'categ_id': self.env.ref('sf_dlm.product_category_embryo_sf').id,
# 'model_surface_process_id': self.env['sf.production.process'].search(
# [('process_encode', '=', item['surface_process_code'])]).id,
# 'model_process_parameters_id': self.env['sf.processing.technology'].search(
# [('process_encode', '=', item['process_parameters_code'])]).id,
'active': True
}
logging.info('product_create1:%s' % item)
copy_product_id.sudo().write(vals)
return copy_product_id
# 外协和采购生成的胚料需要根据材料型号绑定供应商
if route_type == 'subcontract':
no_bom_copy_product_id.purchase_ok = True
no_bom_copy_product_id.seller_ids = [
(0, 0, {'partner_id': supplier.partner_id.id, 'delay': 1.0, 'is_subcontractor': True})]
logging.info('no_bom_copy_product_id-seller_ids-vals:%s' % no_bom_copy_product_id.seller_ids)
elif route_type == 'purchase':
no_bom_copy_product_id.purchase_ok = True
no_bom_copy_product_id.seller_ids = [
(0, 0, {'partner_id': supplier.partner_id.id, 'delay': 1.0})]
logging.info('no_bom_copy_product_id-seller_ids-vals:%s' % no_bom_copy_product_id.seller_ids)
no_bom_copy_product_id.write(vals)
logging.info('no_bom_copy_product_id-vals:%s' % vals)
product_id.product_tmpl_id.active = False
return no_bom_copy_product_id
# 根据模型类型默认给模型的长高宽加配置的长度;
@api.onchange('model_type_id')
def add_product_size(self):
if not self.model_type_id:
return
model_type = self.env['sf.model.type'].search(
[('id', '=', self.model_type_id.id), ('embryo_tolerance', '=', True)])
if model_type:
self.model_long = self.model_long + 1
self.model_width = self.model_width + 1
self.model_height = self.model_width + 1
# for item in self:
# print(item.model_long)
# print(item.model_width)
# print(item.model_height)
# item.model_long = item.model_long + 1
# item.model_width = item.model_width + 1
# item.model_height = item.model_width + 1
else:
return
@api.onchange('upload_model_file')
def onchange_model_file(self):
for item in self:
if len(item.upload_model_file) > 1:
raise ValidationError('只允许上传一个文件')
if item.upload_model_file:
file_attachment_id = item.upload_model_file[0]
item.model_name = file_attachment_id.name
# 附件路径
report_path = file_attachment_id._full_path(file_attachment_id.store_fname)
shapes = read_step_file(report_path)
output_file = get_resource_path('sf_dlm', 'static/file', 'out.stl')
write_stl_file(shapes, output_file, 'binary', 0.03, 0.5)
# 转化为glb
output_glb_file = get_resource_path('sf_dlm', 'static/file', 'out.glb')
util_path = get_resource_path('sf_dlm', 'static/util')
cmd = 'python %s/stl2gltf.py %s %s -b' % (util_path, output_file, output_glb_file)
os.system(cmd)
# 转base64
with open(output_glb_file, 'rb') as fileObj:
image_data = fileObj.read()
base64_data = base64.b64encode(image_data)
item.model_file = base64_data
class ResMrpBom(models.Model):
_inherit = 'mrp.bom'
subcontractor_id = fields.Many2one('res.partner', string='外包商')
def bom_create_line_has(self, embryo):
vals = {
'bom_id': self.id,
'product_id': embryo.id,
'product_tmpl_id': embryo.product_tmpl_id.id,
'product_qty': 1,
'product_uom_id': 1
}
logging.info('bom_create_line_has-vals:%s' % vals)
return self.env['mrp.bom.line'].create(vals)
# 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品后再次进行创建bom
def bom_create(self, product, bom_type):
def bom_create(self, product, bom_type, product_type):
bom_id = self.env['mrp.bom'].create({
'product_tmpl_id': product.product_tmpl_id.id,
'type': bom_type,
# 'subcontractor_id': '' or subcontract.partner_id.id,
'product_qty': 1,
'product_uom_id': 1
})
if bom_type == 'subcontract' and product_type != False:
subcontract = self.get_supplier(product.materials_type_id)
bom_id.subcontractor_id = subcontract.partner_id.id
logging.info('bom_create-vals:%s' % bom_id)
return bom_id
# 生成产品BOM匹配胚料胚料的匹配规则
# 一、匹配的胚料类别需要带有胚料的标签;
# 二、胚料的材料型号与生成产品的材料型号一致;
# 三、胚料的长宽高均要大于模型的长宽高;
# 四、如果匹配成功多个胚料,则选取体积最小的胚料;
# 创建新的胚料,根据胚料材料型号的获取方式(
# 自加工,外协,采购) 的配置, 选择不同的库存路线,一种材料型号配置一个路线相关的配置:
# 材料型号配置不同的获取方式: (自加工, 外协, 采购);
# 原材料重量KG公斤= 胚料的体积立方米m³ * 材料密度 * 1000
def bom_create_Line(self, embryo, materials):
bom_line = self.get_raw_bom(embryo, materials)
# 胚料BOM组件选取当前胚料原材料
# 然后根据当前的胚料的体积得出需要的原材料重量立方米m³ *材料密度 * 1000 = 所需原材料重量KG公斤
# 胚料所需原材料公式当前的胚料的体积立方米m³ *材料密度 * 1000 = 所需原材料重量KG公斤
def bom_create_line(self, embryo):
# 选取当前胚料原材料
bom_line = self.get_raw_bom(embryo)
vals = {
'bom_id': self.id,
'product_id': bom_line.id,
@@ -185,10 +230,23 @@ class ResMrpBom(models.Model):
'product_qty': bom_line.volume * bom_line.materials_type_id.density * 1000,
'product_uom_id': bom_line.uom_id.id
}
logging.info('bom_create_line-vals1:%s' % vals)
return self.env['mrp.bom.line'].create(vals)
# 查询材料型号默认排第一的供应商
def get_supplier(self, materials_type):
seller_id = self.env['sf.supplier.sort'].search(
[('materials_model_id', '=', materials_type.id)],
limit=1,
order='sequence asc')
logging.info('get_supplier-vals:%s' % seller_id)
return seller_id
# 匹配bom
def get_bom(self, product):
embryo = self.env['product.product'].search(
logging.info('get_bom-product:%s' % product)
logging.info('get_bom-product:%s' % product.materials_type_id.id)
embryo_has = self.env['product.product'].search(
[('categ_id.type', '=', '胚料'), ('materials_type_id', '=', product.materials_type_id.id),
('length', '>', product.length), ('width', '>', product.width),
('height', '>', product.height)
@@ -196,10 +254,11 @@ class ResMrpBom(models.Model):
limit=1,
order='volume desc'
)
if embryo:
rate_of_waste = ((embryo.volume - product.model_volume) % embryo.volume) * 100
if rate_of_waste <= 20:
return embryo
logging.info('get_bom-vals:%s' % embryo_has)
if embryo_has:
rate_of_waste = ((embryo_has.volume - product.model_volume) % embryo_has.volume) * 100
if rate_of_waste >= 20:
return embryo_has
else:
return
@@ -223,5 +282,3 @@ class ResProductCategory(models.Model):
# [('type', '=', self.type)])
# if category:
# raise ValidationError("该类别已存在,请选择其他类别")

BIN
sf_dlm/static/file/out.glb Normal file

Binary file not shown.

BIN
sf_dlm/static/file/out.stl Normal file

Binary file not shown.

View File

@@ -0,0 +1,277 @@
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

@@ -6,7 +6,13 @@
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_only_form_view"/>
<field name="arch" type="xml">
<!-- <field name="image_1920" position="replace">-->
<!-- <field name="upload_model_file" required="True"-->
<!-- widget='many2many_binary'/>-->
<!-- </field>-->
<field name="invoice_policy" position="after">
<field name="model_file" required="True" widget="model_viewer"/>
<field name="materials_id" string="材料"/>
<field name="materials_type_id" string="型号"
domain="[('materials_id', '=', materials_id)]"/>
@@ -48,16 +54,16 @@
</field>
</record>
<!-- <record id="view_product_category_form_inherit_sf" model="ir.ui.view">-->
<!-- <field name="name">product.category.form.inherit.sf</field>-->
<!-- <field name="model">product.category</field>-->
<!-- <field name="inherit_id" ref="product.product_category_form_view"/>-->
<!-- <field name="arch" type="xml">-->
<!-- <field name="parent_id" position="before">-->
<!-- <field name="type"/>-->
<!-- </field>-->
<!-- </field>-->
<!-- </record>-->
<record id="view_product_category_form_inherit_sf" model="ir.ui.view">
<field name="name">product.category.form.inherit.sf</field>
<field name="model">product.category</field>
<field name="inherit_id" ref="product.product_category_form_view"/>
<field name="arch" type="xml">
<field name="parent_id" position="before">
<field name="type"/>
</field>
</field>
</record>
<record id="view_template_property_form" model="ir.ui.view">
<field name="name">product.template.stock.property.form.inherit</field>
@@ -94,5 +100,17 @@
</page>
</field>
</record>
<record id="view_mrp_bom_form_inherit_sf" model="ir.ui.view">
<field name="name">mrp.bom.form.inherit.sf</field>
<field name="model">mrp.bom</field>
<field name="inherit_id" ref="mrp.mrp_bom_form_view"/>
<field name="arch" type="xml">
<field name="subcontractor_ids" position="replace">
<field name="subcontractor_id"
attrs="{'invisible': [('type', '!=', 'subcontract')], 'required': [('type', '=', 'subcontract')]}"/>
</field>
</field>
</record>
</data>
</odoo>

View File

@@ -10,5 +10,3 @@ from . import res_user

View File

@@ -6,7 +6,7 @@ class ModelType(models.Model):
_description = '模型类型'
name = fields.Char('名称')
embryo_tolerance = fields.Boolean('胚料的容余量', default=False)
embryo_tolerance = fields.Integer('胚料的容余量')
routing_tmpl_ids = fields.One2many('sf.model.type.routing.sort', 'model_type_id', '工序模板')

View File

@@ -2,7 +2,6 @@
from odoo import api, fields, models, _
class MrpProduction(models.Model):
_inherit = 'mrp.production'
_description = "制造订单"
@@ -10,6 +9,8 @@ class MrpProduction(models.Model):
tray_ids = fields.One2many('sf.tray', 'production_id', string="托盘")
maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Number of maintenance requests")
request_ids = fields.One2many('maintenance.request', 'production_id')
# model_file = fields.Binary('模型文件', related='product_id.model_file')
# model_information = fields.Char('模型信息', related='product_id.model_information')
@api.depends('request_ids')
def _compute_maintenance_count(self):
@@ -130,6 +131,9 @@ class MrpProduction(models.Model):
'location_src_id': production.location_src_id.id,
'location_dest_id': production.location_dest_id.id,
'bom_id': production.bom_id.id,
'model_file': base64.b64decode(production.product_id.model_file),
'model_information': '%s/%s' % (
production.product_id.model_processing_panel, production.product_id.materials_id.name),
'date_deadline': production.date_deadline,
'date_planned_start': production.date_planned_start,
'date_planned_finished': production.date_planned_finished,

View File

@@ -8,6 +8,11 @@ class ResWorkcenter(models.Model):
machine_tool_id = fields.Many2one('sf.machine_tool', '机床')
users_ids = fields.Many2many("res.users", 'users_workcenter')
@api.onchange('machine_tool_id')
def get_machine_tool_is_binding(self):
print('1111111')
for item in self:
item.machine_tool_id.is_binding = False
equipment_ids = fields.One2many(
'maintenance.equipment', 'workcenter_id', string="Maintenance Equipment",

View File

@@ -45,9 +45,11 @@ class ResMrpWorkOrder(models.Model):
user_permissions = fields.Boolean('用户权限', compute='get_user_permissions')
programming_no = fields.Char('编程单号')
is_programming = fields.Boolean('是否编程', default=False)
cnc_worksheet = fields.Binary(
'工作指令', readonly=True)
material_center_point = fields.Char(string='料中心点')
material_center_point = fields.Char(string='料中心点')
X1_axis = fields.Float(default=0)
Y1_axis = fields.Float(default=0)
Z1_axis = fields.Float(default=0)
@@ -120,7 +122,11 @@ class ResMrpWorkOrder(models.Model):
print("(%.2f,%.2f)" % (x, y))
self.material_center_point = ("(%.2f,%.2f,%.2f)" % (x, y, z))
self.X_deviation_angle = jdz
return self.material_center_point
# 将补偿值写入CNC加工工单
workorder = self.env['mrp.workorder'].browse(self.ids)
work = workorder.production_id.workorder_ids
work.compensation_value_x = eval(self.material_center_point)[0]
work.compensation_value_y = eval(self.material_center_point)[1]
def json_workorder_str(self, k, production, route):
workorders_values_str = [0, '', {
@@ -261,20 +267,21 @@ class ResMrpWorkOrder(models.Model):
'embryo_long': cnc.product_id.bom_ids.bom_line_ids.product_id.length,
'embryo_height': cnc.product_id.bom_ids.bom_line_ids.product_id.height,
'embryo_width': cnc.product_id.bom_ids.bom_line_ids.product_id.width,
'order_no': cnc.production_id.origin
# 'factory_code': self.env.user.company_id.partner_id.
'order_no': cnc.production_id.origin,
'user': self.env.user.name,
'model_file': base64.b64encode(cnc.product_id.model_file).decode('utf-8')
}
logging.info('res:%s' % res)
configsettings = self.env['res.config.settings'].get_values()
config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key'])
url = '/api/intelligent_programming/create'
config_url = configsettings['sf_url'] + url
res_str = json.dumps(res)
ret = requests.post(config_url, json={"result": res_str}, data=None, headers=config_header)
# res_str = json.dumps(res)
ret = requests.post(config_url, json={}, data=res, headers=config_header)
ret = ret.json()
result = json.loads(ret['result'])
if result['status'] == 1:
return self.write({'state': 'progress'})
return self.write({'programming_no': result['programming_no'], 'is_programming': True})
def json_workorder_str1(self, k, production, route):
workorders_values_str = [0, '', {
@@ -296,6 +303,7 @@ class ResMrpWorkOrder(models.Model):
def button_start(self):
if self.state == 'waiting' or self.state == 'ready':
self.ensure_one()
if any(not time.date_end for time in self.time_ids.filtered(lambda t: t.user_id.id == self.env.user.id)):
return True
# As button_start is automatically called in the new view
@@ -357,13 +365,13 @@ class CNCprocessing(models.Model):
depth_of_processing_z = fields.Char('加工深度(Z)')
cutting_tool_extension_length = fields.Char('刀具伸出长度')
cutting_tool_handle_type = fields.Char('刀柄型号')
estimated_processing_time = fields.Datetime('预计加工时间')
estimated_processing_time = fields.Char('预计加工时间')
remark = fields.Text('备注')
workorder_id = fields.Many2one('mrp.workorder', string="工单")
# mrs下发编程单创建CNC加工
def cnc_processing_create(self, obj):
workorder = self.env['mrp.workorder'].search([('production_id.name', '=', obj['manufacturing_order_no']),
workorder = self.env['mrp.workorder'].search([('production_id.name', '=', obj['production_order_no']),
('processing_panel', '=', obj['processing_panel']),
('routing_type', '=', 'CNC加工')])
vals = {
@@ -406,26 +414,12 @@ class CNCprocessing(models.Model):
if os.path.exists(nc_file_path):
with open(nc_file_path, 'rb') as file:
data_bytes = file.read()
attachment = self.attachment_create(cnc.program_name + '.NC', data_bytes)
attachment = self.attachment_create(cnc.program_name, data_bytes)
cnc.write({'cnc_id': attachment.id})
file.close()
else:
return False
# 将nc文件对应的excel清单转为pdf
# def to_pdf(self, excel_path, pdf_path):
# """
# 需要在linux中下载好libreoffice
# """
# logging.info('pdf_path:%s' % pdf_path)
# logging.info('pdf_path:%s' % excel_path)
# # 注意cmd中的libreoffice要和linux中安装的一致
# cmd = 'soffice --headless --convert-to pdf'.split() + [excel_path] + ['--outdir'] + [pdf_path]
# p = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=1)
# # p.wait(timeout=30) # 停顿30秒等待转化
# # stdout, stderr = p.communicate()
# p.communicate()
class SfWorkOrderBarcodes(models.Model):
"""

View File

@@ -74,13 +74,17 @@ class StockRule(models.Model):
Procurement = namedtuple('Procurement', ['product_id', 'product_qty',
'product_uom', 'location_id', 'name', 'origin',
'company_id',
'values'])
'values', 'model_file',
'model_information'])
s = Procurement(product_id=item[0].product_id, product_qty=1.0, product_uom=item[0].product_uom,
location_id=item[0].location_id,
name=item[0].name,
origin=item[0].origin,
company_id=item[0].company_id,
values=item[0].values,
model_file=base64.b64decode(item[0].product_id.model_file),
model_information='%s/%s' % (item[0].product_id.model_processing_panel,
item[0].product_id.materials_id.name),
)
item1 = list(item)
item1[0] = s

View File

@@ -3,7 +3,7 @@
import base64
from io import BytesIO
from odoo import api, fields, models, SUPERUSER_ID, _
from pystrich.code128 import Code128Encoder
# from pystrich.code128 import Code128Encoder
class Tray(models.Model):

View File

@@ -31,7 +31,7 @@
<form string="模型类型">
<group>
<field name="name" required="1"/>
<field name="embryo_tolerance" required="1"/>
<field name="embryo_tolerance" string="胚料容余(mm)"/>
</group>
<group>
<field name='routing_tmpl_ids'>

View File

@@ -107,7 +107,7 @@
</xpath>
<xpath expr="//field[@name='alternative_workcenter_ids']" position="after">
<field name="machine_tool_id"/>
<field name="machine_tool_id" domain="[('is_binding', '=', False)]"/>
</xpath>
</field>
</record>

View File

@@ -32,21 +32,32 @@ class Sf_Mrs_Connect(http.Controller):
logging.info('model_code:%s' % model_code)
server_dir = cnc.with_user(request.env.ref("base.user_admin")).download_file_tmp(model_code,
processing_panel)
cnc_file_path = os.path.join(server_dir, cnc.program_name + '.NC')
# cnc_file_path = os.path.join('/', server_dir, cnc.program_name + '.nc')
# logging.info('cnc_file_path:%s' % cnc_file_path)
# cnc.with_user(request.env.ref("base.user_admin")).write_file(cnc_file_path, cnc)
logging.info('server_dir:%s' % server_dir)
for root, dirs, files in os.walk(server_dir):
for f in files:
logging.info('f:%s' % f)
logging.info('f[0]:%s' % f.split('.')[0])
if os.path.splitext(f)[1] == ".pdf":
full_path = os.path.join(server_dir, root, f)
logging.info('pdf:%s' % full_path)
if full_path != False:
if not cnc.workorder_id.cnc_worksheet:
cnc.workorder_id.cnc_worksheet = base64.b64encode(open(full_path, 'rb').read())
else:
logging.info('break:%s' % 'break')
continue
else:
logging.info('cnc.program_name:%s' % cnc.program_name)
if cnc.program_name == f.split('.')[0]:
logging.info('f[0]:%s' % f[0])
cnc_file_path = os.path.join(server_dir, root, f)
logging.info('cnc_file_path:%s' % cnc_file_path)
cnc.with_user(request.env.ref("base.user_admin")).write_file(cnc_file_path, cnc)
# logging.info('get_cnc_processing_create:%s' % '111111111111111')
# for root, dirs, files in os.walk(server_dir):
# for file in files:
# if os.path.splitext(file)[1] == '.xlsx' or os.path.splitext(file)[1] == ".xls":
# pdf_path = os.path.splitext(file)[1] + '.PDF'
# cnc_pdf_path = request.env['sf.cnc.processing'].with_user(
# request.env.ref("base.user_admin")).to_pdf(os.path.join(root, file), pdf_path)
# if pdf_path != False:
# if not cnc.workorder_id.cnc_worksheet:
# cnc.workorder_id.cnc_worksheet = base64.b64encode(open(cnc_pdf_path, 'rb').read())
# else:
# logging.info('break:%s' % 'break')
# break
else:
continue
except Exception as e:
logging.info('get_cnc_processing_create error:%s' % e)

View File

@@ -25,14 +25,15 @@ class FtpController():
# 下载目录下的文件
def download_file_tree(self, target_dir, serverdir):
self.ftp.cwd(target_dir) # 切换工作路径
if not os.path.exists(serverdir):
os.makedirs(serverdir)
self.ftp.cwd(target_dir) # 切换工作路径
remotenames = self.ftp.nlst()
for file in remotenames:
server = os.path.join(serverdir, file)
if file.find(".") != -1:
self.download_file(server, file)
else:
return
# 下载指定目录下的指定文件

View File

@@ -15,20 +15,23 @@ class ReSaleOrder(models.Model):
# 业务平台分配工厂后在智能工厂先创建销售订单
def sale_order_create(self, company_id, delivery_name, delivery_telephone, delivery_address,
deadline_of_delivery):
deadline_of_delivery,user_id):
now_time = datetime.datetime.now()
partner = self.env.user.partner_id
logging.info('partner:%s' % partner)
order_id = self.env['sale.order'].sudo().create({
'company_id': company_id.id,
'date_order': now_time,
'name': self.env['ir.sequence'].next_by_code('sale.order', sequence_date=now_time),
'partner_id': 1,
'state': 'sale',
'user_id': 1,
'partner_id': partner.id,
'state': 'draft',
'user_id': user_id.id,
'person_of_delivery': delivery_name,
'telephone_of_delivery': delivery_telephone,
'address_of_delivery': delivery_address,
'deadline_of_delivery': deadline_of_delivery
})
logging.info('sale_order_create:%s' % order_id)
return order_id
# 业务平台分配工厂时在创建完产品后再创建销售明细信息
@@ -36,10 +39,12 @@ class ReSaleOrder(models.Model):
vals = {
'order_id': self.id,
'product_id': product.id,
'name': '%s/%s/%s/%s/%s' % (
item['model_long'], item['model_width'], item['model_height'], item['model_volume'],
'name': '%s/%s/%s/%s/%s/%s' % (
product.model_long, product.model_width, product.model_height, product.model_volume,
product.model_machining_precision,
product.materials_id.name),
'price_unit': item['price'],
'price_unit': product.list_price,
'route_id': product.route_ids,
'product_uom_qty': item['number']
}
return self.env['sale.order.line'].create(vals)

View File

@@ -0,0 +1,126 @@
===================
Model viewer widget
===================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
:target: https://github.com/OCA/web/tree/14.0/web_widget_model_viewer
:alt: OCA/web
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-14-0/web-14-0-web_widget_model_viewer
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/162/14.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
``<model-viewer>`` is a web component that makes rendering interactive 3D models - optionally in AR - easy to do, on as many browsers and devices as possible. ``<model-viewer>`` strives to give you great defaults for rendering quality and performance.
See `source repository <https://github.com/google/model-viewer>`_ and `documentation <https://modelviewer.dev/>`_.
The model to load is a GLTF 2.0 file format.
See `<https://www.khronos.org/gltf/>`_ and GLTF overview:
.. figure:: https://raw.githubusercontent.com/OCA/web/14.0/web_widget_model_viewer/static/img/gltfOverview.png
Many engine developers have already started transitioning to glTF 2.0 to reap performance, portability and quality benefits, including BabylonJS, three.js, Cesium, Sketchfab, and xeogl and instant3Dhub engines. glTF 2.0 is also seeing industry support by companies such as Adobe, Google, Marmoset, Microsoft, NVIDIA, Oculus, UX3D, and more as well as prominent universities such as, University of Pennsylvania and Sapienza University of Rome.
"example" directory contains the GLB file of a chair, that is rendered in the following way:
.. figure:: https://raw.githubusercontent.com/OCA/web/14.0/web_widget_model_viewer/static/img/Eames_Lounge_Chair.gif
**Table of contents**
.. contents::
:local:
Usage
=====
Add ``widget="model_viewer"`` to your binary field in form view. Optionally you can set ``style`` and ``max_upload_size`` (in MB) attributes.
Changelog
=========
14.0.1.0.0 (2021-10-07)
~~~~~~~~~~~~~~~~~~~~~~~
* [MIG] v14
12.0.2.0.0 (2020-07-14)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] fullscreen and view redesign
12.0.1.0.0 (2020-07-10)
~~~~~~~~~~~~~~~~~~~~~~~
* Start of the history.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_widget_model_viewer%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* TAKOBI
* Openindustry.it
Contributors
~~~~~~~~~~~~
* Lorenzo Battistini (https://takobi.online)
* Andrea Piovesana (https://openindustry.it)
* Marco Colombo (https://phi.technology)
Other credits
~~~~~~~~~~~~~
Chair © Copyright 2020 Shopify Inc., licensed under CC-BY-4.0.
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
.. |maintainer-eLBati| image:: https://github.com/eLBati.png?size=40px
:target: https://github.com/eLBati
:alt: eLBati
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-eLBati|
This module is part of the `OCA/web <https://github.com/OCA/web/tree/14.0/web_widget_model_viewer>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

View File

@@ -0,0 +1,33 @@
# Copyright 2020 Andrea Piovesana @ Openindustry.it
# Copyright 2020 Lorenzo Battistini @ TAKOBI
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Model viewer widget",
"summary": "Easily display interactive 3D models on the web & in AR",
"version": "14.0.1.0.0",
"development_status": "Beta",
"category": "Web",
"website": "https://github.com/OCA/web",
"author": "TAKOBI, Openindustry.it, Odoo Community Association (OCA)",
"maintainers": ["eLBati"],
"license": "AGPL-3",
"depends": [
"web",
],
'assets': {
'web.assets_qweb': [
"/web_widget_model_viewer/static/src/xml/*.xml",
],
'web.assets_backend': [
'/web_widget_model_viewer/static/src/js/web_widget_model_viewer.js',
],
},
"data": [
"views/assets.xml",
],
"qweb": [
],
"application": False,
"installable": True,
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

View File

@@ -0,0 +1,67 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_widget_model_viewer
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2021-02-17 13:45+0000\n"
"Last-Translator: claudiagn <claudia.gargallo@qubiq.es>\n"
"Language-Team: none\n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.3.2\n"
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/js/web_widget_model_viewer.js:59
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:26
#, python-format
msgid "3D model"
msgstr "Model 3D"
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:8
#, python-format
msgid "Clear"
msgstr "Clar"
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/js/web_widget_model_viewer.js:59
#, python-format
msgid "Could not display the selected model."
msgstr "No s'ha pogut mostrar el model seleccionat."
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:7
#, python-format
msgid "Edit"
msgstr "Editar"
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:28
#, python-format
msgid "Fullscreen"
msgstr "Pantalla completa"
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:10
#, python-format
msgid "Uploading..."
msgstr "Pujant..."
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:28
#, python-format
msgid "View fullscreen"
msgstr "Veure pantalla completa"

View File

@@ -0,0 +1,67 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_widget_model_viewer
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2020-09-16 14:00+0000\n"
"Last-Translator: claudiagn <claudia.gargallo@qubiq.es>\n"
"Language-Team: none\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.10\n"
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/js/web_widget_model_viewer.js:59
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:26
#, python-format
msgid "3D model"
msgstr "Modelo 3D"
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:8
#, python-format
msgid "Clear"
msgstr "Claro"
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/js/web_widget_model_viewer.js:59
#, python-format
msgid "Could not display the selected model."
msgstr "No se pudo mostrar el modelo seleccionado."
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:7
#, python-format
msgid "Edit"
msgstr "Editar"
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:28
#, python-format
msgid "Fullscreen"
msgstr "Pantalla completa"
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:10
#, python-format
msgid "Uploading..."
msgstr "Subiendo..."
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:28
#, python-format
msgid "View fullscreen"
msgstr "Ver pantalla completa"

View File

@@ -0,0 +1,65 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_widget_model_viewer
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/js/web_widget_model_viewer.js:59
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:26
#, python-format
msgid "3D model"
msgstr ""
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:8
#, python-format
msgid "Clear"
msgstr ""
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/js/web_widget_model_viewer.js:59
#, python-format
msgid "Could not display the selected model."
msgstr ""
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:7
#, python-format
msgid "Edit"
msgstr ""
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:28
#, python-format
msgid "Fullscreen"
msgstr ""
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:10
#, python-format
msgid "Uploading..."
msgstr ""
#. module: web_widget_model_viewer
#. openerp-web
#: code:addons/web_widget_model_viewer/static/src/xml/web_widget_model_viewer.xml:28
#, python-format
msgid "View fullscreen"
msgstr ""

View File

@@ -0,0 +1,3 @@
* Lorenzo Battistini (https://takobi.online)
* Andrea Piovesana (https://openindustry.it)
* Marco Colombo (https://phi.technology)

View File

@@ -0,0 +1 @@
Chair © Copyright 2020 Shopify Inc., licensed under CC-BY-4.0.

View File

@@ -0,0 +1,15 @@
``<model-viewer>`` is a web component that makes rendering interactive 3D models - optionally in AR - easy to do, on as many browsers and devices as possible. ``<model-viewer>`` strives to give you great defaults for rendering quality and performance.
See `source repository <https://github.com/google/model-viewer>`_ and `documentation <https://modelviewer.dev/>`_.
The model to load is a GLTF 2.0 file format.
See `<https://www.khronos.org/gltf/>`_ and GLTF overview:
.. figure:: ../static/img/gltfOverview.png
Many engine developers have already started transitioning to glTF 2.0 to reap performance, portability and quality benefits, including BabylonJS, three.js, Cesium, Sketchfab, and xeogl and instant3Dhub engines. glTF 2.0 is also seeing industry support by companies such as Adobe, Google, Marmoset, Microsoft, NVIDIA, Oculus, UX3D, and more as well as prominent universities such as, University of Pennsylvania and Sapienza University of Rome.
"example" directory contains the GLB file of a chair, that is rendered in the following way:
.. figure:: ../static/img/Eames_Lounge_Chair.gif

View File

@@ -0,0 +1,14 @@
14.0.1.0.0 (2021-10-07)
~~~~~~~~~~~~~~~~~~~~~~~
* [MIG] v14
12.0.2.0.0 (2020-07-14)
~~~~~~~~~~~~~~~~~~~~~~~
* [IMP] fullscreen and view redesign
12.0.1.0.0 (2020-07-10)
~~~~~~~~~~~~~~~~~~~~~~~
* Start of the history.

View File

@@ -0,0 +1 @@
Add ``widget="model_viewer"`` to your binary field in form view. Optionally you can set ``style`` and ``max_upload_size`` (in MB) attributes.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,472 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<title>Model viewer widget</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="model-viewer-widget">
<h1 class="title">Model viewer widget</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/web/tree/14.0/web_widget_model_viewer"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/web-14-0/web-14-0-web_widget_model_viewer"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/162/14.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p><tt class="docutils literal"><span class="pre">&lt;model-viewer&gt;</span></tt> is a web component that makes rendering interactive 3D models - optionally in AR - easy to do, on as many browsers and devices as possible. <tt class="docutils literal"><span class="pre">&lt;model-viewer&gt;</span></tt> strives to give you great defaults for rendering quality and performance.</p>
<p>See <a class="reference external" href="https://github.com/google/model-viewer">source repository</a> and <a class="reference external" href="https://modelviewer.dev/">documentation</a>.</p>
<p>The model to load is a GLTF 2.0 file format.</p>
<p>See <a class="reference external" href="https://www.khronos.org/gltf/">https://www.khronos.org/gltf/</a> and GLTF overview:</p>
<div class="figure">
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_widget_model_viewer/static/img/gltfOverview.png" src="https://raw.githubusercontent.com/OCA/web/14.0/web_widget_model_viewer/static/img/gltfOverview.png" />
</div>
<p>Many engine developers have already started transitioning to glTF 2.0 to reap performance, portability and quality benefits, including BabylonJS, three.js, Cesium, Sketchfab, and xeogl and instant3Dhub engines. glTF 2.0 is also seeing industry support by companies such as Adobe, Google, Marmoset, Microsoft, NVIDIA, Oculus, UX3D, and more as well as prominent universities such as, University of Pennsylvania and Sapienza University of Rome.</p>
<p>“example” directory contains the GLB file of a chair, that is rendered in the following way:</p>
<div class="figure">
<img alt="https://raw.githubusercontent.com/OCA/web/14.0/web_widget_model_viewer/static/img/Eames_Lounge_Chair.gif" src="https://raw.githubusercontent.com/OCA/web/14.0/web_widget_model_viewer/static/img/Eames_Lounge_Chair.gif" />
</div>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="id4">Usage</a></li>
<li><a class="reference internal" href="#changelog" id="id5">Changelog</a><ul>
<li><a class="reference internal" href="#id1" id="id6">14.0.1.0.0 (2021-10-07)</a></li>
<li><a class="reference internal" href="#id2" id="id7">12.0.2.0.0 (2020-07-14)</a></li>
<li><a class="reference internal" href="#id3" id="id8">12.0.1.0.0 (2020-07-10)</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="id9">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id10">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id11">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id12">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="id13">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="id14">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id4">Usage</a></h1>
<p>Add <tt class="docutils literal"><span class="pre">widget=&quot;model_viewer&quot;</span></tt> to your binary field in form view. Optionally you can set <tt class="docutils literal">style</tt> and <tt class="docutils literal">max_upload_size</tt> (in MB) attributes.</p>
</div>
<div class="section" id="changelog">
<h1><a class="toc-backref" href="#id5">Changelog</a></h1>
<div class="section" id="id1">
<h2><a class="toc-backref" href="#id6">14.0.1.0.0 (2021-10-07)</a></h2>
<ul class="simple">
<li>[MIG] v14</li>
</ul>
</div>
<div class="section" id="id2">
<h2><a class="toc-backref" href="#id7">12.0.2.0.0 (2020-07-14)</a></h2>
<ul class="simple">
<li>[IMP] fullscreen and view redesign</li>
</ul>
</div>
<div class="section" id="id3">
<h2><a class="toc-backref" href="#id8">12.0.1.0.0 (2020-07-10)</a></h2>
<ul class="simple">
<li>Start of the history.</li>
</ul>
</div>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id9">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_widget_model_viewer%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id10">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id11">Authors</a></h2>
<ul class="simple">
<li>TAKOBI</li>
<li>Openindustry.it</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id12">Contributors</a></h2>
<ul class="simple">
<li>Lorenzo Battistini (<a class="reference external" href="https://takobi.online">https://takobi.online</a>)</li>
<li>Andrea Piovesana (<a class="reference external" href="https://openindustry.it">https://openindustry.it</a>)</li>
<li>Marco Colombo (<a class="reference external" href="https://phi.technology">https://phi.technology</a>)</li>
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#id13">Other credits</a></h2>
<p>Chair © Copyright 2020 Shopify Inc., licensed under CC-BY-4.0.</p>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id14">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external" href="https://github.com/eLBati"><img alt="eLBati" src="https://github.com/eLBati.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/14.0/web_widget_model_viewer">OCA/web</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

View File

@@ -0,0 +1,114 @@
// Copyright 2020 Lorenzo Battistini @ TAKOBI
// Copyright 2020 Andrea Piovesana @ Openindustry.it
odoo.define("web_widget_model_viewer.FieldBinaryModelViewer", function (require) {
"use strict";
var BasicFields = require("web.basic_fields");
var core = require("web.core");
var registry = require("web.field_registry");
var session = require("web.session");
var utils = require("web.utils");
var _t = core._t;
var qweb = core.qweb;
var FieldBinaryModelViewer = BasicFields.FieldBinaryFile.extend({
template: "FieldBinaryModelViewer",
events: _.extend({}, BasicFields.FieldBinaryFile.prototype.events, {
click: function () {
if (this.mode === "readonly") {
this.trigger_up("bounce_edit");
}
},
"click #model-viewer-fullscreen": "fullscreen",
}),
supportedFieldTypes: ["binary"],
init: function () {
this._super.apply(this, arguments);
var max_upload_size = this.attrs.max_upload_size;
if (max_upload_size) {
this.max_upload_size = parseInt(max_upload_size, 10) * 1024 * 1024;
} else {
// 250M
this.max_upload_size = 250 * 1024 * 1024;
}
},
_render: function () {
var self = this;
var url = "";
if (this.value) {
if (utils.is_bin_size(this.value)) {
url = session.url("/web/content", {
model: this.model,
id: JSON.stringify(this.res_id),
field: this.name,
});
} else {
url = "data:model/gltf-binary;base64," + this.value;
}
}
var $glb = $(
qweb.render("FieldBinaryModelViewer-glb", {widget: this, url: url})
);
var style = this.attrs.style;
if (style) {
$glb.attr("style", style);
}
this.$("> model-viewer").remove();
this.$el.prepend($glb);
$glb.on("error", function () {
self._clearFile();
$glb.attr("src", "");
self.do_warn(
_t("3D model"),
_t("Could not display the selected model.")
);
});
},
/* eslint-disable complexity */
fullscreen: function (ev) {
var isFullscreenAvailable =
document.fullscreenEnabled ||
document.mozFullScreenEnabled ||
document.webkitFullscreenEnabled ||
document.msFullscreenEnabled ||
false;
var modelViewerElem = ev.target.parentElement.parentElement.parentElement;
if (isFullscreenAvailable) {
var fullscreenElement =
document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
document.msFullscreenElement;
if (fullscreenElement) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
/* Firefox */
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
/* Chrome, Safari and Opera */
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
/* IE/Edge */
document.msExitFullscreen();
}
} else if (modelViewerElem.requestFullscreen) {
modelViewerElem.requestFullscreen();
} else if (modelViewerElem.mozRequestFullScreen) {
/* Firefox */
modelViewerElem.mozRequestFullScreen();
} else if (modelViewerElem.webkitRequestFullscreen) {
/* Chrome, Safari and Opera */
modelViewerElem.webkitRequestFullscreen();
} else if (modelViewerElem.msRequestFullscreen) {
/* IE/Edge */
modelViewerElem.msRequestFullscreen();
}
} else {
console.error("ERROR : full screen not supported by web browser");
}
},
});
registry.add("model_viewer", FieldBinaryModelViewer);
});

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates id="template" xml:space="preserve">
<t t-name="FieldBinaryModelViewer">
<div
class="o_field_image"
aria-atomic="true"
style="background-color: #FFFFFF;"
>
<t t-if="widget.mode !== 'readonly'">
<div class="o_form_image_controls">
<button
class="fa fa-pencil float-left o_select_file_button fa-2x"
title="Edit"
aria-label="Edit"
/>
<button
class="fa fa-trash-o float-right o_clear_file_button fa-2x"
title="Clear"
aria-label="Clear"
/>
<span class="o_form_binary_progress">Uploading...</span>
<t t-call="HiddenInputFile">
<t t-set="image_only" t-value="true" />
<t t-set="fileupload_id" t-value="widget.fileupload_id" />
</t>
</div>
</t>
</div>
</t>
<t t-name="FieldBinaryModelViewer-glb">
<model-viewer
t-att-src='url'
t-att-border="widget.readonly ? 0 : 1"
t-att-name="widget.name"
alt="3D model"
auto-rotate="1"
camera-controls="1"
>
<div class="text-center mt-2 mb-2 mr-2">
<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;" >-->
<!-- 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

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template
id="web_layout_model_viewer"
name="Web layout Model viewer"
inherit_id="web.layout"
>
<xpath expr="//head" position="inside">
<script
type="module"
src="/web_widget_model_viewer/static/src/lib/model-viewer.min.js"
/>
</xpath>
</template>
<!-- <template-->
<!-- id="assets_backend"-->
<!-- name="web_widget_model_viewer assets"-->
<!-- inherit_id="web.assets_backend"-->
<!-- >-->
<!-- <xpath expr="." position="inside">-->
<!-- <script-->
<!-- type="text/javascript"-->
<!-- src="/web_widget_model_viewer/static/src/js/web_widget_model_viewer.js"-->
<!-- />-->
<!-- </xpath>-->
<!-- </template>-->
</odoo>