Blender Add-on to create canonical axonometric and oblique projection camera.
""" Create cameras in various standard parallel projection modes. Copyright (C) 2021 Julien Rippinger This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. """ import bpy from bpy.app.handlers import persistent from math import radians, cos CAMERA_SETTINGS = {'axo.isometric': {'rotation': (radians(54.74), 0.0, radians(45)), 'location': (10.0, -10.0, 10.0), 'pixel_ratio': 1}, 'axo.dimetric': {'rotation': (radians(60), 0.0, radians(23)), 'location': (5.53, -13.04, 8.18), 'pixel_ratio': 1}, 'axo.trimetric': {'rotation': (radians(67), 0.0, radians(34)), 'location': (8.59, -12.734, 6.52), 'pixel_ratio': 1}, 'obl.military': {'rotation': (radians(45), 0.0, radians(30)), 'location': (7.071, -12.247, 10*1/cos(radians(45))), 'pixel_ratio': 1/cos(radians(45))}, 'obl.military.short': {'rotation': (radians(45*.82), 0.0, radians(30)), 'location': (5.309, -9.195, 10*1/cos(radians(45))), 'pixel_ratio': 1/cos(radians(45*.82))}, 'obl.cavalier': {'rotation': (radians(45), 0.0, radians(45)), 'location': (10.0, -10.0, 10*1/cos(radians(45))), 'pixel_ratio': 1/cos(radians(45))}, 'obl.cabinet': {'rotation': (radians(45*.82), 0.0, radians(45)), 'location': (7.5, -7.5, 10*1/cos(radians(45))), 'pixel_ratio': 1/cos(radians(45*.82))}, 'obl.cavalier.angle': {'rotation': (radians(15), 0.0, radians(30)), 'location': (10, -10, 10*1/cos(radians(45))), 'pixel_ratio': 1/cos(radians(15))} } def setup_camera(name): """ core function creating camera with selected setups """ bpy.ops.object.camera_add() obj = bpy.context.object scn = bpy.context.scene.render obj.data.type = 'ORTHO' obj.data.ortho_scale = 10 obj.name = name obj.rotation_euler = CAMERA_SETTINGS[name]['rotation'] x, y, z = bpy.context.scene.cursor.location u, v, w = CAMERA_SETTINGS[name]['location'] obj.location = (x+u, y+v, z+w) scn.pixel_aspect_x = CAMERA_SETTINGS[name]['pixel_ratio'] bpy.ops.view3d.object_as_camera() # axonometry operators class AlICe_OT_createisometriccam(bpy.types.Operator): bl_idname = "view3d.create_isometric_cam" bl_label = "isometric projection" bl_description = "create isometric camera" camera_name = 'axo.isometric' def execute(self, context): setup_camera(self.camera_name) return {'FINISHED'} class AlICe_OT_createdimetriccam(bpy.types.Operator): bl_idname = "view3d.create_dimetric_cam" bl_label = "dimetric projection" bl_description = "create dimetric camera" camera_name = 'axo.dimetric' def execute(self, context): setup_camera(self.camera_name) return {'FINISHED'} class AlICe_OT_createtrimetriccam(bpy.types.Operator): bl_idname = "view3d.create_trimetric_cam" bl_label = "trimetric projection" bl_description = "create trimetric camera" camera_name = 'axo.trimetric' def execute(self, context): setup_camera(self.camera_name) return {'FINISHED'} # oblique operators class AlICe_OT_createmilitarycam(bpy.types.Operator): bl_idname = "view3d.create_military_cam" bl_label = "military projection" bl_description = "create military projection camera" camera_name = 'obl.military' def execute(self, context): setup_camera(self.camera_name) return {'FINISHED'} class AlICe_OT_createmilitaryshortcam(bpy.types.Operator): bl_idname = "view3d.create_militaryshort_cam" bl_label = "military short projection" bl_description = "create military short projection camera" camera_name = 'obl.military.short' def execute(self, context): setup_camera(self.camera_name) return {'FINISHED'} class AlICe_OT_createcavaliercam(bpy.types.Operator): bl_idname = "view3d.create_cavalier_cam" bl_label = "cavalier projection" bl_description = "create cavalier projection camera" camera_name = 'obl.cavalier' def execute(self, context): setup_camera(self.camera_name) return {'FINISHED'} class AlICe_OT_createcabinetcam(bpy.types.Operator): bl_idname = "view3d.create_cabinet_cam" bl_label = "cabinet projection" bl_description = "create cabinet projection camera" camera_name = 'obl.cabinet' def execute(self, context): setup_camera(self.camera_name) return {'FINISHED'} # tool panel class AlICe_PT_projectionspanel(bpy.types.Panel): bl_idname = "POHLKE_PT_Camera_Panel" bl_label = "Add Cameras" bl_category = "Parallel Cams" bl_space_type = "VIEW_3D" bl_region_type = "UI" def draw(self, context): layout = self.layout # orthographic projections layout.label(text="Axonometry") layout.operator("view3d.create_isometric_cam", text="Isometric") layout.operator("view3d.create_dimetric_cam", text="Dimetric") layout.operator("view3d.create_trimetric_cam", text="Trimetric") # oblique projections layout.label(text="Oblique") layout.operator("view3d.create_military_cam", text="Military") layout.operator("view3d.create_militaryshort_cam", text="Military shortened") layout.operator("view3d.create_cavalier_cam", text="Cavalier") layout.operator("view3d.create_cabinet_cam", text="Cabinet") @persistent def set_pixel_ratio(self, context): """ change pixel ratio automatically before rendering """ # get scene scn = bpy.context.scene.render # get active camera camera = bpy.context.scene.camera # change pixel ratio if necessary if camera.name.startswith('axo'): scn.pixel_aspect_x = 1 # find oblique projection type elif camera.name.startswith('obl'): if camera.name.startswith('obl.military'): if camera.name.startswith('obl.military.short'): scn.pixel_aspect_x = CAMERA_SETTINGS['obl.military.short']['pixel_ratio'] else: scn.pixel_aspect_x = CAMERA_SETTINGS['obl.military']['pixel_ratio'] elif camera.name.startswith('obl.cavalier'): scn.pixel_aspect_x = CAMERA_SETTINGS['obl.cavalier']['pixel_ratio'] elif camera.name.startswith('obl.cabinet'): scn.pixel_aspect_x = CAMERA_SETTINGS['obl.cabinet']['pixel_ratio'] else: scn.pixel_aspect_x = 1 # register addon bl_info = { "name": "Pohlke - Parallel Projection Cameras", "description": "Create cameras with various standard parallel projection modes", "author": "Julien Rippinger, Michel Lefèvre, alicelab.be", "version": (1, 1), "blender": (3, 0, 0), "location": "3D Viewport > Sidebar (N) > Parallel Cams", "warning": "", "doc_url": "https://codeberg.org/AlICe.lab/pohlke", "tracker_url": "https://codeberg.org/AlICe.lab/pohlke/issues", "category": "Camera", } classes = (AlICe_PT_projectionspanel, AlICe_OT_createisometriccam, AlICe_OT_createdimetriccam, AlICe_OT_createtrimetriccam, AlICe_OT_createmilitarycam, AlICe_OT_createmilitaryshortcam, AlICe_OT_createcavaliercam, AlICe_OT_createcabinetcam) rgstr, unrgstr = bpy.utils.register_classes_factory(classes) def register(): rgstr() bpy.app.handlers.render_init.append(set_pixel_ratio) def unregister(): unrgstr() bpy.app.handlers.render_init.remove(set_pixel_ratio) if __name__ == '__main__': register()