blendercam/scripts/addons/cam/simple.py

378 wiersze
11 KiB
Python
Czysty Zwykły widok Historia

# blender CAM simple.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 *****
2023-07-21 10:49:06 +00:00
# Solves: No module named 'shapely' (even if it is installed)
2023-11-30 19:01:04 +00:00
#help('modules')
2023-07-21 10:49:06 +00:00
2022-01-21 17:24:35 +00:00
import math
import sys
import os
import string
import time
import bpy
import mathutils
from mathutils import *
from math import *
from shapely.geometry import Point, LineString, Polygon, MultiLineString
def tuple_add(t, t1): # add two tuples as Vectors
return t[0] + t1[0], t[1] + t1[1], t[2] + t1[2]
def tuple_sub(t, t1): # sub two tuples as Vectors
return t[0] - t1[0], t[1] - t1[1], t[2] - t1[2]
def tuple_mul(t, c): # multiply two tuples with a number
return t[0] * c, t[1] * c, t[2] * c
def tuple_length(t): # get length of vector, but passed in as tuple.
return Vector(t).length
# timing functions for optimisation purposes...
def timinginit():
return [0, 0]
def timingstart(tinf):
t = time.time()
tinf[1] = t
def timingadd(tinf):
t = time.time()
tinf[0] += t - tinf[1]
def timingprint(tinf):
print('time ' + str(tinf[0]) + 'seconds')
def progress(text, n=None):
2020-10-12 16:45:08 +00:00
"""function for reporting during the script, works for background operations in the header."""
text = str(text)
if n is None:
n = ''
else:
n = ' ' + str(int(n * 1000) / 1000) + '%'
sys.stdout.write('progress{%s%s}\n' % (text, n))
sys.stdout.flush()
def activate(o):
2020-10-12 16:45:08 +00:00
"""makes an object active, used many times in blender"""
s = bpy.context.scene
bpy.ops.object.select_all(action='DESELECT')
o.select_set(state=True)
s.objects[o.name].select_set(state=True)
bpy.context.view_layer.objects.active = o
def dist2d(v1, v2):
2020-10-12 16:45:08 +00:00
"""distance between two points in 2d"""
return math.hypot((v1[0] - v2[0]), (v1[1] - v2[1]))
def delob(ob):
2020-10-12 16:45:08 +00:00
"""object deletion for multiple uses"""
activate(ob)
bpy.ops.object.delete(use_global=False)
def dupliob(o, pos):
2020-10-12 16:45:08 +00:00
"""helper function for visualising cutter positions in bullet simulation"""
activate(o)
bpy.ops.object.duplicate()
s = 1.0 / BULLET_SCALE
bpy.ops.transform.resize(value=(s, s, s), constraint_axis=(False, False, False), orient_type='GLOBAL',
mirror=False, use_proportional_edit=False, proportional_edit_falloff='SMOOTH',
proportional_size=1)
o = bpy.context.active_object
bpy.ops.rigidbody.object_remove()
o.location = pos
def addToGroup(ob, groupname):
activate(ob)
if bpy.data.groups.get(groupname) is None:
bpy.ops.group.create(name=groupname)
else:
bpy.ops.object.group_link(group=groupname)
def compare(v1, v2, vmiddle, e):
2020-10-12 16:45:08 +00:00
"""comparison for optimisation of paths"""
# e=0.0001
v1 = Vector(v1)
v2 = Vector(v2)
vmiddle = Vector(vmiddle)
vect1 = v2 - v1
vect2 = vmiddle - v1
vect1.normalize()
vect1 *= vect2.length
v = vect2 - vect1
if v.length < e:
return True
return False
def isVerticalLimit(v1, v2, limit):
2020-10-12 16:45:08 +00:00
"""test path segment on verticality threshold, for protect_vertical option"""
z = abs(v1[2] - v2[2])
# verticality=0.05
# this will be better.
#
# print(a)
if z > 0:
v2d = Vector((0, 0, -1))
v3d = Vector((v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]))
a = v3d.angle(v2d)
if a > pi / 2:
a = abs(a - pi)
# print(a)
if a < limit:
# print(abs(v1[0]-v2[0])/z)
# print(abs(v1[1]-v2[1])/z)
if v1[2] > v2[2]:
v1 = (v2[0], v2[1], v1[2])
return v1, v2
else:
v2 = (v1[0], v1[1], v2[2])
return v1, v2
return v1, v2
def getCachePath(o):
fn = bpy.data.filepath
l = len(bpy.path.basename(fn))
bn = bpy.path.basename(fn)[:-6]
print('fn-l:', fn[:-l])
print('bn:', bn)
iname = fn[:-l] + 'temp_cam' + os.sep + bn + '_' + o.name
return iname
2021-08-12 15:50:34 +00:00
def getSimulationPath():
fn = bpy.data.filepath
l = len(bpy.path.basename(fn))
iname = fn[:-l] + 'temp_cam' + os.sep
return iname
def safeFileName(name): # for export gcode
valid_chars = "-_.()%s%s" % (string.ascii_letters, string.digits)
filename = ''.join(c for c in name if c in valid_chars)
return filename
def strInUnits(x, precision=5):
if bpy.context.scene.unit_settings.system == 'METRIC':
return str(round(x * 1000, precision)) + ' mm '
elif bpy.context.scene.unit_settings.system == 'IMPERIAL':
return str(round(x * 1000 / 25.4, precision)) + "'' "
else:
return str(x)
2022-01-22 21:41:02 +00:00
# select multiple object starting with name
def select_multiple(name):
scene = bpy.context.scene
2022-01-22 21:41:02 +00:00
bpy.ops.object.select_all(action='DESELECT')
for ob in scene.objects: # join pocket curve calculations
if ob.name.startswith(name):
ob.select_set(True)
else:
ob.select_set(False)
2022-01-22 21:41:02 +00:00
# join multiple objects starting with 'name' renaming final object as 'name'
def join_multiple(name):
select_multiple(name)
bpy.ops.object.join()
bpy.context.active_object.name = name # rename object
2021-09-08 13:40:47 +00:00
# remove multiple objects starting with 'name'.... useful for fixed name operation
2022-01-22 21:41:02 +00:00
def remove_multiple(name):
scene = bpy.context.scene
bpy.ops.object.select_all(action='DESELECT')
for ob in scene.objects:
if ob.name.startswith(name):
ob.select_set(True)
bpy.ops.object.delete()
2021-12-05 20:03:11 +00:00
2021-12-29 20:13:02 +00:00
2022-01-29 15:51:36 +00:00
def deselect():
bpy.ops.object.select_all(action='DESELECT')
# makes the object with the name active
2022-01-22 21:41:02 +00:00
def make_active(name):
2021-12-05 20:03:11 +00:00
ob = bpy.context.scene.objects[name]
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = ob
2021-12-05 20:03:11 +00:00
ob.select_set(True)
2021-12-29 20:13:02 +00:00
# change the name of the active object
2022-01-21 17:15:05 +00:00
def active_name(name):
2021-12-30 21:13:53 +00:00
bpy.context.active_object.name = name
2021-12-29 20:13:02 +00:00
2022-01-02 16:28:39 +00:00
# renames and makes active name and makes it active
def rename(name, name2):
2022-01-22 21:41:02 +00:00
make_active(name)
2022-01-02 16:28:39 +00:00
bpy.context.active_object.name = name2
# boolean union of objects starting with name result is object name.
# all objects starting with name will be deleted and the result will be name
2022-01-02 16:28:39 +00:00
def union(name):
2022-01-22 21:41:02 +00:00
select_multiple(name)
2022-01-02 16:28:39 +00:00
bpy.ops.object.curve_boolean(boolean_type='UNION')
2022-01-21 17:15:05 +00:00
active_name('unionboolean')
2022-01-22 21:41:02 +00:00
remove_multiple(name)
2022-01-06 14:17:21 +00:00
rename('unionboolean', name)
2022-01-04 16:28:31 +00:00
2022-02-13 12:38:21 +00:00
def intersect(name):
select_multiple(name)
bpy.ops.object.curve_boolean(boolean_type='INTERSECT')
active_name('intersection')
2022-01-02 16:28:39 +00:00
# boolean difference of objects starting with name result is object from basename.
# all objects starting with name will be deleted and the result will be basename
2022-01-06 14:17:21 +00:00
def difference(name, basename):
# name is the series to select
# basename is what the base you want to cut including name
2022-01-22 21:41:02 +00:00
select_multiple(name)
2022-01-06 14:17:21 +00:00
bpy.context.view_layer.objects.active = bpy.data.objects[basename]
2022-01-02 16:28:39 +00:00
bpy.ops.object.curve_boolean(boolean_type='DIFFERENCE')
2022-01-21 17:15:05 +00:00
active_name('booleandifference')
2022-01-22 21:41:02 +00:00
remove_multiple(name)
2022-01-06 14:17:21 +00:00
rename('booleandifference', basename)
2022-01-03 19:43:58 +00:00
# duplicate active object or duplicate move
# if x or y not the default, duplicate move will be executed
def duplicate(x=0, y=0):
if x == 0 and y == 0:
2022-01-07 13:43:45 +00:00
bpy.ops.object.duplicate()
else:
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked": False, "mode": 'TRANSLATION'},
TRANSFORM_OT_translate={"value": (x, y, 0.0)})
# Mirror active object along the x axis
def mirrorx():
bpy.ops.transform.mirror(orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
orient_matrix_type='GLOBAL', constraint_axis=(True, False, False))
# mirror active object along y axis
def mirrory():
bpy.ops.transform.mirror(orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
orient_matrix_type='GLOBAL', constraint_axis=(False, True, False))
# move active object and apply translation
def move(x=0.0, y=0.0):
bpy.ops.transform.translate(value=(x, y, 0.0))
bpy.ops.object.transform_apply(location=True)
# Rotate active object and apply rotation
def rotate(angle):
bpy.context.object.rotation_euler[2] = angle
bpy.ops.object.transform_apply(rotation=True)
# remove doubles
2022-01-21 17:28:28 +00:00
def remove_doubles():
bpy.ops.object.curve_remove_doubles()
# Add overcut to active object
def add_overcut(diametre, overcut=True):
if overcut:
name = bpy.context.active_object.name
bpy.ops.object.curve_overcuts(diameter=diametre, threshold=math.pi/2.05)
overcut_name = bpy.context.active_object.name
2022-01-22 21:41:02 +00:00
make_active(name)
bpy.ops.object.delete()
rename(overcut_name, name)
2022-01-21 17:28:28 +00:00
remove_doubles()
# add bounding rectangtle to curve
2022-01-21 13:13:58 +00:00
def add_bound_rectangle(xmin, ymin, xmax, ymax, name='bounds_rectangle'):
# xmin = minimum corner x value
# ymin = minimum corner y value
# xmax = maximum corner x value
# ymax = maximum corner y value
# name = name of the resulting object
xsize = xmax - xmin
ysize = ymax - ymin
bpy.ops.curve.simple(align='WORLD', location=(xmin + xsize/2, ymin + ysize/2, 0), rotation=(0, 0, 0),
Simple_Type='Rectangle',
Simple_width=xsize, Simple_length=ysize, use_cyclic_u=True, edit_mode=False, shape='3D')
2022-01-17 13:27:08 +00:00
bpy.ops.object.transform_apply(location=True)
2022-01-21 17:15:05 +00:00
active_name(name)
2022-01-21 13:13:58 +00:00
def add_rectangle(width, height, center_x=True, center_y=True):
x_offset = width / 2
y_offset = height / 2
if center_x:
x_offset = 0
if center_y:
y_offset = 0
bpy.ops.curve.simple(align='WORLD', location=(x_offset, y_offset, 0), rotation=(0, 0, 0),
Simple_Type='Rectangle',
Simple_width=width, Simple_length=height, use_cyclic_u=True, edit_mode=False, shape='3D')
bpy.ops.object.transform_apply(location=True)
2022-01-21 17:15:05 +00:00
active_name('simple_rectangle')
2022-01-21 13:13:58 +00:00
# Returns coords from active object
2022-01-21 17:24:35 +00:00
def active_to_coords():
bpy.ops.object.duplicate()
obj = bpy.context.active_object
bpy.ops.object.convert(target='MESH')
2022-01-21 17:15:05 +00:00
active_name("_tmp_mesh")
coords = []
for v in obj.data.vertices: # extract X,Y coordinates from the vertices data
coords.append((v.co.x, v.co.y))
2022-01-22 21:41:02 +00:00
remove_multiple('_tmp_mesh')
return coords
# returns shapely polygon from active object
2022-01-21 17:24:35 +00:00
def active_to_shapely_poly():
return Polygon(active_to_coords()) # convert coordinates to shapely Polygon datastructure