JSCAD User Group

    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    1. Home
    2. sopatt
    3. Posts
    • Profile
    • Following 0
    • Followers 0
    • Topics 2
    • Posts 4
    • Best 0
    • Controversial 0
    • Groups 0

    Posts made by sopatt

    • RE: applyTransforms

      @z3dev if there is anything I can do to help, I'm willing.

      I'm happy to share my code as well.

      Here is PosableGeom3.js:

      "use strict";
      const jscad = require('@jscad/modeling');
      const { Pose } = require('./Pose');
      const { mat4, vec3, vec4 } = jscad.maths;
      const { geom3 } = jscad.geometries;
      const { PI } = Math;
      // Base class for geom3 objects with named Poses
      class PosableGeom3 {
        constructor(geometry, poses) {
          this._geometry = geometry || geom3.create();
          this._poses = {};
          if (poses) {
            Object.keys(poses).forEach(key => {
              this._poses[key] = poses[key].clone();
            });
          }
        }
        get polygons() { return this._geometry.polygons; }
        set polygons() { this._geometry.polygons = value; }
        get transforms() { return this._geometry.transforms; }
        set transforms(value) { this._geometry.transforms = value; }
        clone() {
          // Create a new geom3 with deep-copied polygons and transforms
          const clonedGeom = geom3.clone(this._geometry);
          // Clone the poses
          const clonedPoses = {};
          Object.keys(this._poses).forEach(key => {
            clonedPoses[key] = this._poses[key].clone();
          });
          // Create a new PosableGeom3 with the cloned geometry and poses
          return new PosableGeom3(clonedGeom, clonedPoses);
        }
        transform(matrix) {
          this._geometry = geom3.transform(matrix, this._geometry);
          Object.keys(this._poses).forEach(key => {
            this._poses[key].transform(matrix);
          });
          return this;
        }
        getPose(name) {
          return this._poses[name] || new Pose();
        }
      	applyTransforms(){
      		this._geometry = geom3.create(geom3.toPolygons(this._geometry));
      		return this;
      	}
        alignTo(port, targetPose) {
          const sourcePose = this.getPose(port);
          if (!sourcePose) {
            throw new Error(`Invalid port ${port}`);
          }
          if (!targetPose) {
            throw new Error(`Invalid targetPose`);
          }
          return this.transform(
            sourcePose.getMatrix(targetPose)
          );
        }
      }
      module.exports = { PosableGeom3 };
      

      And here is Pose.js:

      "use strict";
      const jscad = require('@jscad/modeling');
      const { vec3, vec4, mat4 } = jscad.maths;
      const { geom3 } = jscad.geometries;
      const { abs } = Math;
      const { translate } = jscad.transforms;
      const { union } = jscad.booleans;
      const { cylinder, sphere, cylinderElliptic } = jscad.primitives;
      
      const x = 0; const y = 1; const z = 2; const w = 3;
      
      class Pose {
        constructor(point, heading, up) {
          // Default position to origin if not provided
          this._point = point ? vec3.clone(point) : vec3.create();
          // Default heading to Y+ (0, 1, 0) if not provided
          this._heading = vec3.normalize(
            vec3.create(), 
            heading ? vec3.clone(heading) : [0, 1, 0]
          );
          // Default up to Z+ (0, 0, 1) if not provided
          this._up = up ? vec3.clone(up) : [0, 0, 1];
      
          // Project up onto the plane perpendicular to heading
          const dot = vec3.dot(this._up, this._heading);
          const projectedUp = vec3.subtract(
            vec3.create(),
            this._up,
            vec3.scale(vec3.create(), this._heading, dot)
          );
          // Check if up is parallel to heading 
          // (length of projectedUp near zero)
          if (vec3.length(projectedUp) < 0.00001) {
            // Choose a perpendicular vector based on heading
            if (abs(this._heading[z]) < 0.99999) {
              this._up = vec3.cross(
                vec3.create(), this._heading, [0, 0, 1]
              );
            } else {
              this._up = vec3.cross(
                vec3.create(), this._heading, [1, 0, 0]
              );
            }
          } else {
            this._up = vec3.normalize(vec3.create(), projectedUp);
          }
          // Ensure up is normalized
          vec3.normalize(this._up, this._up);
        }
        get point() {
          return vec3.clone(this._point);
        }
        get heading() {
          return vec3.clone(this._heading);
        }
        get up() {
          return vec3.clone(this._up);
        }
        clone() {
          return new Pose(this._point, this._heading, this._up);
        }
        transform(matrix) {
          // Transform point (w = 1)
          let v = this._point.concat(1); // Convert vec3 to vec4
          v = vec4.transform(vec4.create(), v, matrix);
          this._point = vec3.clone(v); // Convert back to vec3
      
          // Transform heading (w = 0)
          v = this._heading.concat(0); // Convert vec3 to vec4
          v = vec4.transform(vec4.create(), v, matrix);
          this._heading = vec3.clone(v); // Convert back to vec3
      
          // Transform up (w = 0)
          v = this._up.concat(0); // Convert vec3 to vec4
          v = vec4.transform(vec4.create(), v, matrix);
          this._up = vec3.clone(v); // Convert back to vec3
      
          return this;
        }
      	getMatrix(targetPose) {
      		let t = mat4.create();
      
      		// Step 1: Translate to origin
      		t = mat4.multiply(
      			mat4.create(),
      			mat4.fromTranslation(mat4.create(), vec3.scale(vec3.create(), this._point, -1)),
      			t
      		);
      
      		// Step 2: Align heading with targetPose
      		t = mat4.multiply(
      			mat4.create(),
      			mat4.fromVectorRotation(mat4.create(), this._heading, targetPose._heading),
      			t
      		);
      
      		// Step 3: Roll around heading to align up vector
      		const p = this.clone().transform(t);
      		const dot = vec3.dot(
      			vec3.cross(vec3.create(), p._up, targetPose._up),
      			p._heading
      		);
      		let angle = vec3.angle(p._up, targetPose._up);
      		if (dot < 0) angle = -angle;
      
      		if (Math.abs(angle) > 1e-6) {
      			t = mat4.multiply(
      				mat4.create(),
      				mat4.fromRotation(mat4.create(), angle, targetPose._heading),
      				t
      			);
      		}
      
      		// Step 4: Translate to targetPose.point
      		t = mat4.multiply(
      			mat4.create(),
      			mat4.fromTranslation(mat4.create(), targetPose._point),
      			t
      		);
      
      		return t;
      	}
        render() {
          const vecGeom = (vector) => {
            const vectorLength = vec3.length(vector) || 1;;
            const vectorRadius = vectorLength / 10;
            const arrowLength = vectorLength / 5;
            const arrowRadius = vectorLength / 5;
            let out = union(
              cylinder({ // arrow body
                center: [0, 0, vectorLength / 2],
                height: vectorLength,
                radius: vectorRadius
              }),
              cylinderElliptic({ // arrow head
                center: [0, 0, vectorLength],
                startRadius: [arrowRadius, arrowRadius],
                endRadius: [0, 0],
                height: arrowLength
              })
            );
            out = geom3.transform(
              mat4.fromVectorRotation(
                mat4.create(),
                [0, 0, vectorLength],
                vector
              ),
              out
            );
            return out;
          };
      
          let g = translate(
            this._point,
            union(
              sphere({ radius: 0.2 }),
              vecGeom(this._heading),
              vecGeom(this._up)
            )
          );
          return g;
        }
        roll(angle) {
          if (angle === 0) return this;
          // Create a rotation matrix around the heading vector
          const rotationMatrix = mat4.create();
          mat4.rotate(
            rotationMatrix, rotationMatrix, angle, this._heading
          );
          // Transform the up vector using the rotation matrix (w = 0)
          const transformedUp = vec4.transform(
            vec4.create(), this._up.concat(0), rotationMatrix
          );
          this._up = vec3.clone(transformedUp);
      
          return this;
        }
        translate(vector) {
          this._point = vec3.add(
            vec3.create(),
            vector,
            this._point
          );
        }
      }
      
      module.exports = { Pose }; // end of file
      
      posted in Design Discussions
      sopatt
      sopatt
    • RE: applyTransforms

      @z3dev hello and thank you for the reply.

      I am extending jscad with some custom classes Pose, PosableGeom3 which wraps geom3, and derivative what-not, that allows me to snap different pieces of a composition to each other based on Poses, which is an object consisting of two vectors and a point which represent a position and orientation. So for example I create tubes with different types of bends (curve, straight, U, dogleg) each having a start pose and an end pose. I do something like myGeom1. alignTo('start', myGeom2.getPose('end')); to snap one's start to another's end using a transform. I can then snap the next piece to the end of that one, etc. It's a heck of a lot easier to do this than calculate every object's individual 3D position, orientation, and rotation separately. It's very powerful and solves problems I was struggling with in OpenSCAD.

      Anyway alignTo calls this.geometry = geom3.transform(matrix, this. geometry) but I was finding my geometry was rendering as if the transforms were never applied. Incidentally PosableGeom3 exposes the underlying geom3's polygons and transforms properties through getters as well as transforms through a getter and a setter so I think that implies a PosableGeom3 "Is_a" geom3. Certainly when I pass it to library functions expecting a geom3 it "just works". I'm not sure why that's not enough but I guess this is an unconventional usage of the library hence why I was needing to apply transforms at times other than what the library expects. Again, that's a guess. In any case I was able to work around it by adding an applyTransforms method to my class which contains this one line: this._geometry = geom3.create(geom3.toPolygons(this._geometry));

      Thanks again for all your effort on this. It's been such a fun, useful, and powerful tool.

      posted in Design Discussions
      sopatt
      sopatt
    • applyTransforms

      Hello,

      In the documentation here, it says:

      (static) transform(matrix, geometry) → {geom3}
      Source:
      modeling/src/geometries/geom3/transform.js, line 15
      Transform the given geometry using the given matrix. This is a lazy transform of the polygons, as this function only adjusts the transforms. See applyTransforms() for the actual application of the transforms to the polygons.

      I can't find any other reference to applyTransforms. When I add objects to a union, it doesn't seem to apply the transforms. If I return an array of objects from my main function, it transforms them before rendering. That's fine I guess, but shouldn't union() work? And is there an applyTransforms() somewhere?

      posted in Design Discussions
      sopatt
      sopatt
    • vec3.angle(a,b)

      Hello,

      I am new to jscad. I was using OpenSCAD and didn't know about jscad until recently but I'm loving it.

      On to my comment:

      vec3.angle(a, b) returns the angle using acos. Because acos(-x) = acos(x) it returns the absolute value of the angle, i.e. it leaves out the direction. Maybe the absolute angle is useful but my use case is to roll geometry from a to b, so I want not only the magnitude but the direction. I can still get it another way.

      But thank you for all the hard work.

      posted in Development Discussions
      sopatt
      sopatt