kopia lustrzana https://github.com/vilemduha/blendercam
813 wiersze
27 KiB
Python
813 wiersze
27 KiB
Python
# blender CAM ops.py (c) 2012 Vilem Novak
|
|
#
|
|
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
#
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ***** END GPL LICENCE BLOCK *****
|
|
|
|
# blender operators definitions are in this file. They mostly call the functions from utils.py
|
|
|
|
|
|
import bpy
|
|
from bpy.props import *
|
|
from bpy_extras.io_utils import ImportHelper
|
|
|
|
import subprocess, os, threading
|
|
from cam import utils, pack, polygon_utils_cam, simple, gcodepath, bridges, simulation
|
|
from cam.async_op import AsyncOperatorMixin,AsyncCancelledException
|
|
import shapely
|
|
import mathutils
|
|
import math
|
|
import textwrap
|
|
import traceback
|
|
|
|
import cam
|
|
from cam.exception import *
|
|
|
|
class threadCom: # object passed to threads to read background process stdout info
|
|
def __init__(self, o, proc):
|
|
self.opname = o.name
|
|
self.outtext = ''
|
|
self.proc = proc
|
|
self.lasttext = ''
|
|
|
|
|
|
def threadread(tcom):
|
|
"""reads stdout of background process, done this way to have it non-blocking"""
|
|
inline = tcom.proc.stdout.readline()
|
|
inline = str(inline)
|
|
s = inline.find('progress{')
|
|
if s > -1:
|
|
e = inline.find('}')
|
|
tcom.outtext = inline[s + 9:e]
|
|
|
|
@bpy.app.handlers.persistent
|
|
def timer_update(context):
|
|
"""monitoring of background processes"""
|
|
text = ''
|
|
s = bpy.context.scene
|
|
if hasattr(bpy.ops.object.calculate_cam_paths_background.__class__, 'cam_processes'):
|
|
processes = bpy.ops.object.calculate_cam_paths_background.__class__.cam_processes
|
|
for p in processes:
|
|
# proc=p[1].proc
|
|
readthread = p[0]
|
|
tcom = p[1]
|
|
if not readthread.is_alive():
|
|
readthread.join()
|
|
# readthread.
|
|
tcom.lasttext = tcom.outtext
|
|
if tcom.outtext != '':
|
|
print(tcom.opname, tcom.outtext)
|
|
tcom.outtext = ''
|
|
|
|
if 'finished' in tcom.lasttext:
|
|
processes.remove(p)
|
|
|
|
o = s.cam_operations[tcom.opname]
|
|
o.computing = False
|
|
utils.reload_paths(o)
|
|
update_zbufferimage_tag = False
|
|
update_offsetimage_tag = False
|
|
else:
|
|
readthread = threading.Thread(target=threadread, args=([tcom]), daemon=True)
|
|
readthread.start()
|
|
p[0] = readthread
|
|
o = s.cam_operations[tcom.opname] # changes
|
|
o.outtext = tcom.lasttext # changes
|
|
|
|
|
|
class PathsBackground(bpy.types.Operator):
|
|
"""calculate CAM paths in background. File has to be saved before."""
|
|
bl_idname = "object.calculate_cam_paths_background"
|
|
bl_label = "Calculate CAM paths in background"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
s = bpy.context.scene
|
|
o = s.cam_operations[s.cam_active_operation]
|
|
self.operation = o
|
|
o.computing = True
|
|
|
|
bpath = bpy.app.binary_path
|
|
fpath = bpy.data.filepath
|
|
|
|
for p in bpy.utils.script_paths():
|
|
scriptpath = p + os.sep + 'addons' + os.sep + 'cam' + os.sep + 'backgroundop.py'
|
|
print(scriptpath)
|
|
if os.path.isfile(scriptpath):
|
|
break
|
|
proc = subprocess.Popen([bpath, '-b', fpath, '-P', scriptpath, '--', '-o=' + str(s.cam_active_operation)],
|
|
bufsize=1, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
|
|
|
|
tcom = threadCom(o, proc)
|
|
readthread = threading.Thread(target=threadread, args=([tcom]), daemon=True)
|
|
readthread.start()
|
|
# self.__class__.cam_processes=[]
|
|
if not hasattr(bpy.ops.object.calculate_cam_paths_background.__class__, 'cam_processes'):
|
|
bpy.ops.object.calculate_cam_paths_background.__class__.cam_processes = []
|
|
bpy.ops.object.calculate_cam_paths_background.__class__.cam_processes.append([readthread, tcom])
|
|
return {'FINISHED'}
|
|
|
|
|
|
class KillPathsBackground(bpy.types.Operator):
|
|
"""Remove CAM path processes in background."""
|
|
bl_idname = "object.kill_calculate_cam_paths_background"
|
|
bl_label = "Kill background computation of an operation"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
s = bpy.context.scene
|
|
o = s.cam_operations[s.cam_active_operation]
|
|
self.operation = o
|
|
|
|
if hasattr(bpy.ops.object.calculate_cam_paths_background.__class__, 'cam_processes'):
|
|
processes = bpy.ops.object.calculate_cam_paths_background.__class__.cam_processes
|
|
for p in processes:
|
|
tcom = p[1]
|
|
if tcom.opname == o.name:
|
|
processes.remove(p)
|
|
tcom.proc.kill()
|
|
o.computing = False
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
async def _calc_path(operator,context):
|
|
s = bpy.context.scene
|
|
o = s.cam_operations[s.cam_active_operation]
|
|
if o.geometry_source == 'OBJECT':
|
|
ob = bpy.data.objects[o.object_name]
|
|
ob.hide_set(False)
|
|
if o.geometry_source == 'COLLECTION':
|
|
obc = bpy.data.collections[o.collection_name]
|
|
for ob in obc.objects:
|
|
ob.hide_set(False)
|
|
if o.strategy == "CARVE":
|
|
curvob = bpy.data.objects[o.curve_object]
|
|
curvob.hide_set(False)
|
|
'''if o.strategy == 'WATERLINE':
|
|
ob = bpy.data.objects[o.object_name]
|
|
ob.select_set(True)
|
|
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)'''
|
|
if bpy.context.mode != 'OBJECT':
|
|
bpy.ops.object.mode_set(mode='OBJECT') # force object mode
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
path = bpy.data.objects.get('cam_path_{}'.format(o.name))
|
|
if path:
|
|
path.select_set(state=True)
|
|
bpy.ops.object.delete()
|
|
|
|
if not o.valid:
|
|
operator.report({'ERROR_INVALID_INPUT'}, "Operation can't be performed, see warnings for info")
|
|
progress_async("Operation can't be performed, see warnings for info")
|
|
return {'FINISHED',False}
|
|
|
|
#check for free movement height < maxz and return with error
|
|
if(o.movement.free_height < o.maxz):
|
|
operator.report({'ERROR_INVALID_INPUT'}, "Free movement height is less than Operation depth start \n correct and try again.")
|
|
progress_async("Operation can't be performed, see warnings for info")
|
|
return {'FINISHED',False}
|
|
|
|
if o.computing:
|
|
return {'FINISHED',False}
|
|
|
|
o.operator = operator
|
|
|
|
if o.use_layers:
|
|
o.movement.parallel_step_back = False
|
|
try:
|
|
await gcodepath.getPath(context, o)
|
|
print("Got path okay")
|
|
except CamException as e:
|
|
traceback.print_tb(e.__traceback__)
|
|
error_str="\n".join(textwrap.wrap(str(e),width=80))
|
|
operator.report({'ERROR'},error_str)
|
|
return {'FINISHED',False}
|
|
except AsyncCancelledException as e:
|
|
return {'CANCELLED',False}
|
|
except Exception as e:
|
|
print("FAIL",e)
|
|
traceback.print_tb(e.__traceback__)
|
|
operator.report({'ERROR'},str(e))
|
|
return {'FINISHED',False}
|
|
coll = bpy.data.collections.get('RigidBodyWorld')
|
|
if coll:
|
|
bpy.data.collections.remove(coll)
|
|
|
|
return {'FINISHED',True}
|
|
|
|
|
|
class CalculatePath(bpy.types.Operator,AsyncOperatorMixin):
|
|
"""calculate CAM paths"""
|
|
bl_idname = "object.calculate_cam_path"
|
|
bl_label = "Calculate CAM paths"
|
|
bl_options = {'REGISTER', 'UNDO','BLOCKING'}
|
|
|
|
@classmethod
|
|
def poll(cls,context):
|
|
s = context.scene
|
|
o = s.cam_operations[s.cam_active_operation]
|
|
if o is not None:
|
|
if cam.isValid(o,context):
|
|
return True
|
|
return False
|
|
|
|
async def execute_async(self, context):
|
|
(retval,success) = await _calc_path(self,context)
|
|
print(f"CALCULATED PATH (success={success},retval={retval}")
|
|
return retval
|
|
|
|
|
|
class PathsAll(bpy.types.Operator):
|
|
"""calculate all CAM paths"""
|
|
bl_idname = "object.calculate_cam_paths_all"
|
|
bl_label = "Calculate all CAM paths"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
i = 0
|
|
for o in bpy.context.scene.cam_operations:
|
|
bpy.context.scene.cam_active_operation = i
|
|
print('\nCalculating path :' + o.name)
|
|
print('\n')
|
|
bpy.ops.object.calculate_cam_paths_background()
|
|
i += 1
|
|
|
|
return {'FINISHED'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.prop_search(self, "operation", bpy.context.scene, "cam_operations")
|
|
|
|
|
|
class CamPackObjects(bpy.types.Operator):
|
|
"""calculate all CAM paths"""
|
|
bl_idname = "object.cam_pack_objects"
|
|
bl_label = "Pack curves on sheet"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
bpy.ops.object.mode_set(mode='OBJECT') # force object mode
|
|
obs = bpy.context.selected_objects
|
|
pack.packCurves()
|
|
# layout.
|
|
return {'FINISHED'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
|
|
class CamSliceObjects(bpy.types.Operator):
|
|
"""Slice a mesh object horizontally"""
|
|
# warning, this is a separate and neglected feature, it's a mess - by now it just slices up the object.
|
|
bl_idname = "object.cam_slice_objects"
|
|
bl_label = "Slice object - usefull for lasercut puzzles e.t.c."
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
from cam import slice
|
|
ob = bpy.context.active_object
|
|
slice.sliceObject(ob)
|
|
return {'FINISHED'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
|
|
def getChainOperations(chain):
|
|
"""return chain operations, currently chain object can't store operations directly due to blender limitations"""
|
|
chop = []
|
|
for cho in chain.operations:
|
|
for so in bpy.context.scene.cam_operations:
|
|
if so.name == cho.name:
|
|
chop.append(so)
|
|
return chop
|
|
|
|
|
|
class PathsChain(bpy.types.Operator,AsyncOperatorMixin):
|
|
"""calculate a chain and export the gcode alltogether. """
|
|
bl_idname = "object.calculate_cam_paths_chain"
|
|
bl_label = "Calculate CAM paths in current chain and export chain gcode"
|
|
bl_options = {'REGISTER', 'UNDO','BLOCKING'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
s = context.scene
|
|
chain = s.cam_chains[s.cam_active_chain]
|
|
return cam.isChainValid(chain,context)[0]
|
|
|
|
async def execute_async(self, context):
|
|
s = context.scene
|
|
bpy.ops.object.mode_set(mode='OBJECT') # force object mode
|
|
chain = s.cam_chains[s.cam_active_chain]
|
|
chainops = getChainOperations(chain)
|
|
meshes = []
|
|
try:
|
|
for i in range(0, len(chainops)):
|
|
s.cam_active_operation = s.cam_operations.find(chainops[i].name)
|
|
self.report({'INFO'},f"Calculating path: {chainops[i].name}")
|
|
result,success=await _calc_path(self,context)
|
|
if not success and 'FINISHED' in result:
|
|
self.report({'ERROR'},f"Couldn't calculate path: {chainops[i].name}")
|
|
except Exception as e:
|
|
print("FAIL",e)
|
|
traceback.print_tb(e.__traceback__)
|
|
operator.report({'ERROR'},str(e))
|
|
return {'FINISHED'}
|
|
|
|
for o in chainops:
|
|
meshes.append(bpy.data.objects["cam_path_{}".format(o.name)].data)
|
|
gcodepath.exportGcodePath(chain.filename, meshes, chainops)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class PathExportChain(bpy.types.Operator):
|
|
"""calculate a chain and export the gcode alltogether. """
|
|
bl_idname = "object.cam_export_paths_chain"
|
|
bl_label = "Export CAM paths in current chain as gcode"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
s = context.scene
|
|
chain = s.cam_chains[s.cam_active_chain]
|
|
return cam.isChainValid(chain,context)[0]
|
|
|
|
def execute(self, context):
|
|
s = bpy.context.scene
|
|
|
|
chain = s.cam_chains[s.cam_active_chain]
|
|
chainops = getChainOperations(chain)
|
|
meshes = []
|
|
|
|
# if len(chainops)<4:
|
|
|
|
for o in chainops:
|
|
# bpy.ops.object.calculate_cam_paths_background()
|
|
meshes.append(bpy.data.objects["cam_path_{}".format(o.name)].data)
|
|
gcodepath.exportGcodePath(chain.filename, meshes, chainops)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class PathExport(bpy.types.Operator):
|
|
"""Export gcode. Can be used only when the path object is present"""
|
|
bl_idname = "object.cam_export"
|
|
bl_label = "Export operation gcode"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
|
|
s = bpy.context.scene
|
|
operation = s.cam_operations[s.cam_active_operation]
|
|
|
|
print("EXPORTING", operation.filename, bpy.data.objects["cam_path_{}".format(operation.name)].data, operation)
|
|
|
|
gcodepath.exportGcodePath(operation.filename, [bpy.data.objects["cam_path_{}".format(operation.name)].data],
|
|
[operation])
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CAMSimulate(bpy.types.Operator,AsyncOperatorMixin):
|
|
"""simulate CAM operation
|
|
this is performed by: creating an image, painting Z depth of the brush substractively.
|
|
Works only for some operations, can not be used for 4-5 axis."""
|
|
bl_idname = "object.cam_simulate"
|
|
bl_label = "CAM simulation"
|
|
bl_options = {'REGISTER', 'UNDO','BLOCKING'}
|
|
|
|
operation: StringProperty(name="Operation",
|
|
description="Specify the operation to calculate", default='Operation')
|
|
|
|
async def execute_async(self, context):
|
|
s = bpy.context.scene
|
|
operation = s.cam_operations[s.cam_active_operation]
|
|
|
|
operation_name = "cam_path_{}".format(operation.name)
|
|
|
|
if operation_name in bpy.data.objects:
|
|
try:
|
|
await simulation.doSimulation(operation_name, [operation])
|
|
except AsyncCancelledException as e:
|
|
return {'CANCELLED'}
|
|
else:
|
|
self.report({'ERROR'},'no computed path to simulate')
|
|
return {'FINISHED'}
|
|
return {'FINISHED'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.prop_search(self, "operation", bpy.context.scene, "cam_operations")
|
|
|
|
|
|
class CAMSimulateChain(bpy.types.Operator, AsyncOperatorMixin):
|
|
"""simulate CAM chain, compared to single op simulation just writes into one image and thus enables
|
|
to see how ops work together."""
|
|
bl_idname = "object.cam_simulate_chain"
|
|
bl_label = "CAM simulation"
|
|
bl_options = {'REGISTER', 'UNDO','BLOCKING'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
s = context.scene
|
|
chain = s.cam_chains[s.cam_active_chain]
|
|
return cam.isChainValid(chain,context)[0]
|
|
|
|
operation: StringProperty(name="Operation",
|
|
description="Specify the operation to calculate", default='Operation')
|
|
|
|
async def execute_async(self, context):
|
|
s = bpy.context.scene
|
|
chain = s.cam_chains[s.cam_active_chain]
|
|
chainops = getChainOperations(chain)
|
|
|
|
canSimulate = True
|
|
for operation in chainops:
|
|
if operation.name not in bpy.data.objects:
|
|
canSimulate = True # force true
|
|
print("operation name " + str(operation.name))
|
|
if canSimulate:
|
|
try:
|
|
await simulation.doSimulation(chain.name, chainops)
|
|
except AsyncCancelledException as e:
|
|
return {'CANCELLED'}
|
|
else:
|
|
print('no computed path to simulate')
|
|
return {'FINISHED'}
|
|
return {'FINISHED'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.prop_search(self, "operation", bpy.context.scene, "cam_operations")
|
|
|
|
|
|
class CamChainAdd(bpy.types.Operator):
|
|
"""Add new CAM chain"""
|
|
bl_idname = "scene.cam_chain_add"
|
|
bl_label = "Add new CAM chain"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene is not None
|
|
|
|
def execute(self, context):
|
|
# main(context)
|
|
s = bpy.context.scene
|
|
s.cam_chains.add()
|
|
chain = s.cam_chains[-1]
|
|
s.cam_active_chain = len(s.cam_chains) - 1
|
|
chain.name = 'Chain_' + str(s.cam_active_chain + 1)
|
|
chain.filename = chain.name
|
|
chain.index = s.cam_active_chain
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CamChainRemove(bpy.types.Operator):
|
|
"""Remove CAM chain"""
|
|
bl_idname = "scene.cam_chain_remove"
|
|
bl_label = "Remove CAM chain"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene is not None
|
|
|
|
def execute(self, context):
|
|
bpy.context.scene.cam_chains.remove(bpy.context.scene.cam_active_chain)
|
|
if bpy.context.scene.cam_active_chain > 0:
|
|
bpy.context.scene.cam_active_chain -= 1
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CamChainOperationAdd(bpy.types.Operator):
|
|
"""Add operation to chain"""
|
|
bl_idname = "scene.cam_chain_operation_add"
|
|
bl_label = "Add operation to chain"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene is not None
|
|
|
|
def execute(self, context):
|
|
s = bpy.context.scene
|
|
chain = s.cam_chains[s.cam_active_chain]
|
|
s = bpy.context.scene
|
|
chain.operations.add()
|
|
chain.active_operation += 1
|
|
chain.operations[-1].name = s.cam_operations[s.cam_active_operation].name
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CamChainOperationUp(bpy.types.Operator):
|
|
"""Add operation to chain"""
|
|
bl_idname = "scene.cam_chain_operation_up"
|
|
bl_label = "Add operation to chain"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene is not None
|
|
|
|
def execute(self, context):
|
|
s = bpy.context.scene
|
|
chain = s.cam_chains[s.cam_active_chain]
|
|
a = chain.active_operation
|
|
if a > 0:
|
|
chain.operations.move(a, a - 1)
|
|
chain.active_operation -= 1
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CamChainOperationDown(bpy.types.Operator):
|
|
"""Add operation to chain"""
|
|
bl_idname = "scene.cam_chain_operation_down"
|
|
bl_label = "Add operation to chain"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene is not None
|
|
|
|
def execute(self, context):
|
|
s = bpy.context.scene
|
|
chain = s.cam_chains[s.cam_active_chain]
|
|
a = chain.active_operation
|
|
if a < len(chain.operations) - 1:
|
|
chain.operations.move(a, a + 1)
|
|
chain.active_operation += 1
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CamChainOperationRemove(bpy.types.Operator):
|
|
"""Remove operation from chain"""
|
|
bl_idname = "scene.cam_chain_operation_remove"
|
|
bl_label = "Remove operation from chain"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene is not None
|
|
|
|
def execute(self, context):
|
|
s = bpy.context.scene
|
|
chain = s.cam_chains[s.cam_active_chain]
|
|
chain.operations.remove(chain.active_operation)
|
|
chain.active_operation -= 1
|
|
if chain.active_operation < 0:
|
|
chain.active_operation = 0
|
|
return {'FINISHED'}
|
|
|
|
|
|
def fixUnits():
|
|
"""Sets up units for blender CAM"""
|
|
s = bpy.context.scene
|
|
|
|
s.unit_settings.system_rotation = 'DEGREES'
|
|
|
|
s.unit_settings.scale_length = 1.0
|
|
# Blender CAM doesn't respect this property and there were users reporting problems, not seeing this was changed.
|
|
|
|
|
|
# add pocket op for medial axis and profile cut inside to clean unremoved material
|
|
def Add_Pocket(self, maxdepth, sname, new_cutter_diameter):
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
s = bpy.context.scene
|
|
mpocket_exists = False
|
|
for ob in s.objects: # delete old medial pocket
|
|
if ob.name.startswith("medial_poc"):
|
|
ob.select_set(True)
|
|
bpy.ops.object.delete()
|
|
|
|
for op in s.cam_operations: # verify medial pocket operation exists
|
|
if op.name == "MedialPocket":
|
|
mpocket_exists = True
|
|
|
|
ob = bpy.data.objects[sname]
|
|
ob.select_set(True)
|
|
bpy.context.view_layer.objects.active = ob
|
|
utils.silhoueteOffset(ob, -new_cutter_diameter/2, 1, 0.3)
|
|
bpy.context.active_object.name = 'medial_pocket'
|
|
|
|
if not mpocket_exists: # create a pocket operation if it does not exist already
|
|
s.cam_operations.add()
|
|
o = s.cam_operations[-1]
|
|
o.object_name= 'medial_pocket'
|
|
s.cam_active_operation = len(s.cam_operations) - 1
|
|
o.name = 'MedialPocket'
|
|
o.filename = o.name
|
|
o.strategy = 'POCKET'
|
|
o.use_layers = False
|
|
o.material.estimate_from_model = False
|
|
o.material.size[2] = -maxdepth
|
|
o.minz_from = 'MATERIAL'
|
|
|
|
class CamOperationAdd(bpy.types.Operator):
|
|
"""Add new CAM operation"""
|
|
bl_idname = "scene.cam_operation_add"
|
|
bl_label = "Add new CAM operation"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene is not None
|
|
|
|
def execute(self, context):
|
|
s = bpy.context.scene
|
|
fixUnits()
|
|
|
|
ob = bpy.context.active_object
|
|
if ob is None:
|
|
self.report({'ERROR_INVALID_INPUT'}, "Please add an object to base the operation on.")
|
|
return {'CANCELLED'}
|
|
|
|
minx, miny, minz, maxx, maxy, maxz = utils.getBoundsWorldspace([ob])
|
|
s.cam_operations.add()
|
|
o = s.cam_operations[-1]
|
|
o.object_name = ob.name
|
|
o.minz = minz
|
|
|
|
s.cam_active_operation = len(s.cam_operations) - 1
|
|
|
|
o.name = f"Op_{ob.name}_{s.cam_active_operation + 1}"
|
|
o.filename = o.name
|
|
|
|
if s.objects.get('CAM_machine') is None:
|
|
utils.addMachineAreaObject()
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CamOperationCopy(bpy.types.Operator):
|
|
"""Copy CAM operation"""
|
|
bl_idname = "scene.cam_operation_copy"
|
|
bl_label = "Copy active CAM operation"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene is not None
|
|
|
|
def execute(self, context):
|
|
# main(context)
|
|
scene = bpy.context.scene
|
|
|
|
fixUnits()
|
|
|
|
scene = bpy.context.scene
|
|
if len(scene.cam_operations) == 0: return {'CANCELLED'}
|
|
copyop = scene.cam_operations[scene.cam_active_operation]
|
|
scene.cam_operations.add()
|
|
scene.cam_active_operation += 1
|
|
l = len(scene.cam_operations) - 1
|
|
scene.cam_operations.move(l, scene.cam_active_operation)
|
|
o = scene.cam_operations[scene.cam_active_operation]
|
|
|
|
for k in copyop.keys():
|
|
o[k] = copyop[k]
|
|
o.computing = False
|
|
|
|
# ###get digits in the end
|
|
|
|
isdigit = True
|
|
numdigits = 0
|
|
num = 0
|
|
if o.name[-1].isdigit():
|
|
numdigits = 1
|
|
while isdigit:
|
|
numdigits += 1
|
|
isdigit = o.name[-numdigits].isdigit()
|
|
numdigits -= 1
|
|
o.name = o.name[:-numdigits] + str(int(o.name[-numdigits:]) + 1).zfill(numdigits)
|
|
o.filename = o.name
|
|
else:
|
|
o.name = o.name + '_copy'
|
|
o.filename = o.filename + '_copy'
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CamOperationRemove(bpy.types.Operator):
|
|
"""Remove CAM operation"""
|
|
bl_idname = "scene.cam_operation_remove"
|
|
bl_label = "Remove CAM operation"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene is not None
|
|
|
|
def execute(self, context):
|
|
scene = context.scene
|
|
try:
|
|
if len(scene.cam_operations) == 0: return {'CANCELLED'}
|
|
active_op = scene.cam_operations[scene.cam_active_operation]
|
|
active_op_object = bpy.data.objects[active_op.name]
|
|
scene.objects.active = active_op_object
|
|
bpy.ops.object.delete(True)
|
|
except:
|
|
pass
|
|
|
|
ao = scene.cam_operations[scene.cam_active_operation]
|
|
print(cam.was_hidden_dict)
|
|
if ao.name in cam.was_hidden_dict:
|
|
del cam.was_hidden_dict[ao.name]
|
|
|
|
scene.cam_operations.remove(scene.cam_active_operation)
|
|
if scene.cam_active_operation > 0:
|
|
scene.cam_active_operation -= 1
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
# move cam operation in the list up or down
|
|
class CamOperationMove(bpy.types.Operator):
|
|
"""Move CAM operation"""
|
|
bl_idname = "scene.cam_operation_move"
|
|
bl_label = "Move CAM operation in list"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
direction: EnumProperty(name='direction',
|
|
items=(('UP', 'Up', ''), ('DOWN', 'Down', '')),
|
|
description='direction', default='DOWN')
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene is not None
|
|
|
|
def execute(self, context):
|
|
# main(context)
|
|
a = bpy.context.scene.cam_active_operation
|
|
cops = bpy.context.scene.cam_operations
|
|
if self.direction == 'UP':
|
|
if a > 0:
|
|
cops.move(a, a - 1)
|
|
bpy.context.scene.cam_active_operation -= 1
|
|
|
|
else:
|
|
if a < len(cops) - 1:
|
|
cops.move(a, a + 1)
|
|
bpy.context.scene.cam_active_operation += 1
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CamOrientationAdd(bpy.types.Operator):
|
|
"""Add orientation to cam operation, for multiaxis operations"""
|
|
bl_idname = "scene.cam_orientation_add"
|
|
bl_label = "Add orientation"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene is not None
|
|
|
|
def execute(self, context):
|
|
s = bpy.context.scene
|
|
a = s.cam_active_operation
|
|
o = s.cam_operations[a]
|
|
gname = o.name + '_orientations'
|
|
bpy.ops.object.empty_add(type='ARROWS')
|
|
|
|
oriob = bpy.context.active_object
|
|
oriob.empty_draw_size = 0.02 # 2 cm
|
|
|
|
simple.addToGroup(oriob, gname)
|
|
oriob.name = 'ori_' + o.name + '.' + str(len(bpy.data.collections[gname].objects)).zfill(3)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CamBridgesAdd(bpy.types.Operator):
|
|
"""Add bridge objects to curve"""
|
|
bl_idname = "scene.cam_bridges_add"
|
|
bl_label = "Add bridges"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene is not None
|
|
|
|
def execute(self, context):
|
|
s = bpy.context.scene
|
|
a = s.cam_active_operation
|
|
o = s.cam_operations[a]
|
|
bridges.addAutoBridges(o)
|
|
return {'FINISHED'}
|