Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop
# 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:
@@ -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="控制系统")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,10 @@ _logger = logging.getLogger(__name__)
|
||||
class AuthenticationError(Exception):
|
||||
pass
|
||||
|
||||
class AuthenticationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Http(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
@@ -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
BIN
sf_dlm/static/file/out.glb
Normal file
Binary file not shown.
BIN
sf_dlm/static/file/out.stl
Normal file
BIN
sf_dlm/static/file/out.stl
Normal file
Binary file not shown.
277
sf_dlm/static/util/stl2gltf.py
Normal file
277
sf_dlm/static/util/stl2gltf.py
Normal 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)
|
||||
@@ -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>
|
||||
@@ -10,5 +10,3 @@ from . import res_user
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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', '工序模板')
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
# 下载指定目录下的指定文件
|
||||
|
||||
@@ -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)
|
||||
|
||||
126
web_widget_model_viewer/README.rst
Normal file
126
web_widget_model_viewer/README.rst
Normal 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.
|
||||
0
web_widget_model_viewer/__init__.py
Normal file
0
web_widget_model_viewer/__init__.py
Normal file
33
web_widget_model_viewer/__manifest__.py
Normal file
33
web_widget_model_viewer/__manifest__.py
Normal 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,
|
||||
}
|
||||
BIN
web_widget_model_viewer/example/Chair.glb
Normal file
BIN
web_widget_model_viewer/example/Chair.glb
Normal file
Binary file not shown.
BIN
web_widget_model_viewer/example/chair.png
Normal file
BIN
web_widget_model_viewer/example/chair.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 123 KiB |
67
web_widget_model_viewer/i18n/ca.po
Normal file
67
web_widget_model_viewer/i18n/ca.po
Normal 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"
|
||||
67
web_widget_model_viewer/i18n/es.po
Normal file
67
web_widget_model_viewer/i18n/es.po
Normal 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"
|
||||
65
web_widget_model_viewer/i18n/pt_BR.po
Normal file
65
web_widget_model_viewer/i18n/pt_BR.po
Normal 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 ""
|
||||
3
web_widget_model_viewer/readme/CONTRIBUTORS.rst
Normal file
3
web_widget_model_viewer/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1,3 @@
|
||||
* Lorenzo Battistini (https://takobi.online)
|
||||
* Andrea Piovesana (https://openindustry.it)
|
||||
* Marco Colombo (https://phi.technology)
|
||||
1
web_widget_model_viewer/readme/CREDITS.rst
Normal file
1
web_widget_model_viewer/readme/CREDITS.rst
Normal file
@@ -0,0 +1 @@
|
||||
Chair © Copyright 2020 Shopify Inc., licensed under CC-BY-4.0.
|
||||
15
web_widget_model_viewer/readme/DESCRIPTION.rst
Normal file
15
web_widget_model_viewer/readme/DESCRIPTION.rst
Normal 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
|
||||
14
web_widget_model_viewer/readme/HISTORY.rst
Normal file
14
web_widget_model_viewer/readme/HISTORY.rst
Normal 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.
|
||||
1
web_widget_model_viewer/readme/USAGE.rst
Normal file
1
web_widget_model_viewer/readme/USAGE.rst
Normal 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.
|
||||
BIN
web_widget_model_viewer/static/description/icon.png
Normal file
BIN
web_widget_model_viewer/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
472
web_widget_model_viewer/static/description/index.html
Normal file
472
web_widget_model_viewer/static/description/index.html
Normal 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"><model-viewer></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"><model-viewer></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="model_viewer"</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>
|
||||
BIN
web_widget_model_viewer/static/img/Eames_Lounge_Chair.gif
Normal file
BIN
web_widget_model_viewer/static/img/Eames_Lounge_Chair.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.7 MiB |
BIN
web_widget_model_viewer/static/img/gltfOverview.png
Normal file
BIN
web_widget_model_viewer/static/img/gltfOverview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 MiB |
114
web_widget_model_viewer/static/src/js/web_widget_model_viewer.js
Normal file
114
web_widget_model_viewer/static/src/js/web_widget_model_viewer.js
Normal 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);
|
||||
});
|
||||
@@ -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>
|
||||
27
web_widget_model_viewer/views/assets.xml
Normal file
27
web_widget_model_viewer/views/assets.xml
Normal 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>
|
||||
Reference in New Issue
Block a user