mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
cad operators - use Blender units for props
This commit is contained in:
@@ -130,7 +130,7 @@ class CadFillet(bpy.types.Operator):
|
||||
bl_label = "CAD Fillet"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resolution: bpy.props.IntProperty(name="Arc Resolution", min=0, default=1)
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.1, subtype="DISTANCE")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -143,18 +143,15 @@ class CadFillet(bpy.types.Operator):
|
||||
layout.prop(self, prop)
|
||||
|
||||
def execute(self, context):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
self.radius = self.radius * si_conversion
|
||||
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
obj = bpy.context.active_object
|
||||
cursor = obj.matrix_world.inverted() @ bpy.context.scene.cursor.location
|
||||
mesh = obj.data
|
||||
assert obj and isinstance((mesh := obj.data), bpy.types.Mesh)
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
selected_edges = [e for e in bm.edges if e.select]
|
||||
if len(selected_edges) != 2:
|
||||
self.report({"ERROR"}, "Exactly 2 edges should be selected.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Assume the user has selected two edges sharing a vert, but merge verts
|
||||
@@ -235,6 +232,7 @@ class CadArcFrom2Points(bpy.types.Operator):
|
||||
if bpy.context.mode != "EDIT_MESH":
|
||||
return {"CANCELLED"}
|
||||
obj = bpy.context.active_object
|
||||
assert obj
|
||||
region = bpy.context.region
|
||||
region_3d = bpy.context.area.spaces.active.region_3d
|
||||
cursor = bpy.context.scene.cursor.location
|
||||
@@ -242,10 +240,12 @@ class CadArcFrom2Points(bpy.types.Operator):
|
||||
if not center:
|
||||
return {"CANCELLED"}
|
||||
mesh = obj.data
|
||||
assert isinstance(mesh, bpy.types.Mesh)
|
||||
mw = obj.matrix_world
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
selected_verts = [v for v in bm.verts if v.select]
|
||||
if len(selected_verts) != 2:
|
||||
self.report({"ERROR"}, "Exactly 2 vertices should be selected.")
|
||||
return {"CANCELLED"}
|
||||
v1 = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, mw @ selected_verts[0].co)
|
||||
v2 = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, mw @ selected_verts[1].co)
|
||||
@@ -343,10 +343,10 @@ class CadOffset(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_offset"
|
||||
bl_label = "CAD Offset"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
distance: bpy.props.FloatProperty(name="Distance", default=0.1)
|
||||
distance: bpy.props.FloatProperty(name="Distance", default=0.1, subtype="DISTANCE")
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
def poll(cls, context):
|
||||
return context.mode == "EDIT_MESH"
|
||||
|
||||
def draw(self, context):
|
||||
@@ -369,9 +369,6 @@ class CadOffset(bpy.types.Operator):
|
||||
# effect of converting an unclosed loop into a closed loop which is also
|
||||
# not what users expect.
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
self.distance = self.distance * si_conversion
|
||||
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
@@ -543,7 +540,7 @@ class CadOffset(bpy.types.Operator):
|
||||
class AddIfcCircle(bpy.types.Operator):
|
||||
bl_idname = "bim.add_ifccircle"
|
||||
bl_label = "Add IfcCircle"
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.5)
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.5, subtype="DISTANCE")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -551,18 +548,19 @@ class AddIfcCircle(bpy.types.Operator):
|
||||
return bool(obj) and obj.type == "MESH"
|
||||
|
||||
def execute(self, context):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
self.radius = self.radius * si_conversion
|
||||
|
||||
obj = context.active_object
|
||||
assert obj and isinstance((mesh := obj.data), bpy.types.Mesh)
|
||||
self.obj = obj
|
||||
self.mesh = mesh
|
||||
if self.has_selected_existing_circle(context):
|
||||
self.change_radius(context)
|
||||
else:
|
||||
self.create_circle(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
def has_selected_existing_circle(self, context):
|
||||
obj = context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
def has_selected_existing_circle(self, context: bpy.types.Context) -> bool:
|
||||
obj = self.obj
|
||||
bm = bmesh.from_edit_mesh(self, mesh)
|
||||
verts = [v for v in bm.verts if v.select and not v.hide]
|
||||
if len(verts) != 2:
|
||||
return False
|
||||
@@ -575,32 +573,32 @@ class AddIfcCircle(bpy.types.Operator):
|
||||
for group in groups:
|
||||
if group in verts[0][deform_layer] and group in verts[1][deform_layer]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def change_radius(self, context):
|
||||
obj = context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
def change_radius(self, context: bpy.types.Object) -> None:
|
||||
bm = bmesh.from_edit_mesh(self.mesh)
|
||||
verts = [v for v in bm.verts if v.select and not v.hide]
|
||||
center = verts[0].co.lerp(verts[1].co, 0.5)
|
||||
verts[0].co = center + ((verts[0].co - center).normalized() * self.radius)
|
||||
verts[1].co = center + ((verts[1].co - center).normalized() * self.radius)
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
bmesh.update_edit_mesh(self.mesh)
|
||||
|
||||
def create_circle(self, context):
|
||||
obj = context.active_object
|
||||
def create_circle(self, context: bpy.types.Context) -> None:
|
||||
obj = self.obj
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
# The last group may be the result of a prior run of this operator.
|
||||
# I tried looping through all groups but Blender group indices seem to behave unpredictably.
|
||||
if len(obj.vertex_groups):
|
||||
last_group = obj.vertex_groups[-1]
|
||||
verts_in_group = [v for v in obj.data.vertices if last_group.index in [vg.group for vg in v.groups]]
|
||||
verts_in_group = [v for v in self.mesh.vertices if last_group.index in [vg.group for vg in v.groups]]
|
||||
if "IFCCIRCLE" in last_group.name and len(verts_in_group) != 2:
|
||||
obj.vertex_groups.remove(last_group)
|
||||
group = obj.vertex_groups.new(name="IFCCIRCLE")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
bm = bmesh.from_edit_mesh(self.mesh)
|
||||
|
||||
bm.verts.layers.deform.verify()
|
||||
deform_layer = bm.verts.layers.deform.active
|
||||
@@ -617,7 +615,7 @@ class AddIfcCircle(bpy.types.Operator):
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
bmesh.update_edit_mesh(self.mesh)
|
||||
|
||||
|
||||
class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
@@ -638,18 +636,19 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
layout.prop(self, prop)
|
||||
|
||||
def execute(self, context):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
self.radius = self.radius * si_conversion
|
||||
|
||||
obj = context.active_object
|
||||
assert obj and isinstance((mesh := obj.data), bpy.types.Mesh)
|
||||
self.obj = obj
|
||||
self.mesh = mesh
|
||||
if self.has_selected_existing_arc(context):
|
||||
self.change_radius(context)
|
||||
else:
|
||||
self.create_arc(context)
|
||||
return self.create_arc(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
def has_selected_existing_arc(self, context: bpy.types.Context) -> bool:
|
||||
obj = context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
obj = self.obj
|
||||
bm = bmesh.from_edit_mesh(self.mesh)
|
||||
verts = [v for v in bm.verts if v.select and not v.hide]
|
||||
if len(verts) != 3:
|
||||
return False
|
||||
@@ -668,13 +667,14 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
return False
|
||||
|
||||
def change_radius(self, context: bpy.types.Context) -> None:
|
||||
obj = context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
obj = self.obj
|
||||
bm = bmesh.from_edit_mesh(self.mesh)
|
||||
edges = [e for e in bm.edges if e.select and not e.hide]
|
||||
|
||||
mid = list(set(edges[0].verts) & set(edges[1].verts))[0]
|
||||
v1 = edges[0].other_vert(mid)
|
||||
v2 = edges[1].other_vert(mid)
|
||||
assert v1 and v2
|
||||
center = tool.Cad.get_center_of_arc([v1.co, mid.co, v2.co])
|
||||
|
||||
if len(v1.link_edges) != 2 or len(v2.link_edges) != 2:
|
||||
@@ -682,6 +682,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
|
||||
v3 = v1.link_edges[1].other_vert(v1) if mid in v1.link_edges[0].verts else v1.link_edges[0].other_vert(v1)
|
||||
v4 = v2.link_edges[1].other_vert(v2) if mid in v2.link_edges[0].verts else v2.link_edges[0].other_vert(v2)
|
||||
assert v3 and v4
|
||||
dir1 = (v3.co - v1.co).normalized()
|
||||
dir2 = (v4.co - v2.co).normalized()
|
||||
|
||||
@@ -702,11 +703,10 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
def create_arc(self, context):
|
||||
obj = bpy.context.active_object
|
||||
bmesh.update_edit_mesh(self.mesh)
|
||||
|
||||
def create_arc(self, context: bpy.types.Context) -> set[str]:
|
||||
obj = self.obj
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
# The last group may be the result of a prior run of this operator.
|
||||
# I tried looping through all groups but Blender group indices seem to behave unpredictably.
|
||||
@@ -718,7 +718,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
group = obj.vertex_groups.new(name="IFCARCINDEX")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
bm = bmesh.from_edit_mesh(self.mesh)
|
||||
|
||||
bm.verts.layers.deform.verify()
|
||||
deform_layer = bm.verts.layers.deform.active
|
||||
@@ -737,6 +737,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
shared_vert = list(set(selected_edges[0].verts) & set(selected_edges[1].verts))[0]
|
||||
v1 = selected_edges[0].other_vert(shared_vert)
|
||||
v2 = selected_edges[1].other_vert(shared_vert)
|
||||
assert v1 and v2
|
||||
dir1 = (v1.co - shared_vert.co).normalized()
|
||||
dir2 = (v2.co - shared_vert.co).normalized()
|
||||
edge_angle = dir1.angle(dir2)
|
||||
@@ -770,7 +771,8 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
bmesh.update_edit_mesh(self.mesh)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AlignViewToProfile(bpy.types.Operator):
|
||||
@@ -806,8 +808,8 @@ class AlignViewToProfile(bpy.types.Operator):
|
||||
class AddRectangle(bpy.types.Operator):
|
||||
bl_idname = "bim.add_rectangle"
|
||||
bl_label = "Add Rectangle"
|
||||
x: bpy.props.FloatProperty(name="X", default=1)
|
||||
y: bpy.props.FloatProperty(name="Y", default=1)
|
||||
x: bpy.props.FloatProperty(name="X", default=1, subtype="DISTANCE")
|
||||
y: bpy.props.FloatProperty(name="Y", default=1, subtype="DISTANCE")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -815,11 +817,8 @@ class AddRectangle(bpy.types.Operator):
|
||||
return bool(obj) and obj.type == "MESH"
|
||||
|
||||
def execute(self, context):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
self.x = self.x * si_conversion
|
||||
self.y = self.y * si_conversion
|
||||
|
||||
obj = context.active_object
|
||||
assert obj and isinstance(obj.data, bpy.types.Mesh)
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
cursor = obj.matrix_world.inverted() @ context.scene.cursor.location
|
||||
new_verts = [
|
||||
|
||||
@@ -116,9 +116,9 @@ class CadTool(WorkSpaceTool):
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Offset", "S_O", "Offset", ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Rectangle", "S_R", "Rectangle", ui_context)
|
||||
add_layout_hotkey_operator(row, "Rectangle", "S_R", "", ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Circle", "S_C", "Circle", ui_context)
|
||||
add_layout_hotkey_operator(row, "Circle", "S_C", bpy.ops.bim.add_ifccircle.__doc__, ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "3-Point Arc", "S_V", bpy.ops.bim.set_arc_index.__doc__, ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
@@ -238,9 +238,8 @@ class CadHotkey(bpy.types.Operator):
|
||||
row.prop(props, "resolution")
|
||||
|
||||
def hotkey_S_C(self):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
if tool.Geometry.is_profile_object_active():
|
||||
bpy.ops.bim.add_ifccircle(radius=self.props.radius / si_conversion)
|
||||
bpy.ops.bim.add_ifccircle(radius=self.props.radius)
|
||||
else:
|
||||
bpy.ops.bim.cad_arc_from_2_points()
|
||||
|
||||
@@ -248,15 +247,13 @@ class CadHotkey(bpy.types.Operator):
|
||||
bpy.ops.bim.cad_trim_extend()
|
||||
|
||||
def hotkey_S_F(self):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
if tool.Geometry.is_profile_object_active():
|
||||
bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius / si_conversion)
|
||||
bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius)
|
||||
else:
|
||||
bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius / si_conversion)
|
||||
bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius)
|
||||
|
||||
def hotkey_S_O(self):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
bpy.ops.bim.cad_offset(distance=self.props.distance / si_conversion)
|
||||
bpy.ops.bim.cad_offset(distance=self.props.distance)
|
||||
|
||||
def hotkey_S_Q(self):
|
||||
obj = bpy.context.active_object
|
||||
@@ -288,8 +285,7 @@ class CadHotkey(bpy.types.Operator):
|
||||
return
|
||||
|
||||
if tool.Geometry.is_profile_object_active():
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
bpy.ops.bim.add_rectangle(x=self.props.x / si_conversion, y=self.props.y / si_conversion)
|
||||
bpy.ops.bim.add_rectangle(x=self.props.x, y=self.props.y)
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["pset_data"]
|
||||
|
||||
Reference in New Issue
Block a user